天天看点

JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇

SpringMVC学习笔记

  • 源码篇
    • 处理器适配器
      • 注册流程
      • 处理流程
      • 参数绑定流程
      • 返回值处理流程
      • ResponseBody注解解析
    • 视图解析器
      • 注册流程
      • 处理流程
  • 了解篇
    • 异常处理器
      • 异常理解
      • 异常处理器演示
    • 乱码解决
      • GET请求乱码
      • POST请求乱码
      • 响应乱码
    • 非注解开发方式
      • 处理器开发
      • 配置处理器映射器
      • 配置处理器适配器
      • 注意事项

源码篇

处理器适配器

注册流程

  • 分析源码入口:

    RequestMappingHandlerAdapter#afterPropertiesSet

    方法:
@Override
public void afterPropertiesSet() {
	// Do this first, it may add ResponseBody advice beans
	// 初始化Controller类通知缓存
	initControllerAdviceCache();
	// 初始化参数解析器
	if (this.argumentResolvers == null) {
		List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
		this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	// 初始化处理器Binder参数解析器
	if (this.initBinderArgumentResolvers == null) {
		List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
		this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	// 初始化处理器返回值解析器
	if (this.returnValueHandlers == null) {
		List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
		this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
	}
}
           
  • 以上方法,分别注册了处理器增强类的相关操作(@ModelAttribute、@InitBinder、@ExceptionHandler)、参数解析器、initBinder参数解析器、返回值处理器。这些注册的参数解析器和返回值处理器会在执行Handler方法时进行调用。
  • 进入

    initControllerAdviceCache

    方法看看:
private void initControllerAdviceCache() {
	if (getApplicationContext() == null) {
		return;
	}
	if (logger.isInfoEnabled()) {
		logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
	}
	
	// 查找到所有被ControllerAdvice注解的类
	List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
	AnnotationAwareOrderComparator.sort(adviceBeans);

	List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

	for (ControllerAdviceBean adviceBean : adviceBeans) {
		Class<?> beanType = adviceBean.getBeanType();
		if (beanType == null) {
			throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
		}
		// 获取ControllerAdviceBean中带有ModelAttribute注解但是不带用RequestMapping注解的方法
		Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
		if (!attrMethods.isEmpty()) {
			this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
			if (logger.isInfoEnabled()) {
				logger.info("Detected @ModelAttribute methods in " + adviceBean);
			}
		}
		// 获取ControllerAdviceBean中带有InitBinder注解的方法
		Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
		if (!binderMethods.isEmpty()) {
			this.initBinderAdviceCache.put(adviceBean, binderMethods);
			if (logger.isInfoEnabled()) {
				logger.info("Detected @InitBinder methods in " + adviceBean);
			}
		}
		// 如果该ControllerAdviceBean是RequestBodyAdvice的子类,则将该类放入集合中
		if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
			requestResponseBodyAdviceBeans.add(adviceBean);
			if (logger.isInfoEnabled()) {
				logger.info("Detected RequestBodyAdvice bean in " + adviceBean);
			}
		}
		// 如果该ControllerAdviceBean是ResponseBodyAdvice的子类,则将该类放入集合中
		if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
			requestResponseBodyAdviceBeans.add(adviceBean);
			if (logger.isInfoEnabled()) {
				logger.info("Detected ResponseBodyAdvice bean in " + adviceBean);
			}
		}
	}

	if (!requestResponseBodyAdviceBeans.isEmpty()) {
		this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
	}
}
           
  • 记笔记:
RequestMappingHandlerAdapter#afterPropertiesSet()
  |--RequestMappingHandlerAdapter#initControllerAdviceCache():初始化控制器增强
  |--RequestMappingHandlerAdapter#getDefaultArgumentResolvers():初始化参数解析器
  |--RequestMappingHandlerAdapter#getDefaultInitBinderArgumentResolvers():初始化处理器Binder参数解析器
  |--RequestMappingHandlerAdapter#getDefaultReturnValueHandlers():初始化处理器返回值解析器
           
  • 涉及到的类:
* RequestMappingHandlerAdapter
           

