源碼剖析Springboot自定義異常

  博主看到新服務是封裝的自定義異常,準備入手剖析一下,自定義的異常是如何進行抓住我們請求的方法的異常,並進行封裝返回到。廢話不多說,先看看如何才能實現封裝異常,先來一個示例:

 1 @ControllerAdvice
 2 public class TstExceptionHandle{
 3 
 4     @ExceptionHandler(Exception.class)
 5     public void myExceptionHandle(HttpServletResponse response){
 6         response.setStatus(403);
 7         System.out.println("做封裝處理");
 8     }
 9 
10 }

  博主只做了簡單的配置示例,主要的是進行源碼剖析Springboot是如何獲取自定義異常並進行返回的。來吧!

  第一步:肯定是在Springboot啟動的過程中進行的異常處理初始化,於是就找到了handlerExceptionResolver類,在創建該類的時候,會進行添加我們自定義異常。

 1     public HandlerExceptionResolver handlerExceptionResolver(
 2             @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
 3         List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
 4         //不用管這個方法,這個方法主要進行的是調用實現了WebMvcConfigurer接口bean的configureHandlerExceptionResolvers方法,系統的都是空方法
 5         configureHandlerExceptionResolvers(exceptionResolvers);
 6         if (exceptionResolvers.isEmpty()) {
 7             //我們的在這裡才添加,我們看看這個方法
 8             addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
 9         }
10         extendHandlerExceptionResolvers(exceptionResolvers);
11         HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
12         composite.setOrder(0);
13         composite.setExceptionResolvers(exceptionResolvers);
14         return composite;
15     }

org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport

 1     protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
 2             ContentNegotiationManager mvcContentNegotiationManager) {
 3 
 4         ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
 5         exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
 6         exceptionHandlerResolver.setMessageConverters(getMessageConverters());
 7         exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
 8         exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
 9         if (jackson2Present) {
10             exceptionHandlerResolver.setResponseBodyAdvice(
11                     Collections.singletonList(new JsonViewResponseBodyAdvice()));
12         }
13         if (this.applicationContext != null) {
14             exceptionHandlerResolver.setApplicationContext(this.applicationContext);
15         }
16         //上面的 都是設置的屬性,跟我們沒啥大關係,主要在這裡進行的添加自定義異常處理
17         exceptionHandlerResolver.afterPropertiesSet();
18         exceptionResolvers.add(exceptionHandlerResolver);
19 
20         ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
21         responseStatusResolver.setMessageSource(this.applicationContext);
22         exceptionResolvers.add(responseStatusResolver);
23 
24         exceptionResolvers.add(new DefaultHandlerExceptionResolver());
25     }

org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport

    最主要的初始化過程在這裡,從這些代碼中就可以看到為什麼我們自定義異常需要進行使用@ControllerAdvice,並且方法使用@ExceptionHandler(Exception.class)註解了

 1     @Override
 2     public void afterPropertiesSet() {
 3         // Do this first, it may add ResponseBodyAdvice beans
 4         //走這裡初始化,添加
 5         initExceptionHandlerAdviceCache();
 6 
 7         if (this.argumentResolvers == null) {
 8             List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
 9             this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
10         }
11         if (this.returnValueHandlers == null) {
12             List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
13             this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
14         }
15     }
16 
17 
18     org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
19     private void initExceptionHandlerAdviceCache() {
20         if (getApplicationContext() == null) {
21             return;
22         }
23         //看到這裡基本就知道啥意思了,找出帶有@ControllerAdvice的註解bean
24         List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
25         for (ControllerAdviceBean adviceBean : adviceBeans) {
26             Class<?> beanType = adviceBean.getBeanType();
27             if (beanType == null) {
28                 throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
29             }
30             //找出當前bean的異常處理方法
31             ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
32             if (resolver.hasExceptionMappings()) {
33                 this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
34             }
35             if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
36                 this.responseBodyAdvice.add(adviceBean);
37             }
38         }
39 
40         if (logger.isDebugEnabled()) {
41             int handlerSize = this.exceptionHandlerAdviceCache.size();
42             int adviceSize = this.responseBodyAdvice.size();
43             if (handlerSize == 0 && adviceSize == 0) {
44                 logger.debug("ControllerAdvice beans: none");
45             }
46             else {
47                 logger.debug("ControllerAdvice beans: " +
48                         handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
49             }
50         }
51     }

