全局異常捕獲器的實(shí)現(xiàn),及其原理

先放實(shí)體類,通用的響應(yīng)體

@Data
public class ResponseDTO<T> {
    private int code;
    private String msg;
    private T data;
    @Override
    public String toString() {
        return "ResponseDTO [code=" + code + ", msg=" + msg + ", data=" + data + "]";
    }
    public ResponseDTO(int code, String msg, T data) {
        super();
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    
}

響應(yīng)枚舉,記錄各個(gè)狀態(tài)下的信息

public class ResponseContants {
    private static final int UNKNOW_ERROR_CODE = 10000;
    private static final int PARAM_ERROR_CODE = 10001;
    private static final int SERVICE_ERROR_CODE = 10002;
    public enum ResponseMsg{
        SUCCESS(0, "success"),
        UNKNOW_ERROR(UNKNOW_ERROR_CODE, "未知異常"),
        PARAM_ERROR(PARAM_ERROR_CODE, "參數(shù)存在錯(cuò)誤"),
        SERVICE_ERROR(SERVICE_ERROR_CODE, "處理業(yè)務(wù)的過程中出現(xiàn)異常");
        private int msgCode;
        private String msg;
        ResponseMsg(int msgCode, String msg) {
            this.msgCode = msgCode;
            this.msg = msg;
        }
        public int getMsgCode() {
            return msgCode;
        }

        public void setMsgCode(int msgCode) {
            this.msgCode = msgCode;
        }

        public String getMsg() {
            return msg;
        }

        public void setMsg(String msg) {
            this.msg = msg;
        }

        @Override
        public String toString() {
            return "ResponseContants [msgCode=" + msgCode + ", msg=" + msg + "]";
        }
        
    }
}

生成響應(yīng)體的工具

public class CommonUtils {
    
    //生成通用響應(yīng)體
    public static <T> ResponseDTO<T> genResponseDTO(T data, ResponseMsg responseMsg) {
        return new ResponseDTO<T>(responseMsg.getMsgCode(), responseMsg.getMsg(), data);
    }
    
    public static<T> ResponseDTO<T> genResponseDTO(int code, String message, T data) {
        return new ResponseDTO<T>(code, message, data);
    }
}

全局異常捕獲器

@ControllerAdvice
@ComponentScan(basePackages = "com.cet.electric.ibsdataservice")
@Slf4j
@Responbody
public class GlobalExceptionHandler {
    @ExceptionHandler(value = Exception.class)
    // 該注解聲明異常處理方法
    public ResponseDTO<ErrorMsg> exceptionHandler(HttpServletRequest request, Exception exception) {
        log.error("GlobalExceptionHandler exceptionHandler exception = {}", exception);
        if (exception instanceof ErrorMsg) {
            return CommonUtils.genResponseDTO(((ErrorMsg) exception).getCode(), exception.getMessage(),
                    (ErrorMsg) exception);
        }
        
        
        return CommonUtils.genResponseDTO(ResponseContants.ResponseMsg.UNKNOW_ERROR.getMsgCode(),
                ResponseContants.ResponseMsg.UNKNOW_ERROR.getMsg(),
                new ErrorMsg(ResponseContants.ResponseMsg.UNKNOW_ERROR.getMsgCode(), exception.getMessage()));
    }
}

隨后我們自己隨便定義一個(gè)接口,拋出異常,發(fā)現(xiàn)最后返回的響應(yīng)是我們自己定義的。

那么spring究竟是怎么實(shí)現(xiàn)的呢。
那么首先來看看一個(gè)接口。HandlerExceptionResolver ,這個(gè)接口是異常處理解析器的接口,里面只提供了一個(gè)方法,就是resolveException方法,并且返回視圖。

public interface HandlerExceptionResolver {

    /**
     * Try to resolve the given exception that got thrown during handler execution,
     * returning a {@link ModelAndView} that represents a specific error page if appropriate.
     * <p>The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty}
     * to indicate that the exception has been resolved successfully but that no view
     * should be rendered, for instance by setting a status code.
     * @param request current HTTP request
     * @param response current HTTP response
     * @param handler the executed handler, or {@code null} if none chosen at the
     * time of the exception (for example, if multipart resolution failed)
     * @param ex the exception that got thrown during handler execution
     * @return a corresponding {@code ModelAndView} to forward to,
     * or {@code null} for default processing in the resolution chain
     */
    @Nullable
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}

那么接下來,看看這個(gè)接口的對應(yīng)實(shí)現(xiàn),有好幾層,其中AbstractHandlerExceptionResolver是核心。實(shí)現(xiàn)了大部分的功能。而AbstractHandlerMethodExceptionResolver則是AbstractHandlerExceptionResolver的子類,專門用于處理方法的異常。

ExceptionHandlerExceptionResolver->AbstractHandlerMethodExceptionResolver->
AbstractHandlerExceptionResolver->HandlerExceptionResolver

通過debug。ExceptionHandlerExceptionResolver的initExceptionHandlerAdviceCache方法,會(huì)將spring中被ControllerAdviceBean注釋所修飾的bean給找出來,并且存入緩存中

