【Spring Boot】@Async 注解的使用

案例需求:用户下单后,服务端生成订单并保存,然后给用户发短信、邮件通知下单成功
由于短信和邮件发送比较耗时,是秒级别的,所以应采用异步的方式,实现方案有很多,这里使用Spring提供的对ThreadPoolExecutor封装的线程池ThreadPoolTaskExecutor,直接在业务方法上使用注解@Async即可(先用@EnableAsync开启)

线程池ThreadPoolExecutor执行规则如下

在这里插入图片描述

1. 自定义application.yml配置项及其值

和创建线程池的参数对应,具体含义见步骤2的成员属性注释,参数根据实际情况自行修改,这里仅作为演示

async:
  # 异步订单任务线程池参数
  order-service:
    core-pool-size: 20
    max-pool-size: 100
    keep-alive-seconds: 10
    queue-capacity: 200
    thread-name-prefix: async-order-service-

2. 编写线程池配置类

@Setter
@ConfigurationProperties(prefix = "async.order-service")
@EnableAsync
@Configuration
public class AsyncOrderServiceConfig {
    /**
     * 核心线程数(默认线程数)
     */
    private int corePoolSize;
    /**
     * 最大线程数
     */
    private int maxPoolSize;
    /**
     * 允许线程空闲时间(单位:默认为秒)
     */
    private int keepAliveSeconds;
    /**
     * 缓冲队列大小
     */
    private int queueCapacity;
    /**
     * 线程池名前缀
     */
    private String threadNamePrefix;

    @Bean
    public ThreadPoolTaskExecutor asyncOrderService() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        threadPoolTaskExecutor.setThreadNamePrefix(threadNamePrefix);
        // 线程池对拒绝任务的处理策略
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 完成任务自动关闭 , 默认为false
        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        // 核心线程超时退出,默认为false
        threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

首先通过@EnableAsync开始对异步任务的支持,然后使用@ConfigurationProperties把同类配置信息自动封装成一个实体类。其属性prefix表示application.yml配置文件中配置项的前缀,最后结合Lombok的@Setter保证配置文件的值能够注入到该类对应的属性中

此步其实可省略,因为使用@Async不指定value属性时,SpringBoot默认会创建 SimpleAsyncTaskExecutor线程池来处理异步方法调用
注解@Async定义如下,只有一个value属性(SpringBoot 2.3.7 RELEASE)
在这里插入图片描述

3. 创建两个接口OrderService和AsyncOrderService及其实现类

创建订单业务接口

public interface OrderService {
    /**
     * 下订单
     */
    void makeOrder();

    /**
     * 生成订单并保存到数据库
     */
    void saveOrder();
}

创建异步订单业务接口

public interface AsyncOrderService {
    /**
     * 短信服务
     */
    void sendSms();

    /**
     * 邮件服务
     */
    void sendEmail();
}

创建实现类,编写简单逻辑代替业务逻辑

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private AsyncOrderService asyncOrderService;

    @Override
    public void makeOrder() {
        // 发短信
        asyncOrderService.sendSms();
        // 发邮件
        asyncOrderService.sendEmail();
        // 保存订单
        saveOrder();
    }

    @Override
    public void saveOrder() {
        log.info("生成订单并保存...");
    }
}
@Slf4j
@Service
public class AsyncOrderServiceImpl implements AsyncOrderService {
    @Async("asyncOrderService")
    @Override
    public void sendSms() {
        try {
            long start = System.currentTimeMillis();
            log.info("开始发送短信");
            // 模拟耗时3s
            TimeUnit.SECONDS.sleep(3);
            log.info("短信发送完毕!");
            log.info("发送短信耗时:{} ms", System.currentTimeMillis() - start);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Async("asyncOrderService")
    @Override
    public void sendEmail() {
        try {
            long start = System.currentTimeMillis();
            log.info("开始发送邮件");
            // 模拟耗时2s
            TimeUnit.SECONDS.sleep(2);
            log.info("邮件发送完毕!");
            log.info("发送邮件耗时:{} ms", System.currentTimeMillis() - start);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这里使用了@Async注解,它只有一个String类型的value属性,用于指定一个 Bean 的 Name,类型是 Executor 或 TaskExecutor,表示使用这个指定的线程池来执行异步任务,这里指定为步骤2中线程池配置类asyncOrderService

有几点需要注意下,会使@Async失效

● 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
● 异步方法不能与被调用的异步方法在同一个类中
● 异步类没有使用@Component注解(或其他同类注解)导致spring无法扫描到异步类
● 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
● 异步方法使用非public或static修饰

4. 创建Controller用于测试

@RestController
public class TestController {
    @Autowired
    private OrderService orderService;

    @PostMapping("/makeOrder")
    public String makeOrder() {
        orderService.makeOrder();
        return "ok";
    }
}

5. 观察使用和不用@Async时,控制台打印日志的顺序

使用调式工具调用接口http://localhost:8080/makeOrder,控制台内容如下

使用@Async在这里插入图片描述不使用@Async
在这里插入图片描述
若需对比动态图查看效果,建议刷新页面后即刻查看,效果最佳
使用@Async运行效果
在这里插入图片描述
不使用@Async运行效果
在这里插入图片描述

从代码逻辑来看,前者是异步执行,后者是同步执行;
从执行效果来看,使用@Async完成速度明显提高
在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的示例: 假设我们有一个 UserService,其中有一个方法 sendEmail,它需要异步地发送电子邮件。现在我们想要将当前用户的信息透传给异步任务中使用的线程。 首先,我们需要在异步方法上添加 @Async 注解,并在配置类中启用异步支持: ```java @Configuration @EnableAsync public class AppConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(100); executor.setQueueCapacity(10); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); } } ``` 在上面的示例中,我们创建了一个 ThreadPoolTaskExecutor,它将用于执行异步任务。我们还实现了 AsyncConfigurer 接口,并覆盖了 getAsyncExecutor 和 getAsyncUncaughtExceptionHandler 方法,以提供自定义的 Executor 和异常处理程序。 现在我们需要将当前用户信息存储在一个 ThreadLocal 对象中。这可以通过一个拦截器来实现: ```java public class UserContextInterceptor extends HandlerInterceptorAdapter { private final ThreadLocal<String> userThreadLocal = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String currentUser = request.getHeader("X-User"); userThreadLocal.set(currentUser); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { userThreadLocal.remove(); } public String getCurrentUser() { return userThreadLocal.get(); } } ``` 在上面的示例中,我们创建了一个 UserContextInterceptor,它将在每个请求的开始和结束时执行。在 preHandle 方法中,我们从请求头中获取当前用户信息,并将其存储在一个 ThreadLocal 对象中。在 afterCompletion 方法中,我们将删除该信息,以避免内存泄漏。 现在,我们可以在 UserService 的 sendEmail 方法中使用 UserContextInterceptor 中存储的当前用户信息: ```java @Service public class UserService { @Autowired private JavaMailSender mailSender; @Autowired private UserContextInterceptor userContextInterceptor; @Async public void sendEmail(String to, String subject, String text) { String currentUser = userContextInterceptor.getCurrentUser(); // 使用当前用户信息发送电子邮件 // ... } } ``` 在上面的示例中,我们使用 @Autowired 注解将 UserContextInterceptor 注入到 UserService 中。在 sendEmail 方法中,我们从 UserContextInterceptor 中获取当前用户信息,并在发送电子邮件时使用它。 通过这种方式,我们可以将当前用户信息透传给异步任务中使用的线程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值