SpringBoot 异常处理二三事

2020 之前打算将 Spring 和 SpringBoot 的官方文档看一遍,顺便看点感兴趣的源码。

昨天莫名想研究在 SpringBoot 中 Exception 怎么处理的。
复制代码

代码 GitHub

涉及内容。

SpringBoot 启动生成的异常怎么处理的。

实现这个接口 ApplicationListener<ApplicationReadyEvent> ,

将 Bean 加入 ioc ,当程序启动发生异常你会感知到。比如启动失败发送邮件通知。

实现这个接口 ApplicationListener<ApplicationReadyEvent> ,
将 Bean 加入 ioc 容器中,当程序启动成功你会感知到。


基于实现 SpringBootExceptionReporter,对启动异常分析,
在我们自定义 starter 很有用
复制代码

SpringBoot 运行期间发生的异常。

@ExceptionHandler 和 @RestControllerAdvice 结合。处理标记的异常。

Tomcat 会根据 Request 判断是否有异常需要处理。
然后转发 DispatcherServlet url /error,这个可路径在 SpringBoot 可修改。

BasicErrorController 既是处理 /error 。

会讲一些源码,记录一下处理流程。
复制代码

判断对象、对象父类,Class 的泛型类型

看源码的时候学到的,很强大的功能。ResolvableType
复制代码

SpringBoot 启动的异常处理

SpringApplication.run(String... args) 中启动系统上下文,当发生异常的时候,
SpringApplication.handleRunFailure 处理启动异常逻辑。
1、会发送失败事件,可通过监听事件,处理逻辑。
2、SpringApplication.reportFailure 分析日常信息。
实现这个接口 SpringBootExceptionReporter 就可以注册异常了。
不过 SpringBoot FailureAnalyzers 给了默认实现。
我们可以基于 FailureAnalyzers 的逻辑进行扩展。
复制代码

通过事件机制进行异常信息控制

通过 ApplicationEvent 事件,及发布事件,可以很好的解耦。

我们也可以通过自定义业务事件进行结构业务。

监听启动失败的事件

@Component
public class StartFailedApplicationListener implements ApplicationListener<ApplicationFailedEvent> {

    @Autowired
    private EmailService emailService;

    @Override
    public void onApplicationEvent(ApplicationFailedEvent applicationFailedEvent) {
        // 发送邮件告诉启动失败了
        Throwable exception = applicationFailedEvent.getException();

//        31  红色   32  绿色 33  黄色
        StringJoiner stringJoiner = new StringJoiner("", "/031[32;4m", "/031[0m");
        String join = String.join("", "服务器 ip: 192.168.11.11 启动失败, 异常原因为:", exception.getMessage());
        String s = stringJoiner.add(join).toString();
        emailService.sendEmail(s);
    }
}
复制代码

实现接口监听启动成功的事件

@Component
public class StartSuccessApplicationListener implements ApplicationListener<ApplicationReadyEvent> {

    @Autowired
    private EmailService emailService;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        // 发送邮件告诉启动成功了
        //        31  红色   32  绿色 33  黄色
        StringJoiner stringJoiner = new StringJoiner("", "/033[32;4m", "/033[0m");
        String join = String.join("", "服务器 ip: 192.168.11.11 启动成功!");
        String s = stringJoiner.add(join).toString();
        emailService.sendEmail(s);
    }
}
复制代码

使用注解监听事件

@Component
public class AnnotationListener {

    @EventListener(value={ApplicationReadyEvent.class})
    public void annotationListener(){
        System.out.println(AnnotationListener.class.getName()+"启动成功了");
    }

}
复制代码

FailureAnalyzers 异常分析

第一步继承 AbstractFailureAnalyzer,确认处理那个异常。

public class StartExceptionFailureAnalyzer extends AbstractFailureAnalyzer<RuntimeException> {
    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, RuntimeException cause) {
        Throwable rootCause = cause.getCause();
        if (rootCause instanceof StartException) {
            return new FailureAnalysis("测试启动异常","",rootCause);
        }
        return null;
    }
}
复制代码

第二步实现 FailureAnalysisReporter,确认处理某个异常的逻辑。

public class MyFailureAnalysisReporter implements FailureAnalysisReporter {
    private EmailService emailService;
    public MyFailureAnalysisReporter(){
        emailService=new EmailService();
    }
    @Override
    public void report(FailureAnalysis analysis) {
        final Throwable cause = analysis.getCause();
        final String message = cause.getMessage();
        emailService.sendEmail(String.join("","异常原因:",message));
    }

}
复制代码

第三部将上述两个类加入到 spring.factories

SpringFactoriesLoader 可以加载 spring.factories 的类

org.springframework.boot.diagnostics.FailureAnalyzer=/
com.fly.exception.start.analyzer.StartExceptionFailureAnalyzer
org.springframework.boot.diagnostics.FailureAnalysisReporter=/
com.fly.exception.start.analyzer.MyFailureAnalysisReporter
复制代码

服务运行时,异常处理

建议返回值可以设置成 ResponseEntity,比较容易设置请求头和状态码,restful 接口实现的时候挺有用。

@Component
@RestControllerAdvice
public class HandleActionException extends ResponseEntityExceptionHandler {
    public HandleActionException(){

    }