处理流程

  • 分析源码入口:

    DispatcherServlet#doDispatch

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 接着进入

    AbstractHandlerMethodAdapter#handle

    方法:
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {
	return handleInternal(request, response, (HandlerMethod) handler);
}
           
  • 进入

    RequestMappingHandlerAdapter#handleInternal

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 进入

    invokeHandlerMethod

    方法:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
		// 获取数据绑定工厂,作用是为了获取WebDataBinder,WebDataBinder给参数进行类型转换
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		// 设置参数解析器
		if (this.argumentResolvers != null) {
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		// 设置返回值解析器
		if (this.returnValueHandlers != null) {
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}
		invocableMethod.setDataBinderFactory(binderFactory);
		// parameterNameDiscoverer : 参数名称发现者。通过反射可以很容易获取参数的类型,但是参数名称一般都和源码的不一样
		invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

		ModelAndViewContainer mavContainer = new ModelAndViewContainer();
		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
		mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

		AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
		asyncWebRequest.setTimeout(this.asyncRequestTimeout);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.setTaskExecutor(this.taskExecutor);
		asyncManager.setAsyncWebRequest(asyncWebRequest);
		asyncManager.registerCallableInterceptors(this.callableInterceptors);
		asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

		if (asyncManager.hasConcurrentResult()) {
			Object result = asyncManager.getConcurrentResult();
			mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
			asyncManager.clearConcurrentResult();
			if (logger.isDebugEnabled()) {
				logger.debug("Found concurrent result value [" + result + "]");
			}
			invocableMethod = invocableMethod.wrapConcurrentResult(result);
		}
		
		// 调用HandlerMethod,执行请求并处理结果
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		if (asyncManager.isConcurrentHandlingStarted()) {
			return null;
		}

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
		webRequest.requestCompleted();
	}
}
           
  • 进入

    ServletInvocableHandlerMethod#invokeAndHandle

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 到此为止,我们已经看完处理器的处理流程了。
  • 记笔记:
DispatcherServlet#doDispatch():ha.handle(...),以AbstractHandlerMethodAdapter为例
  |--AbstractHandlerMethodAdapter#handle(request, response, handler)
	|--RequestMappingHandlerAdapter#handleInternal(request, response, (HandlerMethod) handler)
	  |--RequestMappingHandlerAdapter#invokeHandlerMethod(request, response, handler)
		|--ServletInvocableHandlerMethod#invokeAndHandle(...):执行请求并处理结果
		  |--InvocableHandlerMethod#invokeForRequest(...):执行请求【参数绑定流程入口】
		  |--HandlerMethodReturnValueHandlerComposite#handleReturnValue(...):处理返回值【返回值处理流程入口】
           
  • 涉及到的类:
* DispatcherServlet
* AbstractHandlerMethodAdapter
* RequestMappingHandlerAdapter
* ServletInvocableHandlerMethod
* InvocableHandlerMethod
* HandlerMethodReturnValueHandlerComposite
           

参数绑定流程

  • 分析入口:

    InvocableHandlerMethod#invokeForRequest

    方法:
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	// 获取方法参数值(这一步也是从request中将参数解析出来的过程)——已经进行类型转换了
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
				"' with arguments " + Arrays.toString(args));
	}
	// 执行HandlerMethod方法,并获取返回值(也就是执行Controller类中的方法)
	Object returnValue = doInvoke(args);
	if (logger.isTraceEnabled()) {
		logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
				"] returned [" + returnValue + "]");
	}
	return returnValue;
}
           
  • 进入

    getMethodArgumentValues

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 重点看看

    HandlerMethodArgumentResolverComposite#resolveArgument

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 此处需要根据具体的参数类型调用相应的解析器,其中简单类型调用的都是

    AbstractNamedValueMethodArgumentResolver

    类中的方法;POJO类型调用的都是

    ModelAttributeMethodProcessor

    类中的方法。我们就以简单类型(对应

    AbstractNamedValueMethodArgumentResolver

    )为例讲解一下具体的参数绑定过程:
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
	
	// NamedValueInfo对象封装了方法参数的[方法名称、是否必须、默认值]三个信息
	// 相当于封装了RequestParam注解(请求参数名称、是否必须、默认值)的信息
	NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
	MethodParameter nestedParameter = parameter.nestedIfOptional();
	
	// 将方法参数名称再处理一下
	Object resolvedName = resolveStringValue(namedValueInfo.name);
	if (resolvedName == null) {
		throw new IllegalArgumentException(
				"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
	}

	// 从request请求中解析出来指定key(参数名称)的值,此时从request中解析出来的参数值都是String类型的。
	Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
	// 空值处理
	if (arg == null) {
		// 使用默认值去设置参数值
		if (namedValueInfo.defaultValue != null) {
			arg = resolveStringValue(namedValueInfo.defaultValue);
		}
		else if (namedValueInfo.required && !nestedParameter.isOptional()) {
			handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
		}
		// 处理空值情况
		arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
	}
	else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
		arg = resolveStringValue(namedValueInfo.defaultValue);
	}

	// 使用WebDataBinder进行数据类型转换
	// WebDataBinder会根据不同的参数,选择不同的PropertyEditor进行参数类型转换
	// 而大多数PropertyEditor都已经内置了,那么如果需要自定义添加PropertyEditor的话,需要使用@InitBinder注解的方法进行处理
	if (binderFactory != null) {
		// 获取web数据绑定器,主要作用是将request请求参数中的值,转换成指定类型的Controller方法参数
		WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
		try {
			// 使用web数据绑定器,将String类型的请求数据,转换成指定数据类型,并绑定到指定方法参数中
			// 此处有两个arg变量,作为参数是String类型的,作为返回值,已经变为了其他类型。
			// parameter参数,表示的是Controller方法的参数
			arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
		}
		catch (ConversionNotSupportedException ex) {
			throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());
		}
		catch (TypeMismatchException ex) {
			throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());

		}
	}

	handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

	return arg;
}
           
  • 我们进入

    RequestParamMethodArgumentResolver#resolveName

    方法看看:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 至于参数绑定器的源码比较复杂,我们就不深入追踪了,我们只需要知道参数转换方式有两种处理方式:

    PropertyEditor

    ConversionService

    ,以及它们处理参数转换的区别就行了。下面代码是在

    TypeConverterDelegate

    类中的:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 记笔记:
