源碼剖析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