    @Override
    protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }
        final ResponseEntity<RetUtil> retUtilResponseEntity = new ResponseEntity<>(RetUtil.build().code(5000), headers, status);
        return retUtilResponseEntity;
    }

    @ExceptionHandler(value = {RuntimeException.class})
    public ResponseEntity<RetUtil> handleRunTimeException(){
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(RetUtil.build().code(5000));
    }
}
复制代码

代码大致处理逻辑如下

SpringBoot 异常处理二三事

继承 BasicErrorController 处理 /error 接口

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class MyErrorController extends BasicErrorController {

    private ApplicationContext applicationContext;


    public MyErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
                                List<ErrorViewResolver> errorViewResolvers) {
        super( errorAttributes,  serverProperties.getError(), errorViewResolvers);
    }

    @Override
    @RequestMapping
    public ResponseEntity error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return ResponseEntity.status(status).body(RetUtil.build().code(status.value()));
        }
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        return new ResponseEntity<>(RetUtil.build().code(status.value()).data(body), status);
    }
}
复制代码

Response.sendError 会将设置 org.apache.coyote.Response 对应的状态码 和 errorState,errorState 为 1 说明有问题。Tomcat 会转发 /error

SpringBoot 异常处理二三事

判断 errorState 是否为 1,为 1 进行业务处理,转发 /error

SpringBoot 异常处理二三事

转发请求,转发 /error 不会 执行过滤器了。

SpringBoot 异常处理二三事

上述图片执行过滤器链逻辑

SpringBoot 异常处理二三事

上述图片,责任链模式执行过滤器链(不会执行过滤器的操作),然后执行 DispatcherServlet.doDispatch

判断对象、对象父类,Class 的泛型类型

泛型使用限定符才能准确获取到

当使用通配符,没有使用限定符,是不能获取的。

ResolvableType 描述一个 Class 的信息

相等于对 Class 的 api 封装了一些东西,很方便使用。

ResolvableType.resolveGenerics 获取当前泛型。
ResolvableType.getInterfaces 获取父接口 Class 信息
ResolvableType.getSuperType 获取父类的 Class 信息
复制代码
@Test
    public void run77() {
        final MyList55<String, Demo2> stringDemo2MyList55 = new MyList55<>();
        final ResolvableType resolvableType = ResolvableType.forInstance(stringDemo2MyList55);
        // null
        // null
        for (ResolvableType generic : resolvableType.getGenerics()) {
            System.out.println(generic.resolve());
        }
    }
复制代码

代码演示使用的类父接口泛型

public interface MyGenericInterface<T extends CharSequence, M extends Demo> {
   default void onApplicationEvent1(T event,M event3){
      System.out.println(event.charAt(1));
      System.out.println(event3.getName());
   }
}

public class MyList2 implements MyGenericInterface<String,Demo>{
}

public class MyList33 implements MyGenericInterface<String,Demo2> {
}

@Data
public class Demo {
    private String name;
}

public class Demo2 extends Demo {
}
复制代码

获取接口的泛型类型

@Test
    public void run22() {
        ResolvableType resolvableType = ResolvableType.forClass(MyList2.class);
        final ResolvableType[] interfaces = resolvableType.getInterfaces();
        final ResolvableType[] generics = interfaces[0].getGenerics();
        /**
         * class java.lang.String
         * class com.fly.exception.Demo
         */

        for (ResolvableType generic : generics) {
            System.out.println(generic.resolve());
        }
    }
    
    @Test
    public void run33() {
        ResolvableType resolvableType = ResolvableType.forClass(MyList33.class);
        final ResolvableType[] interfaces = resolvableType.getInterfaces();
        final ResolvableType[] generics = interfaces[0].getGenerics();
        /**
         * class java.lang.String
         * class com.fly.exception.Demo2
         */
        for (ResolvableType generic : generics) {
            System.out.println(generic.resolve());
        }
    }
复制代码

获取对象的父接口泛型

@Test
    public void run44() {
        final MyList33 myList33 = new MyList33();
        final ResolvableType resolvableType = ResolvableType.forInstance(myList33);
        /**
         * class com.fly.exception.MyList33
         */
        System.out.println(resolvableType.resolve());
        final ResolvableType[] interfaces = resolvableType.getInterfaces();
        for (ResolvableType anInterface : interfaces) {
            final ResolvableType[] generics = anInterface.getGenerics();
            /**
             * class java.lang.String
             * class com.fly.exception.Demo2
             */
            for (ResolvableType generic : generics) {
                System.out.println(generic.resolve());
            }
        }
    }
复制代码

对象自己的泛型

@Test
public void run55() {
    MyList44<String, Demo> objectObjectMyList44 = new MyList44<>();
    final ResolvableType resolvableType = ResolvableType.forInstance(objectObjectMyList44);
    // class java.lang.String
    // class com.fly.exception.Demo
    for (ResolvableType generic : resolvableType.getGenerics()) {
        System.out.println(generic.resolve());
    }
}
复制代码

获取类自身的泛型

@Test
public void run66() {
    final ResolvableType resolvableType = ResolvableType.forClass(MyList44.class);
    // class java.lang.String
    // class com.fly.exception.Demo
    for (ResolvableType generic : resolvableType.getGenerics()) {
        System.out.println(generic.resolve());
    }
}
复制代码

原文 

https://juejin.im/post/5dcfb17ef265da0bc33091f2

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » SpringBoot 异常处理二三事

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址