org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver

  找到類後,是如何找到方法的呢?主要看如何創建ExceptionHandlerMethodResolver的過程。

 1 public ExceptionHandlerMethodResolver(Class<?> handlerType) {
 2     //EXCEPTION_HANDLER_METHODS的定義:
 3     //public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
 4     //            AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
 5     //所以他會尋找帶有ExceptionHandler註解的方法
 6         for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
 7             //尋找方法註解上配置的捕獲的異常類,並添加,如果有兩個方法都對一個異常進行自定義處理了,怎麼辦呢。
 8             for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
 9                 //他會出異常的。不過前提是同一個類里,不同類對同一個異常進行自定義的話,誰在前面就有誰來處理
10                 addExceptionMapping(exceptionType, method);
11             }
12         }
13     }

org/springframework/web/method/annotation/ExceptionHandlerMethodResolver

  添加自定義異常的時候拋異常是在這裡

1     private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
2         Method oldMethod = this.mappedMethods.put(exceptionType, method);
3         //在這裡,已經顯示出來了,博主就不試了
4         if (oldMethod != null && !oldMethod.equals(method)) {
5             throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
6                     exceptionType + "]: {" + oldMethod + ", " + method + "}");
7         }
8     }

org/springframework/web/method/annotation/ExceptionHandlerMethodResolver

  好了。所有異常添加完畢了,我們來測試一下異常來的時候,Springboot是如何選擇自定義異常並返回的,我們上面所有的操作都是在創建HandlerExceptionResolver時進行的,為什麼要添加到HandlerExceptionResolver這裡呢?看一下代碼:

 1 //第一次請求進來時,會先查找是否有自定義異常,如果有的話添加,沒有記錄日誌就完了
 2     private void initHandlerExceptionResolvers(ApplicationContext context) {
 3         this.handlerExceptionResolvers = null;
 4 
 5         if (this.detectAllHandlerExceptionResolvers) {
 6             // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
 7                         //這裡會在beanfactroy中查找到HandlerExceptionResolver類,剛才初始化的時候,我們所有的自定義異常都在裏面 
 8             Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
 9                     .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
10             if (!matchingBeans.isEmpty()) {
11                 this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
12                 // We keep HandlerExceptionResolvers in sorted order.
13                 AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
14             }
15         }
16         else {
17             try {
18                 HandlerExceptionResolver her =
19                         context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
20                 this.handlerExceptionResolvers = Collections.singletonList(her);
21             }
22             catch (NoSuchBeanDefinitionException ex) {
23                 // Ignore, no HandlerExceptionResolver is fine too.
24             }
25         }
26 
27         // Ensure we have at least some HandlerExceptionResolvers, by registering
28         // default HandlerExceptionResolvers if no other resolvers are found.
29         if (this.handlerExceptionResolvers == null) {
30             this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
31             if (logger.isTraceEnabled()) {
32                 logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
33                         "': using default strategies from DispatcherServlet.properties");
34             }
35         }
36     }            

org/springframework/web/servlet/DispatcherServlet

  走完初始化,經過過濾器,攔截器終於到了我們的請求方法,我們的方法還報錯了,所以會走到異常中,我們DispatcherServlet會進行抓住異常,然後回調用我們的processDispatchResult方法,大家可以自己看一下org/springframework/web/servlet/DispatcherServlet.java的源碼,然後我們來分析一下這個方法都幹啥了吧

 1     private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
 2             @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
 3             @Nullable Exception exception) throws Exception {
 4 
 5         boolean errorView = false;
 6 
 7         if (exception != null) {
 8             if (exception instanceof ModelAndViewDefiningException) {
 9                 logger.debug("ModelAndViewDefiningException encountered", exception);
10                 mv = ((ModelAndViewDefiningException) exception).getModelAndView();
11             }
12             else {
13                 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
14                 //如果請求方法有異常,則進行處理,並返回ModelAndView
15                 mv = processHandlerException(request, response, handler, exception);
16                 errorView = (mv != null);
17             }
18         }
19     .........
20     }

