2020 之前打算将 Spring 和 SpringBoot 的官方文档看一遍,顺便看点感兴趣的源码。 昨天莫名想研究在 SpringBoot 中 Exception 怎么处理的。 复制代码
代码 GitHub
实现这个接口 ApplicationListener<ApplicationReadyEvent> , 将 Bean 加入 ioc ,当程序启动发生异常你会感知到。比如启动失败发送邮件通知。 实现这个接口 ApplicationListener<ApplicationReadyEvent> , 将 Bean 加入 ioc 容器中,当程序启动成功你会感知到。 基于实现 SpringBootExceptionReporter,对启动异常分析, 在我们自定义 starter 很有用 复制代码
@ExceptionHandler 和 @RestControllerAdvice 结合。处理标记的异常。 Tomcat 会根据 Request 判断是否有异常需要处理。 然后转发 DispatcherServlet url /error,这个可路径在 SpringBoot 可修改。 BasicErrorController 既是处理 /error 。 会讲一些源码,记录一下处理流程。 复制代码
看源码的时候学到的,很强大的功能。ResolvableType 复制代码
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()+"启动成功了"); } } 复制代码
第一步继承 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)); } } 复制代码
代码大致处理逻辑如下
@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
当使用通配符,没有使用限定符,是不能获取的。
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()); } } 复制代码