Springboot使用@EnableAsync @Async实现异步调用——SpringBoot学习

太过爱你忘了你带给我的痛 2022-02-20 16:39 645阅读 0赞

  SpringBoot 提供了注解 @EnableAsync + @Async 实现方法的异步调用。使用方法超级简单,在启动类上加上 @EnableAsync 注解开启项目的异步调用功能,再在需异步调用的方法上加上注解 @Async 即可实现方法的异步调用。是不是能简单?简单吧。

  接来下为使大家能够深刻理解异步调用,我将通过实现调用普通方法,使用 @EnableAsync + @Async 实现异步调用方法,和不使用 @EnableAsync + @Async 实现异步调用方法等三中国方式来说明。

一、编写 Service

  首先需要编写一个 Service 和 ServiceImpl,新建 src/main/java/com/service/AsyncService.java

  1. package com.service;
  2. /**
  3. * @Description @Async 使用
  4. * @author 欧阳
  5. * @since 2019年4月14日 上午11:50:34
  6. * @version V1.0
  7. */
  8. public interface AsyncService {
  9. /**
  10. * 描述:普通的方法
  11. * @return
  12. */
  13. public String commMethod();
  14. /**
  15. * 描述:使用 @Async 实现异步调用
  16. * @return
  17. */
  18. public String asyncMethod();
  19. /**
  20. * 描述:使用 Callable 实现异步调用
  21. * @return
  22. */
  23. public String callableMethod();
  24. }

   src/main/java/com/service/impl/AsyncServiceImpl.java

  1. package com.service.impl;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.ExecutorService;
  4. import java.util.concurrent.Executors;
  5. import java.util.concurrent.Future;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import org.springframework.scheduling.annotation.Async;
  9. import org.springframework.stereotype.Service;
  10. import com.service.AsyncService;
  11. /**
  12. * @Description @Async 使用
  13. * @author 欧阳
  14. * @since 2019年4月14日 上午11:51:07
  15. * @version V1.0
  16. */
  17. @Service
  18. public class AsyncServiceImpl implements AsyncService {
  19. private static final Logger log = LoggerFactory.getLogger(AsyncServiceImpl.class);
  20. private static final String result = "SUCCUSS"; //返回结果
  21. @Override
  22. public String commMethod() {
  23. log.info("2");
  24. try {
  25. Thread.sleep(3000);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. log.info("3");
  30. return result;
  31. }
  32. @Override
  33. @Async
  34. public String asyncMethod() {
  35. log.info("2");
  36. try {
  37. Thread.sleep(3000);
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. log.info("3");
  42. return result;
  43. }
  44. @Override
  45. public String callableMethod() {
  46. class MyCallable implements Callable<String> {
  47. @Override
  48. public String call() throws Exception {
  49. log.info("2");
  50. try {
  51. Thread.sleep(3000);
  52. } catch (InterruptedException e) {
  53. e.printStackTrace();
  54. }
  55. log.info("3");
  56. return result;
  57. }
  58. }
  59. ExecutorService pool = Executors.newFixedThreadPool(1);
  60. Future<String> submit = pool.submit(new MyCallable());
  61. String result = null;
  62. if(submit.isDone()) {
  63. try {
  64. result = submit.get();
  65. } catch (Exception e) {
  66. e.printStackTrace();
  67. }
  68. }
  69. // 关闭线程池
  70. pool.shutdown();
  71. return result;
  72. }
  73. }

  其中 commMethod 方法是我们平时用的普通方法;asyncMethod 方法是我们用 @Async 注解的方法,能够实现异步调用;callableMethod 方法是通过不使用 @Async 注解实现异步调用的方法。

二、编写 Controller

  为了直观的测试代码,也为了方便大家理解,就选择编写一个 Controller 来进行测试,这里说一下还可以使用 SpringBoot 整合好的 单元测试功能来测试,可以参考我之前写的文章,在 SpringBoot 整合持久层技术的时候有用到。编写的 Controller 代码如下:

  1. package com.controller;
  2. /**
  3. * @Description @Async 使用
  4. * @author 欧阳
  5. * @since 2019年4月14日 上午11:49:53
  6. * @version V1.0
  7. */
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.web.bind.annotation.RequestMapping;
  12. import org.springframework.web.bind.annotation.RestController;
  13. import com.service.AsyncService;
  14. @RestController
  15. public class AsyncController {
  16. private static final Logger log = LoggerFactory.getLogger(AsyncController.class);
  17. @Autowired
  18. private AsyncService asyncService;
  19. @RequestMapping("/commMethod")
  20. public String commMethod() {
  21. log.info("1");
  22. String result = asyncService.commMethod();
  23. log.info("4");
  24. return result;
  25. }
  26. @RequestMapping("/asyncMethod")
  27. public String asyncMethod() {
  28. log.info("1");
  29. String result = asyncService.asyncMethod();
  30. log.info("4");
  31. return result;
  32. }
  33. @RequestMapping("/callableMethod")
  34. public String callableMethod() {
  35. log.info("1");
  36. String result = asyncService.callableMethod();
  37. log.info("4");
  38. return result;
  39. }
  40. }

三、修改启动类

  在启动类上加上注解 @EnableAsync 后启动项目即可。

  1. @SpringBootApplication
  2. @EnableAsync
  3. public class App {
  4. public static void main(String[] args) {
  5. SpringApplication.run(App.class, args);
  6. }
  7. }

四、测试结果分析

  首页访问 URL http://localhost:8080/commMethod ,这个 URL 是普通的没有实现异步的,观察控制台打印信息:

  1. 2019-04-14 13:10:25.718 INFO 2332 --- [nio-8080-exec-7] com.controller.AsyncController : 1
  2. 2019-04-14 13:10:25.719 INFO 2332 --- [nio-8080-exec-7] com.service.impl.AsyncServiceImpl : 2
  3. 2019-04-14 13:10:28.719 INFO 2332 --- [nio-8080-exec-7] com.service.impl.AsyncServiceImpl : 3
  4. 2019-04-14 13:10:28.719 INFO 2332 --- [nio-8080-exec-7] com.controller.AsyncController : 4

  注意这里的打印顺序是 1 2 3 4 。说明 Controller 中的 commMethod 方法是按照程序执行顺序顺序往下执行,在请求该 URL 后切回 Eclipse 观察控制台输出还发现 打印的 2 和 3 中间有时间间隔,这是因为在中间有 Thread.sleep(3000); 三秒的休眠时间;同时到最后 4 打印完后浏览器显示 SUCCUSS 的字样。

  接着我们访问 URL http://localhost:8080/asyncMethod,这个 URL 是使用 @Async 注解的方法,观察控制台打印信息:

  1. 2019-04-14 13:15:31.192 INFO 2332 --- [nio-8080-exec-1] com.controller.AsyncController : 1
  2. 2019-04-14 13:15:31.192 INFO 2332 --- [nio-8080-exec-1] com.controller.AsyncController : 4
  3. 2019-04-14 13:15:31.192 INFO 2332 --- [tTaskExecutor-1] com.service.impl.AsyncServiceImpl : 2
  4. 2019-04-14 13:15:34.193 INFO 2332 --- [tTaskExecutor-1] com.service.impl.AsyncServiceImpl : 3

  我们发现这次打印的顺序是 1 4 2 3。说明 Controller 中的 asyncMethod 方法不是按找顺序执行的,而是先执行 1 4 ,再执行 2 3 ,,同时我们可以观察到浏览器页面上什么都没有,没有显示 SUCCUSS 的字样,也就是说调用的 asyncService 中的 asyncMethod 方法是异步的。

  这里说报一个警告 :No task executor bean found for async processing: no bean of type TaskExecutor and no bean named ‘taskExecutor’ either 。这个警告意思是说 Spring 容器中 没有 TaskExecutor ,我们可以注入一个

  1. package com.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.core.task.TaskExecutor;
  5. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  6. /**
  7. * @Description
  8. * 注入 TaskExecutor 解决 No task executor bean found for async processing:
  9. * no bean of type TaskExecutor and no bean named ‘taskExecutor’ either
  10. * @author 欧阳
  11. * @since 2019年4月14日 下午12:28:11
  12. * @version V1.0
  13. */
  14. @Configuration
  15. public class TaskExecutorBean {
  16. @Bean
  17. public TaskExecutor getTaskExecutor() {
  18. return new ThreadPoolTaskExecutor();
  19. }
  20. }

  最后我们访问 http://localhost:8080/callableMethod ,发现其效果和访问 http://localhost:8080/asyncMethod 的是一模一样的,这说明也可以使用多线程技术实现 @EnableAsync + @Async 的效果,但是我们也发现使用多线程技术实现的异步调用的代码量远比注解方式要多,所以在一般场景下建议使用 SpringBoot 提供的 注解方式 实现异步调用。

发表评论

表情:
评论列表 (有 0 条评论,645人围观)

还没有评论,来说两句吧...

相关阅读