博主看到新服務是封裝的自定義例外,準備入手剖析一下,自定義的例外是如何進行抓住我們請求的方法的例外,并進行封裝回傳到,廢話不多說,先看看如何才能實作封裝例外,先來一個示例:
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
到這里基本如何寫自定義例外、以及為什么這么寫、底層做了哪些判斷都已經講解完了,自定義例外在作業中還是非常常用的一種手段,因為我們不可能暴露出我們內部的錯誤資訊直接回傳給用戶,不僅用戶體驗不好,并且安全性也極其差,
原創不易,轉載請說明出處:https://www.cnblogs.com/guoxiaoyu/p/13489565.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/80790.html
標籤:Java
