SpringBoot 提供了注解 @EnableAsync
+ @Async
实现方法的异步调用。使用方法超级简单,在启动类上加上 @EnableAsync
注解开启项目的异步调用功能,再在需异步调用的方法上加上注解 @Async
即可实现方法的异步调用。是不是能简单?简单吧。
接来下为使大家能够深刻理解异步调用,我将通过实现调用普通方法,使用 @EnableAsync
+ @Async
实现异步调用方法,和不使用 @EnableAsync
+ @Async
实现异步调用方法等三中国方式来说明。
首先需要编写一个 Service 和 ServiceImpl,新建 src/main/java/com/service/AsyncService.java
package com.service;
/**
* @Description @Async 使用
* @author 欧阳
* @since 2019年4月14日 上午11:50:34
* @version V1.0
*/
public interface AsyncService {
/**
* 描述:普通的方法
* @return
*/
public String commMethod();
/**
* 描述:使用 @Async 实现异步调用
* @return
*/
public String asyncMethod();
/**
* 描述:使用 Callable 实现异步调用
* @return
*/
public String callableMethod();
}
src/main/java/com/service/impl/AsyncServiceImpl.java
package com.service.impl;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.service.AsyncService;
/**
* @Description @Async 使用
* @author 欧阳
* @since 2019年4月14日 上午11:51:07
* @version V1.0
*/
@Service
public class AsyncServiceImpl implements AsyncService {
private static final Logger log = LoggerFactory.getLogger(AsyncServiceImpl.class);
private static final String result = "SUCCUSS"; //返回结果
@Override
public String commMethod() {
log.info("2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("3");
return result;
}
@Override
@Async
public String asyncMethod() {
log.info("2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("3");
return result;
}
@Override
public String callableMethod() {
class MyCallable implements Callable {
@Override
public String call() throws Exception {
log.info("2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("3");
return result;
}
}
ExecutorService pool = Executors.newFixedThreadPool(1);
Future submit = pool.submit(new MyCallable());
String result = null;
if(submit.isDone()) {
try {
result = submit.get();
} catch (Exception e) {
e.printStackTrace();
}
}
// 关闭线程池
pool.shutdown();
return result;
}
}
其中 commMethod
方法是我们平时用的普通方法;asyncMethod
方法是我们用 @Async
注解的方法,能够实现异步调用;callableMethod
方法是通过不使用 @Async
注解实现异步调用的方法。
为了直观的测试代码,也为了方便大家理解,就选择编写一个 Controller 来进行测试,这里说一下还可以使用 SpringBoot 整合好的 单元测试功能来测试,可以参考我之前写的文章,在 SpringBoot 整合持久层技术的时候有用到。编写的 Controller
代码如下:
package com.controller;
/**
* @Description @Async 使用
* @author 欧阳
* @since 2019年4月14日 上午11:49:53
* @version V1.0
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.service.AsyncService;
@RestController
public class AsyncController {
private static final Logger log = LoggerFactory.getLogger(AsyncController.class);
@Autowired
private AsyncService asyncService;
@RequestMapping("/commMethod")
public String commMethod() {
log.info("1");
String result = asyncService.commMethod();
log.info("4");
return result;
}
@RequestMapping("/asyncMethod")
public String asyncMethod() {
log.info("1");
String result = asyncService.asyncMethod();
log.info("4");
return result;
}
@RequestMapping("/callableMethod")
public String callableMethod() {
log.info("1");
String result = asyncService.callableMethod();
log.info("4");
return result;
}
}
在启动类上加上注解 @EnableAsync
后启动项目即可。
@SpringBootApplication
@EnableAsync
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
首页访问 URL http://localhost:8080/commMethod
,这个 URL 是普通的没有实现异步的,观察控制台打印信息:
2019-04-14 13:10:25.718 INFO 2332 --- [nio-8080-exec-7] com.controller.AsyncController : 1
2019-04-14 13:10:25.719 INFO 2332 --- [nio-8080-exec-7] com.service.impl.AsyncServiceImpl : 2
2019-04-14 13:10:28.719 INFO 2332 --- [nio-8080-exec-7] com.service.impl.AsyncServiceImpl : 3
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
注解的方法,观察控制台打印信息:
2019-04-14 13:15:31.192 INFO 2332 --- [nio-8080-exec-1] com.controller.AsyncController : 1
2019-04-14 13:15:31.192 INFO 2332 --- [nio-8080-exec-1] com.controller.AsyncController : 4
2019-04-14 13:15:31.192 INFO 2332 --- [tTaskExecutor-1] com.service.impl.AsyncServiceImpl : 2
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
,我们可以注入一个
package com.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* @Description
* 注入 TaskExecutor 解决 No task executor bean found for async processing:
* no bean of type TaskExecutor and no bean named ‘taskExecutor’ either
* @author 欧阳
* @since 2019年4月14日 下午12:28:11
* @version V1.0
*/
@Configuration
public class TaskExecutorBean {
@Bean
public TaskExecutor getTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
最后我们访问 http://localhost:8080/callableMethod
,发现其效果和访问 http://localhost:8080/asyncMethod
的是一模一样的,这说明也可以使用多线程技术实现 @EnableAsync
+ @Async
的效果,但是我们也发现使用多线程技术实现的异步调用的代码量远比注解方式要多,所以在一般场景下建议使用 SpringBoot 提供的 注解方式 实现异步调用。