SpringMVC 异常处理机制,

Posted QQ_851228082

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringMVC 异常处理机制,相关的知识,希望对你有一定的参考价值。

首先异常处理属于SpringMVC模块,而不属于spring core,有这种意识能帮助我们更好的理解spring framework。SpringMVC统一异常处理机制,一共有两种,第一种实现HandlerExceptionResolver接口、第二种在异常处理类上添加@ControllerAdvice注解,在方法上加@ExceptionHandler处理指定异常类型。

实现HandlerExceptionResolver

HandlerExceptionResolver只有一个resolveException方法,返回参数ModelAndView,前后端不分离时通常使用这种方式,大体逻辑是判断是否是ajax请求,如果是ajax请求,则返回null,否则返回非null的ModelAndView。

public interface HandlerExceptionResolver 
	@Nullable
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response,
			 @Nullable Object handler, Exception ex);

DefaultHandlerExceptionResolver是此接口的默认实现,看一下DefaultHandlerExceptionResolver#doResolveException实现。

protected ModelAndView doResolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 

		try 
		    //这就是常见的,状态码405,请求方法不正确导致
			if (ex instanceof HttpRequestMethodNotSupportedException) 
				return handleHttpRequestMethodNotSupported(
						(HttpRequestMethodNotSupportedException) ex, request, response, handler);
			
			 //这就是常见的,状态码415,媒体类型不正确导致
			else if (ex instanceof HttpMediaTypeNotSupportedException) 
				return handleHttpMediaTypeNotSupported(
						(HttpMediaTypeNotSupportedException) ex, request, response, handler);
			
			//其他逻辑
		
		catch (Exception handlerEx) 
			if (logger.isWarnEnabled()) 
				logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
			
		
		//本类处理不了,返回null,以让后边的ExceptionResolver处理
		return null;
	

这是第一种处理异常的方法。

@ControllerAdvice、@ExceptionHandler

写一个类,添加@ControllerAdvice(更通常的使用@RestControllerAdvice),在类的方法上添加@ExceptionHandler,并指明要处理的Exception类型。

@ControllerAdvice
public class MyExceptionHanlder
    //注解可以不指定异常,方法参数上指定
	@ExceptionHandler
	public ModelAndView handleException1(Exception1 excep)
	//只处理Exception1类型的异常
   
   //也可以配置在注解上,优先级大于方法参数,一般用来缩小可处理的异常范围
   @ExceptionHandler(Exception2.class)
	public ModelAndView handleException1(Exception2Parent excep)
	//只处理Exception1类型的异常
   
   @ExceptionHandler
	public ModelAndView handleException(Exception excep)
	//兜底,处理其他未处理的异常
   -.

@RestControllerAdvice适合前后端分离情况,它等于@ControllerAdvice+@ResponseBody

@RestControllerAdvice
public class MyExceptionHanlder
	@ExceptionHandler
	public  MyVO handleException1(Exception1 excep)
   

这是第二种处理异常的方法。

SpringMVC中返回值统一处理

有时需要对返回值做统一处理,比如对返回值统一包装为code:200,msg:"",data:object,controller方法返回参数可以是POJO,而不必是Result<T>的样子,那么就需要实现ResponseBodyAdvice接口,它在@ResponseBody或者ResponseEntity之后,但在HttpMessageConverter之前执行,通常与异常处理类一起使用。

@RestControllerAdvice
public class MyExceptionHanlder implements ResponseBodyAdvice<Object>
	@ExceptionHandler
	public  MyVO handleException1(Exception1 excep)
   
   boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType)
	return true;
	
	//在这里将返回值包装为统一数据类型
	Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType,
			ServerHttpRequest request, ServerHttpResponse response)
		 if(body.getClass != Result.class)
			return Result<>(body);
		
		return body;
	

springmvc 异常处理机制原理

MvcNameSpaceHandler解析<mvc:annotation-config/>时依次注入ExceptionHandlerExceptionResolverResponseStatusExceptionHandler(用来处理ResponseStatusException)、DefaultHandlerExceptionResolver

public BeanDefinition parse(Element element, ParserContext context) 
	// 先注册ExceptionHandlerExceptionResolver,用来处理加了@ExceptionHandler方法抛出的异常
	RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
	methodExceptionResolver.getPropertyValues().add("order", 0);//优先级最高
	String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);
	// 再注册ResponseStatusExceptionResolver
	RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
	statusExceptionResolver.getPropertyValues().add("order", 1);//优先级其次
	String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);
	//然后注册DefaultHandlerExceptionResolver
	RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
	defaultExceptionResolver.getPropertyValues().add("order", 2);//优先级再次
	String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);

然后 DispatcherServlet#initStrategies初始化异常解析器类。

protected void initStrategies(ApplicationContext context) 
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		//初始化处理异常解析器
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	

这一步中除了系统内部定义的ExceptionResolver,还会将自定义HandlerExceptionResolver加入到处理链中。

