In projects, ExceptionHandler is often used as a global exception handling center. So what is the principle of ExceptionHandler handling exceptions, today to analyze it.

ExceptionHandler Example

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(value = RuntimeException.class)
    public String handle() {return "error"; }}Copy the code

It is easy to use. Add the ControllerAdvice annotation to the class and the ExceptionHandler annotation to the method to handle the corresponding exception message in the method.

The principle of analyzing

The role of ControllerAdvice and ExceptionHandler annotations

Exception handling core classes is ExceptionHandlerExceptionResolver, enter the class. Look at the afterPropertiesSet method.

	public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans initExceptionHandlerAdviceCache(); . } private voidinitExceptionHandlerAdviceCache() {
		if (getApplicationContext() == null) {
			return; } // This line of code will find all the classes marked with ControllerAdvice annotations List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans);for(ControllerAdviceBean adviceBean : adviceBeans) { Class<? > beanType = adviceBean.getBeanType();if (beanType == null) {
				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: "+ adviceBean); } // Iterate through these classes to find the methods marked with the ExceptionHandler annotation. ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {
				this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
			}
			if(ResponseBodyAdvice.class.isAssignableFrom(beanType)) { this.responseBodyAdvice.add(adviceBean); }}... }Copy the code

Through the above code, you can see that in ExceptionHandlerExceptionResolver class, the class to scan all labeled ExceptionHandler annotation method, and bring them into the exceptionHandlerAdviceCache.

Principle of exception handling

Having seen the ControllerAdvice and ExceptionHandler annotations in action, let’s look at how exception handling works. Enter the doDispatch method of the DispatcherServlet

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { ... // Handle the controller method mv = ha.handle(processedRequest, response, mappedHandler.gethandler ()); . } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }... }Copy the code

It can be seen from the doDispatch method that the program first processes the business logic of the Controller layer. For the exceptions thrown by the business logic, the program is uniformly encapsulated and then processed in the processDispatchResult method. So we went into the method to find out.

	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false; // If an exception occurs in the program, handle itif(exception ! = null) {if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}
		...
	}

	protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {

		// Success and error responses may use different content types
		request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

		// Check registered HandlerExceptionResolvers...
		ModelAndView exMv = null;
		if(this.handlerExceptionResolvers ! = null) {/ / traverse handlerExceptionResolvers exception information processingfor (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				exMv = resolver.resolveException(request, response, handler, ex);
				if(exMv ! = null) {break; }}}... }Copy the code

Where did the handlerExceptionResolvers here?

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

		if (this.detectAllHandlerExceptionResolvers) {
			// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
					.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true.false); . }... }Copy the code

When the DispatcherServlet is initialized, it looks for a class of type HandlerExceptionResolver in the container. And just ExceptionHandlerExceptionResolver class is inherited HandlerExceptionResolver interface, so this place will put him in the DispatcherServlet. So the above traverse handlerExceptionResolvers processing abnormal information, is to call the ExceptionHandlerExceptionResolver resolveException method. So let’s go into that method.

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

		if (shouldApplyTo(request, handler)) {
			prepareResponse(ex, response);
			ModelAndView result = doResolveException(request, response, handler, ex); . } } protected final ModelAndViewdoResolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

		return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
	}

	protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); .else{ // Otherwise, just the given exception as-is exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod); }}... } public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { ObjectreturnValue = invokeForRequest(webRequest, mavContainer, providedArgs); . } public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... ProvidedArgs) throws Exception {// Obtain method parameters Object[] args = getMethodArgumentValues(Request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {
			logger.trace("Arguments: "+ Arrays.toString(args)); } // Execute methodreturn doInvoke(args);
	}
Copy the code

The entire exception execution logic as above code, simple point is to find the corresponding exception handling method, execute him. The logic in getMethodArgumentValues is the same as that in SpringBoot’s source-parsed controller layer, but the types of arguments they can handle are different.

Check the afterPropertiesSet ExceptionHandlerExceptionResolver class methods:

	public void afterPropertiesSet() {...if(this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); }... } protected List<HandlerMethodArgumentResolver>getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

		// Annotation-based argument resolution
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());

		// Custom arguments
		if(getCustomArgumentResolvers() ! = null) { resolvers.addAll(getCustomArgumentResolvers()); }return resolvers;
	}
Copy the code

This is the type of argument that is accepted in the ExceptionHandler method. Take a look, it is much less than the controller side of the type, pay attention to use it.


Returns the directory