美文网首页中间件
重温@RestControllerAdvice异常处理

重温@RestControllerAdvice异常处理

作者: liaomengge | 来源:发表于2019-10-27 17:46 被阅读0次

    从DispatcherServlet入口,如下:

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                    " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }
    
        // Keep a snapshot of the request attributes in case of an include,
        // to be able to restore the original attributes after the include.
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<String, Object>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
    
        // Make framework objects available to handlers and view objects.
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
    
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    
        try {
            //直接看这个方法
            doDispatch(request, response);
        }
        finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }
    
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
    
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
    
            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
    
                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
    
                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
    
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
    
                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
    
                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            //处理结果(里面含异常处理)
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }
    

    进入异常处理的地方


    21
    22

    进入到resolveException方法,默认解析器有3个,ExceptionHandlerExceptionResolver,ResponseStatusExceptionResolver,DefaultHandlerExceptionResolver;具体实例化的地方在WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers方法

    protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
        exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
        exceptionHandlerResolver.setMessageConverters(getMessageConverters());
        exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
        exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
        if (jackson2Present) {
            exceptionHandlerResolver.setResponseBodyAdvice(
                    Collections.<ResponseBodyAdvice<?>>singletonList(new JsonViewResponseBodyAdvice()));
        }
        exceptionHandlerResolver.setApplicationContext(this.applicationContext);
        exceptionHandlerResolver.afterPropertiesSet();
        exceptionResolvers.add(exceptionHandlerResolver);
    
        ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
        responseStatusResolver.setMessageSource(this.applicationContext);
        exceptionResolvers.add(responseStatusResolver);
    
        exceptionResolvers.add(new DefaultHandlerExceptionResolver());
    }
    
    23

    先看ExceptionHandlerExceptionResolver的处理如下:
    ExceptionHandlerExceptionResolver初始化的时候,

    @Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBodyAdvice beans
        initExceptionHandlerAdviceCache();
    
        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }
    
    26
    find所有ControllerAdvice Annotion注解的bean(找@ControllerAdvice注解或者其作为meta annotation,可以看下AnnotationUtils#findAnnotation方法),并排序;如果该类实现了ResponseBodyAdvice(可自定义扩展统一response的返回)接口
    24
    25
    public ExceptionHandlerMethodResolver(Class<?> handlerType) {
        for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
            for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
                addExceptionMapping(exceptionType, method);
            }
        }
    }
    
    public static final MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() {
        @Override
        public boolean matches(Method method) {
            return (AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null);
        }
    };
    

    找到标有ExceptionHandler注解的方法,并缓存再exceptionHandlerCache上,然后判断该ControllerAdviceBean是否能应用到该controller上

    protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
        Class<?> handlerType = null;
    
        if (handlerMethod != null) {
            // Local exception handler methods on the controller class itself.
            // To be invoked through the proxy, even in case of an interface-based proxy.
            handlerType = handlerMethod.getBeanType();
            ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
            if (resolver == null) {
                resolver = new ExceptionHandlerMethodResolver(handlerType);
                this.exceptionHandlerCache.put(handlerType, resolver);
            }
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
            }
            // For advice applicability check below (involving base packages, assignable types
            // and annotation presence), use target class instead of interface-based proxy.
            if (Proxy.isProxyClass(handlerType)) {
                handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
            }
        }
    
        for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
            ControllerAdviceBean advice = entry.getKey();
            //判断是否匹配
            if (advice.isApplicableToBeanType(handlerType)) {
                ExceptionHandlerMethodResolver resolver = entry.getValue();
                Method method = resolver.resolveMethod(exception);
                if (method != null) {
                    return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
                }
            }
        }
    
        return null;
    }
    
    27

    可以看一下ControllerAdvice注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface ControllerAdvice {
    
    /**
     * Alias for the {@link #basePackages} attribute.
     * <p>Allows for more concise annotation declarations e.g.:
     * {@code @ControllerAdvice("org.my.pkg")} is equivalent to
     * {@code @ControllerAdvice(basePackages="org.my.pkg")}.
     * @since 4.0
     * @see #basePackages()
     */
    @AliasFor("basePackages")
    String[] value() default {};
    
    /**
     * Array of base packages.
     * <p>Controllers that belong to those base packages or sub-packages thereof
     * will be included, e.g.: {@code @ControllerAdvice(basePackages="org.my.pkg")}
     * or {@code @ControllerAdvice(basePackages={"org.my.pkg", "org.my.other.pkg"})}.
     * <p>{@link #value} is an alias for this attribute, simply allowing for
     * more concise use of the annotation.
     * <p>Also consider using {@link #basePackageClasses()} as a type-safe
     * alternative to String-based package names.
     * @since 4.0
     */
    @AliasFor("value")
    String[] basePackages() default {};
    
    /**
     * Type-safe alternative to {@link #value()} for specifying the packages
     * to select Controllers to be assisted by the {@code @ControllerAdvice}
     * annotated class.
     * <p>Consider creating a special no-op marker class or interface in each package
     * that serves no purpose other than being referenced by this attribute.
     * @since 4.0
     */
    Class<?>[] basePackageClasses() default {};
    
    /**
     * Array of classes.
     * <p>Controllers that are assignable to at least one of the given types
     * will be assisted by the {@code @ControllerAdvice} annotated class.
     * @since 4.0
     */
    Class<?>[] assignableTypes() default {};
    
    /**
     * Array of annotations.
     * <p>Controllers that are annotated with this/one of those annotation(s)
     * will be assisted by the {@code @ControllerAdvice} annotated class.
     * <p>Consider creating a special annotation or use a predefined one,
     * like {@link RestController @RestController}.
     * @since 4.0
     */
    Class<? extends Annotation>[] annotations() default {};
    
    }
    

    最后,定位到ServletInvocableHandlerMethod#invokeAndHandle异常方法处理


    28

    RequestResponseBodyMethodProcessor#handleReturnValue


    29
    通过消息解析器,把流写出去
    protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
        Object outputValue;
        Class<?> valueType;
        Type declaredType;
    
        if (value instanceof CharSequence) {
            outputValue = value.toString();
            valueType = String.class;
            declaredType = String.class;
        }
        else {
            outputValue = value;
            valueType = getReturnValueType(outputValue, returnType);
            declaredType = getGenericType(returnType);
        }
    
        HttpServletRequest request = inputMessage.getServletRequest();
        List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
        List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
    
        if (outputValue != null && producibleMediaTypes.isEmpty()) {
            throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
        }
    
        Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
        for (MediaType requestedType : requestedMediaTypes) {
            for (MediaType producibleType : producibleMediaTypes) {
                if (requestedType.isCompatibleWith(producibleType)) {
                    compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
                }
            }
        }
        if (compatibleMediaTypes.isEmpty()) {
            if (outputValue != null) {
                throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
            }
            return;
        }
    
        List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
        MediaType.sortBySpecificityAndQuality(mediaTypes);
    
        MediaType selectedMediaType = null;
        for (MediaType mediaType : mediaTypes) {
            if (mediaType.isConcrete()) {
                selectedMediaType = mediaType;
                break;
            }
            else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
                selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                break;
            }
        }
    
        if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                if (messageConverter instanceof GenericHttpMessageConverter) {
                    if (((GenericHttpMessageConverter) messageConverter).canWrite(
                            declaredType, valueType, selectedMediaType)) {
                        outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                                (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                                inputMessage, outputMessage);
                        if (outputValue != null) {
                            addContentDispositionHeader(inputMessage, outputMessage);
                            ((GenericHttpMessageConverter) messageConverter).write(
                                    outputValue, declaredType, selectedMediaType, outputMessage);
                            if (logger.isDebugEnabled()) {
                                logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
                                        "\" using [" + messageConverter + "]");
                            }
                        }
                        return;
                    }
                }
                else if (messageConverter.canWrite(valueType, selectedMediaType)) {
                    outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                            (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                            inputMessage, outputMessage);
                    if (outputValue != null) {
                        addContentDispositionHeader(inputMessage, outputMessage);
                        ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
                                    "\" using [" + messageConverter + "]");
                        }
                    }
                    return;
                }
            }
        }
    
        if (outputValue != null) {
            throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
        }
    }
    
    30

    在body writer之前,会先调用ResponseBodyAdvice match做一层处理,也就是说,


    31
    @RestControllerAdvice
    public class GlobalExceptionHandler extends ResponseEntityExceptionHandler implements ResponseBodyAdvice {}
    

    可以统一返回结果集~~~
    同理,ResponseStatusExceptionResolver的处理也比较类似_

    本文由博客一文多发平台 OpenWrite 发布!

    相关文章

      网友评论

        本文标题:重温@RestControllerAdvice异常处理

        本文链接:https://www.haomeiwen.com/subject/kfkmvctx.html