    private void initExceptionHandlerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }
        //找出容器中被@ControllerAdviceBean注解修飾的bean,并且進(jìn)行排序
        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);
            }
            ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
            if (resolver.hasExceptionMappings()) {
                this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
            }
            if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                this.responseBodyAdvice.add(adviceBean);
            }
        }

        if (logger.isDebugEnabled()) {
            int handlerSize = this.exceptionHandlerAdviceCache.size();
            int adviceSize = this.responseBodyAdvice.size();
            if (handlerSize == 0 && adviceSize == 0) {
                logger.debug("ControllerAdvice beans: none");
            }
            else {
                logger.debug("ControllerAdvice beans: " +
                        handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
            }
        }
    }

DispatcherServlet是如何處理的,對于拋出的異常,無非也是try catch再去處理,可以看以下代碼

    /**
     * Process the actual dispatching to the handler.
     * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
     * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
     * to find the first that supports the handler class.
     * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
     * themselves to decide which methods are acceptable.
     * @param request current HTTP request
     * @param response current HTTP response
     * @throws Exception in case of any kind of processing failure
     */
    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) {
                    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 (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) {
                //出現(xiàn)異常,則先捕獲
                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);
                }
            }
        }
    }


    /**
     * Handle the result of handler selection and handler invocation, which is
     * either a ModelAndView or an Exception to be resolved to a ModelAndView.
     */
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
            @Nullable Exception exception) throws Exception {

        boolean errorView = false;

        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                //異常必然不是這個(gè)類型的。因此走另外一個(gè)分支
                logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            }
            else {
                //這里則是根據(jù)異常處理器返回視圖的關(guān)鍵
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }
        。。。下面還有一段代碼。但是對分析這個(gè)過程來說意義不大,所以直接忽略
    }


    @Nullable
    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) {
            for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
                //異常的視圖的則是從此處獲取了,那么resolver是什么東西呢?我們通過斷點(diǎn)來看看,如下圖所示
                exMv = resolver.resolveException(request, response, handler, ex);
                if (exMv != null) {
                    break;
                }
            }
        }
        //其實(shí)下面還有一段。不過感覺一個(gè)是看不懂二是意義不大,直接忽略
    }

dispatcher中的handlerExceptionResolvers,有2個(gè)對象分別是DefaultErrorAttributes以及HandlerExceptionResolverComposite,通過debug發(fā)現(xiàn),HandlerExceptionResolverComposite類型才是關(guān)鍵


image.png

然后我們來看看HandlerExceptionResolverComposite的resolveException方法。其實(shí)通過名字都知道這個(gè)類是一個(gè)復(fù)合的異常處理解析器。其中有一個(gè)成員變量用于存儲(chǔ)異常解析器。

public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {
    //用于存儲(chǔ)異常解析器
    @Nullable
    private List<HandlerExceptionResolver> resolvers;


    /**
     * Resolve the exception by iterating over the list of configured exception resolvers.
     * <p>The first one to return a {@link ModelAndView} wins. Otherwise {@code null}
     * is returned.
     */
    @Override
    @Nullable
    //遍歷處理異常,若異常處理器匹配,則直接返回處理后的結(jié)構(gòu),總共有3個(gè)對象,如下圖所示
    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;
    }

}

復(fù)合異常處理器所存儲(chǔ)的異常解析器類型。這里我們只需要關(guān)注ExceptionHandlerExceptionResolver即可。


image.png

下面來看看ExceptionHandlerExceptionResolver的resolveException方法,發(fā)現(xiàn)是在父類實(shí)現(xiàn)的。下面直接貼代碼