InvocableHandlerMethod#invokeForRequest(...)
  |--InvocableHandlerMethod#getMethodArgumentValues(request, mavContainer, providedArgs):参数解析
	|--HandlerMethod#getMethodParameters():得到方法参数
	|--HandlerMethodArgumentResolverComposite#supportsParameter(MethodParameter):看看参数解析器是否可以解析这个参数
	|--HandlerMethodArgumentResolverComposite#resolveArgument(...):执行参数解析
      |--HandlerMethodArgumentResolverCompositegetArgumentResolver(parameter):根据参数获取具体的参数解析器
	  |--AbstractNamedValueMethodArgumentResolver#resolveArgument(...):调用具体的参数解析器进行解析,此处以简单类型的为例
		|--AbstractNamedValueMethodArgumentResolver#getNamedValueInfo(parameter):获取NamedValueInfo信息【方法参数名称、是否必须、默认值】
		|--AbstractNamedValueMethodArgumentResolver#resolveStringValue(value):解析出方法参数名称
		|--RequestParamMethodArgumentResolver#resolveName(...):解析出请求参数中指定名称的参数值,该参数此时是字符串类型
		|--DataBinder#convertIfNecessary(...):利用参数绑定器将字符串类型的请求参数值转换成指定方法参数类型的值
		  |--TypeConverterSupport#convertIfNecessary(...)
			|--TypeConverterSupport#doConvert(...)
			  |--TypeConverterDelegate#convertIfNecessary(...):交给类型转换器代理处理
				|--TypeConverterDelegate#convertIfNecessary(...)
				  |--PropertyEditorRegistrySupport#findCustomEditor(...):获取自定义属性编辑器【PropertyEditor】——处理String→任意类型
				  |--PropertyEditorRegistrySupport#fgetConversionService(...):获取自定义类型转换服务【ConversionService】——处理任意类型→任意类型
  |--InvocableHandlerMethod#doInvoke(args):参数绑定之后,真正执行HandlerMethod对象
           
  • 涉及到的类:
* InvocableHandlerMethod
* HandlerMethod
* HandlerMethodArgumentResolverComposite
* AbstractNamedValueMethodArgumentResolver
* RequestParamMethodArgumentResolver
* DataBinder
* TypeConverterSupport
* TypeConverterDelegate
* PropertyEditorRegistrySupport
           

返回值处理流程

  • 分析入口:

    ServletInvocableHandlerMethod#invokeAndHandle

    方法中的

    handleReturnValue

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 返回值由

    this.returnValueHandlers

    进行处理,这个成员变量的类型是

    HandlerMethodReturnValueHandlerComposite

    ,它继承自

    HandlerMethodReturnValueHandler

    类,该类有两个方法:
public interface HandlerMethodReturnValueHandler {
	// 是否支持指导返回值类型解析
	boolean supportsReturnType(MethodParameter returnType);
	// 解析返回值
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
           
  • 我们进入

    HandlerMethodReturnValueHandlerComposite#handleReturnValue

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 记笔记:
HandlerMethodReturnValueHandlerComposite#handleReturnValue(...)
  |--HandlerMethodReturnValueHandlerComposite#selectHandler(...)
  |--调用具体的处理器处理,以`@ResponseBody`注解为例
           
