SpringBoot Source Parsing-Principle of Exception Handler Handling Exceptions

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

Example of ExceptionHandler

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

Use is still very simple, add Controller Advice annotation on the class, add ExceptionHandler annotation on the method, you can handle the corresponding exception information in the method.

Principle analysis

Controller Advice and Exception Handler Annotations

The core class of exception handling is ExceptionHandlerExceptionResolver, which is entered into this class. View the afterPropertiesSet method.

    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBodyAdvice beans
        initExceptionHandlerAdviceCache();
        ...
    }

    private void initExceptionHandlerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }
        //This line of code finds all the classes marked with Controller Advice 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);
            }
            //Traverse through these classes to find ways to annotate ExceptionHandler annotations.
            ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
            if (resolver.hasExceptionMappings()) {
                this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
            }
            if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                this.responseBodyAdvice.add(adviceBean);
            }
        }
        ...
    }

From the above code, you can see that in the ExceptionHandlerExceptionResolver class, this class scans all methods annotated with ExceptionHandler annotations and stores them in the exceptionHandlerAdviceCache.

Principle of exception handling

After looking at the role of Controller Advice and Exception Handler annotations, let's look at the principles of exception handling. doDispatch Method for Entering Dispatcher Servlet

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
                ...
                //Processing controller Method
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                ...
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            //Exception handling center
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        ...
    }

From the doDispatch method, it can be seen that the program first processes the business logic of the controller layer, and then encapsulates the exception thrown by the business logic, and then enters the process Dispatch Result method for processing. So let's go into this method and explore it.

    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 to the program, it is handled
        if (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) {
        //Traversing handler Exception Resolvers to handle exception information
            for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
                exMv = resolver.resolveException(request, response, handler, ex);
                if (exMv != null) {
                    break;
                }
            }
        }
        ...
    }

Where did the handler Exception Resolvers come from?

    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 code

When the Dispatcher Servlet is initialized, a class of Handler Exception Resolver type is found in the container. The Exception Handler Exception Resolver class just now inherits the Handler Exception Resolver interface, so this place places it in the Dispatcher Servlet.The resolveException method of ExceptionHandlerExceptionResolver was called. So we go into this 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 ModelAndView doResolveException(
            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 {

        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        ...
    }

    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        //Getting the parameters of the method
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Arguments: " + Arrays.toString(args));
        }
        //Execution method
        return doInvoke(args);
    }

The execution logic of the whole exception, such as the code above, is simply to find the corresponding exception handling method and execute it. The logical sum in getMethod ArgumentValues SpringBoot Source Parsing-controller Layer Parameter Encapsulation It's the same, but the types of parameters they can handle are different.

View the afterPropertiesSet method of the ExceptionHandlerExceptionResolver class:

    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;
    }

Here is the parameter type that can be accepted in the ExceptionHandler method. Look, it's much less than the type on the controller side. Just pay attention to it when you use it.

Keywords: SpringBoot less

Added by donturo on Sat, 18 May 2019 00:43:19 +0300