public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
    @Override
    @Nullable
    public ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
        //這里是再次判斷請求跟handler是否匹配。
        if (shouldApplyTo(request, handler)) {
            prepareResponse(ex, response);
            //開始處理異常的方法。
            ModelAndView result = doResolveException(request, response, handler, ex);
            if (result != null) {
                // Print warn message when warn logger is not enabled...
                if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
                    logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
                }
                // warnLogger with full stack trace (requires explicit config)
                logException(ex, request);
            }
            return result;
        }
        else {
            return null;
        }
    }

    @Override
    @Nullable
    protected final ModelAndView doResolveException(
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
        //開始處理異常。這個(gè)方法的話,是在子類ExceptionHandlerExceptionResolver實(shí)現(xiàn)的,接下來看代碼。
        return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
    }
}

看看具體實(shí)現(xiàn)

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
        implements ApplicationContextAware, InitializingBean {
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
            HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
         //找出這個(gè)出現(xiàn)異常的方法對應(yīng)的處理異常的方法。直接往里面走。
        ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
        if (exceptionHandlerMethod == null) {
            return null;
        }

        if (this.argumentResolvers != null) {
            exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();

        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
            }
            Throwable cause = exception.getCause();
            if (cause != null) {
                // Expose cause as provided argument as well
                exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
            }
            else {
                // Otherwise, just the given exception as-is
                exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
            }
        }
        catch (Throwable invocationEx) {
            // Any other than the original exception is unintended here,
            // probably an accident (e.g. failed assertion or the like).
            if (invocationEx != exception && logger.isWarnEnabled()) {
                logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
            }
            // Continue with default processing of the original exception...
            return null;
        }


            return mav;
        }
    }
  

    //這個(gè)方法具體就是返回對應(yīng)的異常處理方法的。
    @Nullable
    protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
            @Nullable HandlerMethod handlerMethod, Exception exception) {

        Class<?> handlerType = null;
        //這里其實(shí)就是controller里面的handlerMethod,當(dāng)然不為空
        if (handlerMethod != null) {
            //優(yōu)先從緩存中取
            handlerType = handlerMethod.getBeanType();
            ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
            if (resolver == null) {
                 //一開始肯定是取不到的,所以這個(gè)分支里面的代碼可以暫時(shí)不看
                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());
            }
        }
        //既然緩存中不存在,那么只能通過遍歷異常增強(qiáng)器緩存,來找到對應(yīng)的處理器了。下面我們來看看遍歷的過程
        for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
            ControllerAdviceBean advice = entry.getKey();
            //判斷是否匹配
            if (advice.isApplicableToBeanType(handlerType)) {
                ExceptionHandlerMethodResolver resolver = entry.getValue();
           //這里面其實(shí)是通過處理方法的異常,是不是處理異常的父類,從而找出對應(yīng)的處理方法。同時(shí)將處理的異常類型,放入緩存中,對應(yīng)關(guān)系是異常類型->處理異常的方法。結(jié)果如下圖
                Method method = resolver.resolveMethod(exception);
                if (method != null) {
                    return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
                }
            }
        }

        return null;
    }
}

這個(gè)緩存是在bean的后置處理器執(zhí)行的時(shí)候生成的。一開始我們的異常處理器就已經(jīng)被放到緩存中了


image.png
image.png

找到匹配的方法之后,最后不就是Invoke嗎?ExceptionHandlerExceptionResolver中的doResolveHandlerMethodException方法

/**
     * Find an {@code @ExceptionHandler} method and invoke it to handle the raised exception.
     */
    @Override
    @Nullable
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
            HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
        //找到異常對應(yīng)的處理
        ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
        if (exceptionHandlerMethod == null) {
            return null;
        }

        if (this.argumentResolvers != null) {
            exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();

        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
            }
            Throwable cause = exception.getCause();
            if (cause != null) {
                // 通過反射,處理異常,并且放入視圖容器中
                exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
            }
            else {
                //  通過反射,處理異常,并且放入視圖容器中
                exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
            }
        }
        catch (Throwable invocationEx) {
            // Any other than the original exception is unintended here,
            // probably an accident (e.g. failed assertion or the like).
            if (invocationEx != exception && logger.isWarnEnabled()) {
                logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
            }
            // Continue with default processing of the original exception...
            return null;
        }

        if (mavContainer.isRequestHandled()) {
            return new ModelAndView();
        }
        else {
           //將視圖容器中的模型,封裝到mav中取,結(jié)果可以看下圖
            ModelMap model = mavContainer.getModel();
            HttpStatus status = mavContainer.getStatus();
            ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
            mav.setViewName(mavContainer.getViewName());
            if (!mavContainer.isViewReference()) {
                mav.setView((View) mavContainer.getView());
            }
            if (model instanceof RedirectAttributes) {
                Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
            }
            return mav;
        }
    }
image.png

至此,異常的處理便處理完畢了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容