private void initHandlerExceptionResolvers(ApplicationContext context) 
		this.handlerExceptionResolvers = null;

		if (this.detectAllHandlerExceptionResolvers) 
			// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
			//初始化ExceptionResolver
			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
			if (!matchingBeans.isEmpty()) 
				this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
				// We keep HandlerExceptionResolvers in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
			
		
		
	

遇到异常时会依次执行上边注册的ExceptionHandlerExceptionResolverResponseStatusExceptionHandlerDefaultHandlerExceptionResolver



可以看到这三个类都是HandlerExceptionResovler的子类。

springBoot的初始化ExceptionResolver过程


org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#handlerExceptionResolver 中,将ExceptionHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerExceptionResolver封装成HandlerExceptionResolverComposite

@Bean
	public HandlerExceptionResolver handlerExceptionResolver(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) 
		List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
		configureHandlerExceptionResolvers(exceptionResolvers);
		if (exceptionResolvers.isEmpty()) 
		     //获取默认HandlerExceptionResolver,其实就是
			addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
		
		extendHandlerExceptionResolvers(exceptionResolvers);
		//
		HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
		composite.setOrder(0);
		composite.setExceptionResolvers(exceptionResolvers);
		return composite;
	

在org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers中,将3种ExceptionResolver封装成HandlerExceptionResolverComposite 的实现原理。

protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
			ContentNegotiationManager mvcContentNegotiationManager) 

		ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
		exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
		exceptionHandlerResolver.setMessageConverters(getMessageConverters());
		exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
		exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
		if (jackson2Present) 
			exceptionHandlerResolver.setResponseBodyAdvice(
					Collections.singletonList(new JsonViewResponseBodyAdvice()));
		
		if (this.applicationContext != null) 
			exceptionHandlerResolver.setApplicationContext(this.applicationContext);
		
		//解析含有@ControllerAdvice的类
		exceptionHandlerResolver.afterPropertiesSet();
		//先添加ExceptionHandlerExceptionResolver 
		exceptionResolvers.add(exceptionHandlerResolver);
		//再添加ResponseStatusExceptionResolver 
		ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
		responseStatusResolver.setMessageSource(this.applicationContext);
		exceptionResolvers.add(responseStatusResolver);
		//最后添加DefaultHandlerExceptionResolver
		exceptionResolvers.add(new DefaultHandlerExceptionResolver());
	

@ExceptionHandler的实现原理

org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache中搜索@ControllerAdvice的bean

	private void initExceptionHandlerAdviceCache() 
		if (getApplicationContext() == null) 
			return;
		

		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
		for (ControllerAdviceBean adviceBean : adviceBeans) 
			Class<?> beanType = adviceBean.getBeanType();
			if (beanType == null) 
				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
			
			ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
			if (resolver.hasExceptionMappings()) 
				this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
			
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) 
				this.responseBodyAdvice.add(adviceBean);
			
		

然后在org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver中查找本类及父类含有@ExceptionHandler的方法

public ExceptionHandlerMethodResolver(Class<?> handlerType) 
		for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) 
			for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) 
				addExceptionMapping(exceptionType, method);
			
		
	

HandlerExceptionResolverComposite中ExceptionResolver的执行顺序

顺序执行,直到返回非null的ModelAndView,这一点与org.springframework.web.servlet.DispatcherServlet#processHandlerException是一样的。
org.springframework.web.servlet.handler.HandlerExceptionResolverComposite#resolveException

@Override
	@Nullable
	public ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 

		if (this.resolvers != null) 
			for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) 
				ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
				if (mav != null) 
					return mav;
				
			
		
		return null;
	

初次之外,dispatchservlet还注册了DefaultErrorAttributes

@Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() 
		return new DefaultErrorAttributes();
	

总结

从上可以看出,springmvc与springboot 的异常处理内部实现原理有点差异,springboot兼容了springmvc。

  • springmvc
    • DispatcherServlet中注册了ExceptionHandlerExceptionResolverResponseStatusExceptionHandler(用来处理ResponseStatusException)、DefaultHandlerExceptionResolver及自定义HandlerExceptionResolver
  • springboot
    • DispatcherServlet中注册了,DefaultErrorAttributesHandlerExceptionResolverComposite及自定义HandlerExceptionResolver,而HandlerExceptionResolverComposite内又依次执行ExceptionHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerExceptionResolver,只要ExceptionHandlerExceptionResolver匹配了异常则不再执行后续HandlerExceptionResolver。

以上是关于SpringMVC 异常处理机制,的主要内容,如果未能解决你的问题,请参考以下文章

SpringMVC 异常处理机制,

SpringMVC——拦截器异常处理机制

SpringMVC之异常处理机制

Spring MVC学习—项目统一异常处理机制详解与使用案例

面试官:你能给我谈谈Spring MVC的异常处理机制吗?

SpringMvc如何进行异常处理以及常见的注解