org/springframework/web/servlet/DispatcherServlet.java

  那Springboot是如何選擇哪一個是符合條件的自定義異常處理呢?如果我們定義了兩個處理類,都對同一個異常進行捕獲並返回不一樣的信息咋辦呢?看源碼吧

 1 //這裡會選擇符合條件的自定義異常
 2     protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
 3             @Nullable HandlerMethod handlerMethod, Exception exception) {
 4 
 5         Class<?> handlerType = null;
 6 
 7         if (handlerMethod != null) {
 8             // Local exception handler methods on the controller class itself.
 9             // To be invoked through the proxy, even in case of an interface-based proxy.
10             handlerType = handlerMethod.getBeanType();
11             ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
12             if (resolver == null) {
13                 resolver = new ExceptionHandlerMethodResolver(handlerType);
14                 this.exceptionHandlerCache.put(handlerType, resolver);
15             }
16             Method method = resolver.resolveMethod(exception);
17             if (method != null) {
18                 return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
19             }
20             // For advice applicability check below (involving base packages, assignable types
21             // and annotation presence), use target class instead of interface-based proxy.
22             if (Proxy.isProxyClass(handlerType)) {
23                 handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
24             }
25         }
26         //exceptionHandlerAdviceCache這個map是我們添加 的自定義異常
27         for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
28             ControllerAdviceBean advice = entry.getKey();
29             //這個判斷條件是查看是否有符合條件的自定義異常,如果有兩個的話,
30             if (advice.isApplicableToBeanType(handlerType)) {
31                 ExceptionHandlerMethodResolver resolver = entry.getValue();
32                 Method method = resolver.resolveMethod(exception);
33                 if (method != null) {
34                     return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
35                 }
36             }
37         }
38 
39         return null;
40     }

org/springframework/web/servlet/DispatcherServlet.java

  邏輯基本是上面的,但是真正處理是否符合是在這裡的一個方法中:

 1 public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
 2         return this.beanTypePredicate.test(beanType);
 3     }
 4     public boolean test(Class<?> controllerType) {
 5          ///默認不配的其他屬性的時候是返回true的,就是對所有包下的異常都適用
 6         if (!hasSelectors()) {
 7             return true;
 8         }
 9         else if (controllerType != null) {
10             //我們的@ControllerAdvice註解是有basePackages屬性的,只有匹配成功才會返回,否則就算自定義異常想要捕獲,不在捕獲包範圍下不管該異常
11             for (String basePackage : this.basePackages) {
12                 if (controllerType.getName().startsWith(basePackage)) {
13                     return true;
14                 }
15             }
16             for (Class<?> clazz : this.assignableTypes) {
17                 if (ClassUtils.isAssignable(clazz, controllerType)) {
18                     return true;
19                 }
20             }
21             for (Class<? extends Annotation> annotationClass : this.annotations) {
22                 if (AnnotationUtils.findAnnotation(controllerType, annotationClass) != null) {
23                     return true;
24                 }
25             }
26         }
27         return false;
28     }

org/springframework/web/method/ControllerAdviceBean

  到這裡基本如何寫自定義異常、以及為什麼這麼寫、底層做了哪些判斷都已經講解完了,自定義異常在工作中還是非常常用的一種手段,因為我們不可能暴露出我們內部的錯誤信息直接返回給用戶,不僅用戶體驗不好,並且安全性也極其差。

  原創不易,轉載請說明出處://www.cnblogs.com/guoxiaoyu/p/13489565.html