重温@RestControllerAdvice异常处理

从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 attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap();
        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 exceptionResolvers) {
    ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
    exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
    exceptionHandlerResolver.setMessageConverters(getMessageConverters());
    exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
    exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
    if (jackson2Present) {
        exceptionHandlerResolver.setResponseBodyAdvice(
                Collections.>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 resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        List 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 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 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.
 * 

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. *

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"})}. *

{@link #value} is an alias for this attribute, simply allowing for * more concise use of the annotation. *

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. *

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. *

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. *

Controllers that are annotated with this/one of those annotation(s) * will be assisted by the {@code @ControllerAdvice} annotated class. *

Consider creating a special annotation or use a predefined one, * like {@link RestController @RestController}. * @since 4.0 */ Class[] annotations() default {}; }

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


28

RequestResponseBodyMethodProcessor#handleReturnValue


29

通过消息解析器,把流写出去
protected  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 requestedMediaTypes = getAcceptableMediaTypes(request);
    List producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

    if (outputValue != null && producibleMediaTypes.isEmpty()) {
        throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
    }

    Set compatibleMediaTypes = new LinkedHashSet();
    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 mediaTypes = new ArrayList(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>) 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>) 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异常处理)