  • 涉及到的类:
* HandlerMethodReturnValueHandlerComposite
           

ResponseBody注解解析

  • 如果返回值使用

    @ResponseBody

    注解,那么由它注解的返回值会使用

    RequestResponseBodyMethodProcessor

    类进行返回值处理,所以分析入口:

    RequestResponseBodyMethodProcessor#handleReturnValue

    方法
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 进入

    AbstractMessageConverterMethodProcessor#writeWithMessageConverters

    方法,这个方法处理的逻辑比较长,我们挑重点代码进行阅读:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 继续深入

    AbstractGenericHttpMessageConverter#write

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 进入

    AbstractJackson2HttpMessageConverter#writeInternal

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 记笔记:
RequestResponseBodyMethodProcessor#handleReturnValue(...)
	|--AbstractMessageConverterMethodProcessor#writeWithMessageConverters(...)
		|--AbstractGenericHttpMessageConverter#write(...)
			|--AbstractGenericHttpMessageConverter#writeInternal(...)
           
  • 涉及到的类:
* RequestResponseBodyMethodProcessor
* AbstractMessageConverterMethodProcessor
* AbstractGenericHttpMessageConverter
           

视图解析器

注册流程

处理流程

  • 分析入口:

    DispatcherServlet#processDispatchResult

    方法:
  • 进入processDispatchResult方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 然后进入

    render

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 进入

    resolveViewName

    方法:
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
		Locale locale, HttpServletRequest request) throws Exception {

	if (this.viewResolvers != null) {
		for (ViewResolver viewResolver : this.viewResolvers) {
			// 解析视图名称
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
	}
	return null;
}
           
  • 进入

    ViewResolverComposite#resolveViewName

    方法:
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
	for (ViewResolver viewResolver : this.viewResolvers) {
		View view = viewResolver.resolveViewName(viewName, locale);
		if (view != null) {
			return view;
		}
	}
	return null;
}
           
  • 接下来进入

    AbstractCachingViewResolver#resolveViewName

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 接下来进入

    UrlBasedViewResolver#createView

    方法:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 经过几波周转,最终请求进入到

    UrlBasedViewResolver#buildView

    方法,获得基础视图对象:
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇

最终由该类以下方法获取真正的视图对象

protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {
	ApplicationContext context = getApplicationContext();
	if (context != null) {
		Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);
		if (initialized instanceof View) {
			return (View) initialized;
		}
	}
	return view;
}
           
  • 记笔记:
DispatcherServlet#processDispatchResult(...)
  |--DispatcherServlet#render(...):视图渲染
	|--DispatcherServlet#resolveViewName(...):解析视图名称
	  |--ViewResolverComposite#resolveViewName(...)
		|--AbstractCachingViewResolver#resolveViewName(...)
		  |--UrlBasedViewResolver#createView(...)
			|--AbstractCachingViewResolver#createView(...)
			  |--UrlBasedViewResolver#loadView(...)
				|--UrlBasedViewResolver#buildView(...)
				|--UrlBasedViewResolver#applyLifecycleMethods(...):获取真正的视图对象
           
  • 涉及的类:
* DispatcherServlet
* ViewResolverComposite
* AbstractCachingViewResolver
* UrlBasedViewResolver
           

了解篇

异常处理器

  • 异常处理器(HandlerExceptionResolver):SpringMVC在处理请求过程中出现的异常信息交由异常处理器进行处理。异常处理器可以实现针对一个应用系统中出现的异常进行相应的处理。
JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇

异常理解

JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(6)源码篇了解篇
  • 我们通常说的异常包含编译时异常(已检查异常、预期异常)和运行时异常(未检查异常)
    • 继承RuntimeException类的异常类,就是运行时异常。
    • 继承Exception类的异常类,没有继承RuntimeException类的异常类,就是编译时异常。
  • 常见异常举例:
    • 运行时异常,比如:空指针异常、数组越界异常等。对于这类异常,只能通过程序员丰富的经验来解决和测试人员不断的严格测试来解决。
    • 编译时异常,比如:数据库异常、文件读取异常、自定义异常等。对于这类异常,必须使用

      try..catch

      代码块或者throws关键字来处理异常。

异常处理器演示

  • 自定义异常类:
public class BusinessException extends Exception {
	private static final long serialVersionUID = 1L;
	// 异常信息
	private String message;
	public BusinessException(String message) {
		super(message);
		this.message = message;
	}
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
}
           
  • 异常处理器实现:
