SpringBoot 异常处理二三事

2,694 阅读5分钟

前言

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

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

代码 GitHub

涉及内容。

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


实现这个接口 ApplicationListener<ApplicationFailedEvent> ,

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

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


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

SpringBoot 运行期间发生的异常。

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

Tomcat 会根据 Response 判断是否有异常需要处理。
然后转发 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));
    }
}

代码大致处理逻辑如下

继承 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

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

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

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

上述图片,责任链模式执行过滤器链(不会执行过滤器的操作),然后执行 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());
    }
}