博主看到新服务是封装的自定义异常,准备入手剖析一下,自定义的异常是如何进行抓住我们请求的方法的异常,并进行封装返回到。废话不多说,先看看如何才能实现封装异常,先来一个示例:
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 }
最主要的初始化过程在这里,从这些代码中就可以看到为什么我们自定义异常需要进行使用@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 }
好了。所有异常添加完毕了,我们来测试一下异常来的时候,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 }
逻辑基本是上面的,但是真正处理是否符合是在这里的一个方法中:
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