Spring MVC exception handling mechanism,

Keywords: Java Spring Spring Boot

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.

Posted by phencesgirl on Fri, 24 Sep 2021 07:01:12 -0700