public class BusinessExceptionResolver implements HandlerExceptionResolver {
	@Override
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
		// 自定义预期异常
		BusinessException businessException = null;
		// 如果抛出的是系统自定义的异常
		if(ex instanceof BusinessException) {
			businessException = (BusinessException) ex;
		}else {
			businessException = new BusinessException("未知错误");
		}
		ModelAndView modelAndView = new ModelAndView();
		// 把错误信息传递到页面
		modelAndView.addObject("message", businessException.getMessage());
		// 指向错误页面
		modelAndView.setViewName("error");
		return modelAndView;
	}
}
           
  • 异常处理器配置:在springmvc.xml中加入一下代码
<!-- 自定义异常处理器(全局) -->
<bean class="com.yw.springmvc_demo.exception.resolver.BusinessExceptionResolver"/>
           
  • 错误页面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>参数绑定演示demo</title>
</head>
<body>
${message}
</body>
</html>
           
  • 异常测试:
@RequestMapping(value = "/showItemEdit")
@ResponseBody
public String showItemEdit(Integer id) throws Exception{
	Item item = itemService.queryItemById(id);
	if(item == null){
		throw new BusinessException("查询不到商品无法修改");
	}
	return item;
}
           

乱码解决

GET请求乱码

  • 原因分析:GET 请求参数是通过请求首行中的URI发送给Web服务器(Tomcat)的,Tomcat服务器会对URI进行编码操作(此时使用的是Tomcat设置的字符集,默认是ISO8859-1),到了我们的应用程序中的请求参数,已经被Tomcat使用ISO8859-1字符集进行编码了。
  • 解决方式1:修改Tomcat配置文件,指定UTF-8编码:
  • 解决方式2:对请求参数进行重新编码:
String username = request.getParamter("username");
username = new String(username.getBytes("ISO8859-1"), "utf-8");
           
  • 解决方式3:过滤器+请求装饰器统一解决请求乱码,自定义 MyRequestWrapper、MyCharacterEncodingFilter。

POST请求乱码

  • 在 web.xml 文件中加入如下配置:
<filter>
	<filter-name>CharacterEncodingFilter</filter-name>
	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
	<init-param>
		<param-name>encoding</param-name>
		<param-value>utf-8</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>CharacterEncodingFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
           

响应乱码

  • 使用@RequestMapping注解中的produces属性,指定响应体中的编码格式
// @RequestMapping注解中的consumes和produces分别是为请求头和响应头设置contentType
@RequestMapping(value = "responseString", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String responseString() {
	// 如果在使用@ResponseBody注解的前提下,如果返回值是String类型,则返回值会由StringHttpMessageConverter进行处理
	return "查询失败";
}
           

非注解开发方式

处理器开发

  • 实现HttpRequestHandler接口:
// 第二种Handler处理器的编写方式:实现httpRequestHandler接口
public class DemoController implements HttpRequestHandler{
	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
		// 数据填充
		request.setAttribute("msg", "实现HttpRequestHandler接口的开发方式");
		request.getRequestDispatcher("/WEB-INF/jsp/msg.jsp").forward(request, response);
	}
}
           
  • 配置处理器:

配置处理器映射器

  • 在 DispatcherServlet.properties 文件中出现的处理器适配器和处理器映射器,如果没有特殊要求(比如在适配器中配置转换器、在映射器中配置拦截器),那么就不需要再单独进行配置了。
  • BeanNameUrlHandlerMappig 处理器映射器的映射思路:解析 bean 标签中的 name 属性值(请求URL),作为 key;解析 bean 标签中的 class 属性值,作为 value。
  • 根据映射器的映射规则,在 springmvc.xml 中配置请求 URL 和处理器之间的映射关系
<!-- 配置处理器 -->
<bean name="/helloHandler" class="com.yw.springmvc_demo.controller.DemoController"/>
           

配置处理器适配器

  • HttpRequestHandlerAdapter
public class HttpRequestHandlerAdapter implements HandlerAdapter {
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}
	@Override
	public boolean supports(Object handler) {
		return handler instanceof HttpRequestHandler;
	}
}
           
<!-- 配置http请求处理器适配器 -->
<bean name="/helloHandler" class="org.springframeword.web.servlet.mvc.HttpRequestHandlerAdapter"/>
           
  • SimpleControllerHandlerAdapter
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		return ((Controller) handler).handleRequest(request, response);
	}
	@Override
	public boolean supports(Object handler) {
		return handler instanceof Controller;
	}
}
           
<!-- 配置处理器 -->
<bean name="/helloHandler" class="org.springframeword.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
           

注意事项

  • 处理器映射器和处理器适配器可以同时存在多个
  • 非注解方式开发的处理器只能使用非注解的映射器和适配器进行处理

继续阅读