Spring Async 最佳实践:ExceptionHandler

Posted ImportNew

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Async 最佳实践:ExceptionHandler相关的知识,希望对你有一定的参考价值。

(给ImportNew加星标,提高Java技能)


编译:唐尤华

链接:dzone.com/articles/effective-advice-on-spring-async-exceptionhandler-1


本文将讨论在 Spring Boot 使用 `@Async` 注解时如何捕捉异常。正文开始前,建议。


从主线程 fork 新线程时,有两种情况:


1. "Fire-and-forget":fork 线程,然后为这个线程分配任务,接下来什么也不用管。不需要关心任务执行结果,其他业务逻辑的执行也不依赖该结果。通常任务的返回类型是 `void`。让我们通过例子帮助理解:假设你在为员工发薪水,需要给每个员工发送一份工资单邮件,你可以异步执行该任务。发邮件显然不是核心业务逻辑,而是一个横切关注点。然而,发邮件很好,而且在某些情况下是必须的。这时候需要制定失败重试或者定时机制。


2. "Fire-with-callback":在主线程中 fork 一个线程,为该线程分配任务并关联 `Callback`。接下来,主线程会继续执行其他任务并持续检查 `Callback` 结果。主线程需要子线程 `Callback` 执行结果进行下一步工作。



在上面描述的场景中,如果一切顺利是最理想的结果。但如果执行中发生异常,该如何进行异常处理?


第二种情况下,由于回调执行后能够返回成功或失败,因此处理异常非常容易。失败的时候,异常会被封装在 `CompltebleFuture` 里,在主线程中可以检查异常并处理。处理异常的 Java 代码很简单,这里直接略过。


然而,第一种情况的异常处理非常棘手:创建的线程会执行业务逻辑,但如何确保业务执行成功?或者说,执行失败该如何进行调试,如何追踪是什么地方出现了问题?


解决方案很简单:注入自己的 exception handler。这样,当 `Async` 方法执行过程中发生异常,会把程序控制转交给 handler。你的 handler 知道接下来该如何处理。很简单,不是吗?


要做到这一点,需要执行以下步骤:


1. `AsyncConfigurer`:`AsyncConfigurere` 是一个 Spring 提供的接口,包含两个方法。一个可以重载 `TaskExecutor`(线程池),另一个是 exception handler。Exception handler 支持注入用来捕捉 unCaught 异常,也可以自己定义 class 直接实现。这里我不会直接实现,而是用 Spring 提供的 `AsyncConfigurerSupport` 类,通过 `@Configuration` 和 `@EnableAsync` 注解提供默认实现。


```java
package com.example.ask2shamik.springAsync;


import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;


@Configuration
@EnableAsync
public class CustomConfiguration extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor()
{
return new SimpleAsyncTaskExecutor();
}


@Override
@Nullable
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler()
{
return (throwable, method, obj) -> {
System.out.println("Exception Caught in Thread - " + Thread.currentThread().getName());
System.out.println("Exception message - " + throwable.getMessage());
System.out.println("Method name - " + method.getName());
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
package com.example.ask2shamik.springAsync;


import java.util.Map;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;


@Component
public class AsyncMailTrigger {
@Async
public void senMailwithException() throws Exception
{
throw new Exception("SMTP Server not found :: orginated from Thread :: " + Thread.currentThread().getName());
}
}
```


现在,创建调用方法


```java
package com.example.ask2shamik.springAsync;


import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;


@Component
public class AsyncCaller {
@Autowired
AsyncMailTrigger asyncMailTriggerObject;


public void rightWayToCall() throws Exception {
System.out.println("Calling From rightWayToCall Thread " + Thread.currentThread().getName());
asyncMailTriggerObject.senMailwithException();
}
}
```


接下来让我们启动 Spring Boot 应用,看它如何捕捉 `sendMailwithException` 方法引发的异常。


```java
package com.example.ask2shamik.springAsync;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import com.example.ask2shamik.springAsync.demo.AsyncCaller;


@SpringBootApplication
public class DemoApplication {
@Autowired
AsyncCaller caller;


public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}


@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx)
{
return args -> {
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篇!


推荐阅读

(点击标题可跳转阅读)





看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

喜欢就点一下「好看」呗~

以上是关于Spring Async 最佳实践:ExceptionHandler的主要内容,如果未能解决你的问题,请参考以下文章

Node.js 最佳实践异常处理——在 Async/Await 之后

es6+最佳入门实践(11)

从@Async案例找到Spring框架的bug:exposeProxy=true不生效原因大剖析+最佳解决方案享学Spring

Spring事务使用最佳实践

Spring事务使用最佳实践

Spring事务使用最佳实践