SpringAsync最佳实践(2):ExceptionHandler 编译:唐尤华
链接:dzone/articles/effective-advice-on-spring-async-exceptionhandler-1
本⽂将讨论在 Spring Boot 使⽤ `@Async` 注解时如何捕捉异常。正⽂开始前,建议阅读系列的[第⼀篇][1]。
spring aop应用场景从主线程 fork 新线程时,有两种情况:
1. "Fire-and-forget":fork 线程,然后为这个线程分配任务,接下来什么也不⽤管。不需要关⼼任务执⾏结果,其他业务逻辑的执⾏也不依赖该结果。通常任务的返回类型是 `void`。让我们通过例⼦帮助理解:假设你在为员⼯发薪⽔,需要给每个员⼯发送⼀份⼯资单邮件,你可以异步执⾏该任务。发邮件显然不是核⼼业务逻辑,⽽是⼀个横切关注点。然⽽,发邮件很好,⽽且在某些情况下是必须的。这时候需要制定失败重试或者定时机制。
2. "Fire-with-callback":在主线程中 fork ⼀个线程,为该线程分配任务并关联 `Callback`。接下来,主线程会继续执⾏其他任务并持续检查 `Callback` 结果。主线程需要⼦线程 `Callback` 执⾏结果进⾏下⼀步⼯作。
假设你正在做⼀份员⼯报告,员⼯信息根据各⾃数据类型存储在不同的后端。General Service 存储员⼯
通⽤数据,⽐如姓名、⽣⽇、性别、地址等;Financial Service 存储薪资、税⾦以及其他 PF 相关数据。因此,你会创建两个并⾏线程,分别调⽤ General Service 与 Financial Service。这两组数据最终都要在报告中体现,因此需要进⾏数据组合,在主线程中表现为⼦线程 Callback 结果。⼀般会⽤ `CompletebleFuture` 实现。
在上⾯描述的场景中,如果⼀切顺利是最理想的结果。但如果执⾏中发⽣异常,该如何进⾏异常处理?
第⼆种情况下,由于回调执⾏后能够返回成功或失败,因此处理异常⾮常容易。失败的时候,异常会被封装在
`CompltebleFuture` ⾥,在主线程中可以检查异常并处理。处理异常的 Java 代码很简单,这⾥直接略过。
然⽽,第⼀种情况的异常处理⾮常棘⼿:创建的线程会执⾏业务逻辑,但如何确保业务执⾏成功?或者说,执⾏失败该如何进⾏调试,如何追踪是什么地⽅出现了问题?
解决⽅案很简单:注⼊⾃⼰的 exception handler。这样,当 `Async` ⽅法执⾏过程中发⽣异常,会把程序控制转交给handler。你的 handler 知道接下来该如何处理。很简单,不是吗?
要做到这⼀点,需要执⾏以下步骤:
1. `AsyncConfigurer`:`AsyncConfigurere` 是⼀个 Spring 提供的接⼝,包含两个⽅法。⼀个可以重载
`TaskExecutor`(线程池),另⼀个是 exception handler。Exception handler ⽀持注⼊⽤来捕捉 unCaught 异常,也可以⾃⼰定义 class 直接实现。这⾥我不会直接实现,⽽是⽤ Spring 提供的 `AsyncConfigurerSupport` 类,通过
`@Configuration` 和 `@EnableAsync` 注解提供默认实现。
```java
ample.ask2shamik.springAsync;
flect.Method;
urrent.Executor;
importorg.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
t.annotation.Configuration;
ask.SimpleAsyncTaskExecutor;
importorg.springframework.lang.Nullable;
importorg.springframework.lang.Nullable;
importorg.springframework.scheduling.annotation.AsyncConfigurerSupport;
importorg.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
publicclassCustomConfiguration extends AsyncConfigurerSupport {
@ Override
publicExecutor getAsyncExecutor(){
returnnewSimpleAsyncTaskExecutor();
}
@Override
@ Nullable
publicAsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(){
return(throwable, method, obj) -> {
System.out.println( "Exception Caught in Thread - "+ Thread.currentThread().getName());
System.out.println( "Exception message - "+ Message());
System.out.println( "Method name - "+ Name());
for(Object param : obj) {
System.out.println( "Parameter value - "+ param);
}
};
}
}
```
请注意,因为不想使⽤⾃定义 task executor,在 `getAsyncExecutor` ⽅法中,没有创建任何新的 executor。因此,我将使⽤ Spring 默认的 `SimpleAsyncExecutor`。
但是,我需要⾃⼰定义 uncaught exception handler 处理 uncaught 异常。因此,我写了⼀条继承
`AsyncUncaughtExceptionHandler` 类的 lambda 表达式并覆盖 `handleuncaughtexception` ⽅法。
这样,会让 Spring 加载与应⽤匹配的 `AsyncConfugurer(CustomConfiguration)` 并⽤ lambda 表达式进⾏异常处理。新建⼀个 `@Async` ⽅法抛出异常:
```java
ample.ask2shamik.springAsync;
importjava.util.Map;
importorg.springframework.scheduling.annotation.Async;
importorg.springframework.stereotype.Component;
@Component
publicclassAsyncMailTrigger {
@ Async
publicvoidsenMailwithException()throws Exception {
thrownewException( "SMTP Server not found :: orginated from Thread :: "+ Thread.currentThread().getName());
}
}
```
现在,创建调⽤⽅法。
```java
ample.ask2shamik.springAsync;
importjava.util.HashMap;
importjava.util.Map;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.scheduling.annotation.Async;
importorg.springframework.stereotype.Component;
@Component
publicclassAsyncCaller {
@Autowired
AsyncMailTrigger asyncMailTriggerObject;
publicvoidrightWayToCall()throws Exception {
System.out.println( "Calling From rightWayToCall Thread "+ Thread.currentThread().getName()); asyncMailTriggerObject.senMailwithException();
}
}
```
接下来让我们启动 Spring Boot 应⽤,看它如何捕捉 `sendMailwithException` ⽅法引发的异常。```java
ample.ask2shamik.springAsync;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.boot.CommandLineRunner;
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
t.ApplicationContext;
t.annotation.Bean;
importorg.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
publicclassDemoApplication {
@Autowired
AsyncCaller caller;
publicstaticvoidmain(String[] args){
SpringApplication.run(DemoApplication. class, args);
}
@ Bean
publicCommandLineRunner commandLineRunner(ApplicationContext ctx){
returnargs -> {
returnargs -> {
caller.rightWayToCall();
};
}
}
```
结果如下:
```shell
Calling From rightWayToCall Thread main
Exception Caught in Thread - SimpleAsyncTaskExecutor -1
Exception message - SMTP Server not found:: originated from Thread:: SimpleAsyncTaskExecutor -1 Method name - senMailwithException
```
总结
希望你喜欢这个教程!如果你有任何问题,欢迎在下⽅的评论区留⾔。敬请期待第3篇!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论