SpringBoot 提供了註解 @EnableAsync
+ @Async
實現方法的異步調用。使用方法超級簡單,在啓動類上加上 @EnableAsync
註解開啓項目的異步調用功能,再在需異步調用的方法上加上註解 @Async
即可實現方法的異步調用。是不是能簡單?簡單吧。
接來下爲使大家能夠深刻理解異步調用,我將通過實現調用普通方法,使用 @EnableAsync
+ @Async
實現異步調用方法,和不使用 @EnableAsync
+ @Async
實現異步調用方法等三中國方式來說明。
一、編寫 Service
首先需要編寫一個 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<String> {
@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<String> 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
爲了直觀的測試代碼,也爲了方便大家理解,就選擇編寫一個 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 提供的 註解方式 實現異步調用。