First, exception handling belongs to the spring MVC module, not the spring core. Having this awareness can help us better understand the spring framework. There are two spring MVC unified exception handling mechanisms. The first is to implement the HandlerExceptionResolver interface. The second is to add @ ControllerAdvice annotation on the exception handling class and @ ExceptionHandler on the method to handle the specified exception type.
Implement HandlerExceptionResolver
HandlerExceptionResolver has only one resolveException method, which returns the parameter ModelAndView. This method is usually used when the front and back ends are not separated. The general logic is to judge whether it is an ajax request. If it is an ajax request, it returns null, otherwise it returns a non null ModelAndView.
public interface HandlerExceptionResolver { @Nullable ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex); }
DefaultHandlerExceptionResolver is the default implementation of this interface. Take a look at the implementation of DefaultHandlerExceptionResolver#doResolveException.
protected ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { try { //This is common. The status code 405 is caused by incorrect request method if (ex instanceof HttpRequestMethodNotSupportedException) { return handleHttpRequestMethodNotSupported( (HttpRequestMethodNotSupportedException) ex, request, response, handler); } //This is common. The status code 415 is caused by incorrect media type else if (ex instanceof HttpMediaTypeNotSupportedException) { return handleHttpMediaTypeNotSupported( (HttpMediaTypeNotSupportedException) ex, request, response, handler); } //Other logic } catch (Exception handlerEx) { if (logger.isWarnEnabled()) { logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx); } } //This class cannot handle it and returns null to be handled by the following ExceptionResolver return null; }
This is the first way to handle exceptions.
@ControllerAdvice,@ExceptionHandler
Write a class, add @ controlleradvice (more commonly, @ RestControllerAdvice), add @ ExceptionHandler on the method of the class, and indicate the type of Exception to be processed.
@ControllerAdvice public class MyExceptionHanlder{ //Annotations can be specified on method parameters without specifying exceptions @ExceptionHandler public ModelAndView handleException1(Exception1 excep){ //Only exceptions of type Exception1 are handled } //It can also be configured on the annotation. The priority is greater than the method parameter. It is generally used to narrow the range of exceptions that can be handled @ExceptionHandler({Exception2.class}) public ModelAndView handleException1(Exception2Parent excep){ //Only exceptions of type Exception1 are handled } @ExceptionHandler public ModelAndView handleException(Exception excep){ //Handle other unhandled exceptions }-. }
@RestControllerAdvice is suitable for front and rear end separation, which is equal to @ ControllerAdvice+@ResponseBody
@RestControllerAdvice public class MyExceptionHanlder{ @ExceptionHandler public MyVO handleException1(Exception1 excep){ } }
This is the second way to handle exceptions.
Unified processing of return values in spring MVC
Sometimes the return value needs to be uniformly processed. For example, the return value is uniformly wrapped as {code:200,msg:"",data:object}. The return parameter of the controller method can be POJO instead of result < T >. Then it is necessary to implement the ResponseBodyAdvice interface, which is executed after @ ResponseBody or ResponseEntity but before HttpMessageConverter, Usually used with exception handling classes.
@RestControllerAdvice public class MyExceptionHanlder implements ResponseBodyAdvice<Object>{ @ExceptionHandler public MyVO handleException1(Exception1 excep){ } boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType){ return true; } //Here, the return value is wrapped as a unified data type 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; } }
Principle of spring MVC exception handling mechanism
When MvcNameSpaceHandler resolves < MVC: annotation config / >, it successively injects ExceptionHandlerExceptionResolver, responsestatusexceptionhandler (used to handle ResponseStatusException) and DefaultHandlerExceptionResolver
public BeanDefinition parse(Element element, ParserContext context) { // First register the ExceptionHandlerExceptionResolver to handle the exceptions thrown by the @ ExceptionHandler method RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class); methodExceptionResolver.getPropertyValues().add("order", 0);//Highest priority String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver); // Re register ResponseStatusExceptionResolver RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class); statusExceptionResolver.getPropertyValues().add("order", 1);//Second priority String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver); //Then register the DefaultHandlerExceptionResolver RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class); defaultExceptionResolver.getPropertyValues().add("order", 2);//Priority again String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver); }
Then DispatcherServlet#initStrategies initializes the exception parser class.
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); //Initialize exception handling parser initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
In this step, in addition to the exception resolver defined in the system, the custom handlerexception resolver will also be added to the processing chain.
private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null; if (this.detectAllHandlerExceptionResolvers) { // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. //Initialize 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); } } }
When an exception is encountered, execute the above registered ExceptionHandlerExceptionResolver, ResponseStatusExceptionHandler and DefaultHandlerExceptionResolver in turn,
You can see that these three classes are subclasses of HandlerExceptionResovler.
Initializing ExceptionResolver procedure for springBoot
stay
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#handlerExceptionResolver, encapsulate ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver and DefaultHandlerExceptionResolver into HandlerExceptionResolverComposite
@Bean public HandlerExceptionResolver handlerExceptionResolver( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) { List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>(); configureHandlerExceptionResolvers(exceptionResolvers); if (exceptionResolvers.isEmpty()) { //Get the default HandlerExceptionResolver, which is addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager); } extendHandlerExceptionResolvers(exceptionResolvers); // HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); composite.setOrder(0); composite.setExceptionResolvers(exceptionResolvers); return composite; }
In org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers, three kinds of exceptionresolvers are encapsulated into the implementation principle of 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); } //Resolve the class containing @ ControllerAdvice exceptionHandlerResolver.afterPropertiesSet(); //Add ExceptionHandlerExceptionResolver first exceptionResolvers.add(exceptionHandlerResolver); //Add ResponseStatusExceptionResolver ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver(); responseStatusResolver.setMessageSource(this.applicationContext); exceptionResolvers.add(responseStatusResolver); //Finally, add DefaultHandlerExceptionResolver exceptionResolvers.add(new DefaultHandlerExceptionResolver()); }
@Implementation principle of ExceptionHandler
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache search for @ 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); } } }
Then, find the methods of this class and its parent class containing @ ExceptionHandler in org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver
public ExceptionHandlerMethodResolver(Class<?> handlerType) { for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) { addExceptionMapping(exceptionType, method); } } }
Execution order of ExceptionResolver in HandlerExceptionResolverComposite
Execute sequentially until a non null ModelAndView is returned, which is the same as 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; }
In addition to the initial, the dispatchservlet also registers DefaultErrorAttributes
@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }
summary
It can be seen from the above that the internal implementation principles of exception handling between spring MVC and spring boot are somewhat different. Spring boot is compatible with spring MVC.
- springmvc
- ExceptionHandlerExceptionResolver, responsestatusexceptionhandler (used to handle ResponseStatusException), DefaultHandlerExceptionResolver and custom HandlerExceptionResolver are registered in the dispatcher servlet
- springboot
- DefaultErrorAttributes, HandlerExceptionResolverComposite and custom HandlerExceptionResolver are registered in the dispatcher servlet, and the HandlerExceptionResolverComposite executes ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver and DefaultHandlerExceptionResolver in turn, As long as the ExceptionHandlerExceptionResolver matches the exception, no subsequent HandlerExceptionResolver will be executed.
- DefaultErrorAttributes, HandlerExceptionResolverComposite and custom HandlerExceptionResolver are registered in the dispatcher servlet, and the HandlerExceptionResolverComposite executes ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver and DefaultHandlerExceptionResolver in turn, As long as the ExceptionHandlerExceptionResolver matches the exception, no subsequent HandlerExceptionResolver will be executed.