Springboot自定义异常全解析
这里看到新服务是封装的自定义异常,准备入手剖析一下,自定义的异常是如何进行抓住我们请求的方法的异常,并进行封装返回到。废话不多说,先看看如何才能实现封装异常,先来一个示例:
在这里,您会看到新服务是一个封装的自定义异常。 我准备分析它。 自定义异常如何捕获我们请求的方法的异常并将其封装回去。 让我们看看如何实现封装异常,让我们举个例子:
@ControllerAdvice
public class TstExceptionHandle{
@ExceptionHandler(Exception.class)
public void myExceptionHandle(HttpServletResponse response){
response.setStatus(403);
System.out.println("做封装处理");
}
}
博主只做了简单的配置示例,主要的是进行源码剖析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 }
好了。所有异常添加完毕了,我们来测试一下异常来的时候,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 }
走完初始化,经过过滤器,拦截器终于到了我们的请求方法,我们的方法还报错了,所以会走到异常中,我们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 }
那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 }
到这里基本如何写自定义异常、以及为什么这么写、底层做了哪些判断都已经讲解完了,自定义异常在工作中还是非常常用的一种手段,因为我们不可能暴露出我们内部的错误信息直接返回给用户,不仅用户体验不好,并且安全性也极其差。