博主看到新服务是封装的自定义异常,准备入手剖析一下,自定义的异常是如何进行抓住我们请求的方法的异常,并进行封装返回到。废话不多说,先看看如何才能实现封装异常,先来一个示例:

 @ControllerAdvice
public class TstExceptionHandle{ @ExceptionHandler(Exception.class)
public void myExceptionHandle(HttpServletResponse response){
response.setStatus(403);
System.out.println("做封装处理");
} }

  博主只做了简单的配置示例,主要的是进行源码剖析Springboot是如何获取自定义异常并进行返回的。来吧!

  第一步:肯定是在Springboot启动的过程中进行的异常处理初始化,于是就找到了handlerExceptionResolver类,在创建该类的时候,会进行添加我们自定义异常。

     public HandlerExceptionResolver handlerExceptionResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
//不用管这个方法,这个方法主要进行的是调用实现了WebMvcConfigurer接口bean的configureHandlerExceptionResolvers方法,系统的都是空方法
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
//我们的在这里才添加,我们看看这个方法
addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}

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

     protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
ContentNegotiationManager mvcContentNegotiationManager) { ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
exceptionHandlerResolver.setMessageConverters(getMessageConverters());
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
exceptionHandlerResolver.setResponseBodyAdvice(
Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
if (this.applicationContext != null) {
exceptionHandlerResolver.setApplicationContext(this.applicationContext);
}
//上面的 都是设置的属性,跟我们没啥大关系,主要在这里进行的添加自定义异常处理
exceptionHandlerResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerResolver); ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
responseStatusResolver.setMessageSource(this.applicationContext);
exceptionResolvers.add(responseStatusResolver); exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}

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

   最主要的初始化过程在这里,从这些代码中就可以看到为什么我们自定义异常需要进行使用@ControllerAdvice,并且方法使用@ExceptionHandler(Exception.class)注解了

     @Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
//走这里初始化,添加
initExceptionHandlerAdviceCache(); if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
} org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
//看到这里基本就知道啥意思了,找出带有@ControllerAdvice的注解bean
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
//找出当前bean的异常处理方法
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
} if (logger.isDebugEnabled()) {
int handlerSize = this.exceptionHandlerAdviceCache.size();
int adviceSize = this.responseBodyAdvice.size();
if (handlerSize == 0 && adviceSize == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " +
handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
}
}
}

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

  找到类后,是如何找到方法的呢?主要看如何创建ExceptionHandlerMethodResolver的过程。

 public ExceptionHandlerMethodResolver(Class<?> handlerType) {
//EXCEPTION_HANDLER_METHODS的定义:
//public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
// AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
//所以他会寻找带有ExceptionHandler注解的方法
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
//寻找方法注解上配置的捕获的异常类,并添加,如果有两个方法都对一个异常进行自定义处理了,怎么办呢。
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
//他会出异常的。不过前提是同一个类里,不同类对同一个异常进行自定义的话,谁在前面就有谁来处理
addExceptionMapping(exceptionType, method);
}
}
}

org/springframework/web/method/annotation/ExceptionHandlerMethodResolver

  添加自定义异常的时候抛异常是在这里

     private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
Method oldMethod = this.mappedMethods.put(exceptionType, method);
//在这里,已经显示出来了,博主就不试了
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
exceptionType + "]: {" + oldMethod + ", " + method + "}");
}
}

org/springframework/web/method/annotation/ExceptionHandlerMethodResolver

  好了。所有异常添加完毕了,我们来测试一下异常来的时候,Springboot是如何选择自定义异常并返回的,我们上面所有的操作都是在创建HandlerExceptionResolver时进行的,为什么要添加到HandlerExceptionResolver这里呢?看一下代码:

 //第一次请求进来时,会先查找是否有自定义异常,如果有的话添加,没有记录日志就完了
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null; if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
//这里会在beanfactroy中查找到HandlerExceptionResolver类,刚才初始化的时候,我们所有的自定义异常都在里面
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
else {
try {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, no HandlerExceptionResolver is fine too.
}
} // Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}

org/springframework/web/servlet/DispatcherServlet

  走完初始化,经过过滤器,拦截器终于到了我们的请求方法,我们的方法还报错了,所以会走到异常中,我们DispatcherServlet会进行抓住异常,然后回调用我们的processDispatchResult方法,大家可以自己看一下org/springframework/web/servlet/DispatcherServlet.java的源码,然后我们来分析一下这个方法都干啥了吧

     private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception { boolean errorView = false; if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
//如果请求方法有异常,则进行处理,并返回ModelAndView
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
.........
}

org/springframework/web/servlet/DispatcherServlet.java

  那Springboot是如何选择哪一个是符合条件的自定义异常处理呢?如果我们定义了两个处理类,都对同一个异常进行捕获并返回不一样的信息咋办呢?看源码吧

 //这里会选择符合条件的自定义异常
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) { Class<?> handlerType = null; if (handlerMethod != null) {
// Local exception handler methods on the controller class itself.
// To be invoked through the proxy, even in case of an interface-based proxy.
handlerType = handlerMethod.getBeanType();
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
// For advice applicability check below (involving base packages, assignable types
// and annotation presence), use target class instead of interface-based proxy.
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
//exceptionHandlerAdviceCache这个map是我们添加 的自定义异常
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
//这个判断条件是查看是否有符合条件的自定义异常,如果有两个的话,
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
}
}
} return null;
}

org/springframework/web/servlet/DispatcherServlet.java

  逻辑基本是上面的,但是真正处理是否符合是在这里的一个方法中:

 public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
return this.beanTypePredicate.test(beanType);
}
public boolean test(Class<?> controllerType) {
///默认不配的其他属性的时候是返回true的,就是对所有包下的异常都适用
if (!hasSelectors()) {
return true;
}
else if (controllerType != null) {
//我们的@ControllerAdvice注解是有basePackages属性的,只有匹配成功才会返回,否则就算自定义异常想要捕获,不在捕获包范围下不管该异常
for (String basePackage : this.basePackages) {
if (controllerType.getName().startsWith(basePackage)) {
return true;
}
}
for (Class<?> clazz : this.assignableTypes) {
if (ClassUtils.isAssignable(clazz, controllerType)) {
return true;
}
}
for (Class<? extends Annotation> annotationClass : this.annotations) {
if (AnnotationUtils.findAnnotation(controllerType, annotationClass) != null) {
return true;
}
}
}
return false;
}

org/springframework/web/method/ControllerAdviceBean

  到这里基本如何写自定义异常、以及为什么这么写、底层做了哪些判断都已经讲解完了,自定义异常在工作中还是非常常用的一种手段,因为我们不可能暴露出我们内部的错误信息直接返回给用户,不仅用户体验不好,并且安全性也极其差。

  原创不易,转载请说明出处:https://www.cnblogs.com/guoxiaoyu/p/13489565.html


源码剖析Springboot自定义异常的更多相关文章

  1. 学习SpringBoot整合SSM三大框架源码之SpringBoot

    Spring Boot源码剖析 一.Spring Boot 项目的启动入口流程分析 Spring Boot项目的启动入口main线程上有一个@SpringBootApplication( @Confi ...

  2. 源码剖析@ApiImplicitParam对@RequestParam的required属性的侵入性

    问题起源 使用SpringCloud构建项目时,使用Swagger生成相应的接口文档是推荐的选项,Swagger能够提供页面访问,直接在网页上调试后端系统的接口, 非常方便.最近却遇到了一个有点困惑的 ...

  3. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  4. Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

    声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...

  5. Apache Spark源码剖析

    Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著   ISBN 978-7-121-25420- ...

  6. 基于mybatis-generator-core 1.3.5项目的修订版以及源码剖析

    项目简单说明 mybatis-generator,是根据数据库表.字段反向生成实体类等代码文件.我在国庆时候,没事剖析了mybatis-generator-core源码,写了相当详细的中文注释,可以去 ...

  7. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  8. SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现

    SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...

  9. 自己实现多线程的socket,socketserver源码剖析

    1,IO多路复用 三种多路复用的机制:select.poll.epoll 用的多的两个:select和epoll 简单的说就是:1,select和poll所有平台都支持,epoll只有linux支持2 ...

随机推荐

  1. 紧急处理RAC环境有一个监听down 的情况

    初步处理 1. grid 登录查看是监听是否down掉 srvctl status listener -n node1 或者oracle登录 lsnrctl status 查看 如果掉了 grid 用 ...

  2. 在 CentOS 7(Linux)上部署ASP.NET Core 2.2 Web应用程序(Tengine、Asp.Net Core MVC、Centos 7、MySql)

    一.前言 1.简单记录一下Linux CentOS 7中安装与配置Tengine的详细步骤. 2.简单比较一下Tengine 和Nginx 3.搭建Asp.net Core和部署 Web程序 4.总结 ...

  3. 动态DP,ddp

    动态DP?动态动态规划? 个人理解:动态DP,就是普通DP加修改操作,然后就变成了个毒瘤题. 直接就着例题写吧. 例题 P4719 [模板]"动态 DP"&动态树分治 求树 ...

  4. HTTP request smuggling CL.TE

    CL.TE 简介 前端通过Content-Length处理请求,通过反向代理或者负载均衡将请求转发到后端,后端Transfer-Encoding优先级较高,以TE处理请求造成安全问题. 检测 发送如下 ...

  5. Python网络数据采集PDF高清完整版免费下载|百度云盘

    百度云盘:Python网络数据采集PDF高清完整版免费下载 提取码:1vc5   内容简介 本书采用简洁强大的Python语言,介绍了网络数据采集,并为采集新式网络中的各种数据类型提供了全面的指导.第 ...

  6. Java Web(5)-Servlet详解(下)

    一.HttpServletRequest 类 1. HttpServletRequest 类作用? 每次只要有请求进入 Tomcat 服务器,Tomcat 服务器就会把请求过来的 HTTP 协议信息解 ...

  7. Day13_Thymeleaf简介

    学于黑马和传智播客联合做的教学项目 感谢 黑马官网 传智播客官网 微信搜索"艺术行者",关注并回复关键词"乐优商城"获取视频和教程资料! b站在线视频 1.Th ...

  8. PHP array_flip() 函数

    ------------恢复内容开始------------ 实例 反转数组中的键名和对应关联的键值: <?php$a1=array("a"=>"red&qu ...

  9. PHP array_combine() 函数

    ------------恢复内容开始------------ 实例 通过合并两个数组来创建一个新数组,其中的一个数组元素为键名,另一个数组元素为键值: <?php$fname=array(&qu ...

  10. PHP exp() 函数

    实例 返回 'e' 的不同次方: <?phpecho(exp(0) . "<br>");echo(exp(1) . "<br>") ...