據我觀察,不管是初級新手仍是高級開發者都流行用 Spring Boot 構建本身的程序。Spring Boot 「約定優於配置」的風格讓你們在開發時能專一於業務邏輯。須要的時候,查閱 Spring Boot 教程就能夠很方便地瞭解 Spring 的工做機制。儘管大多數時候只要添加幾個註解就能夠搞定,但有時候仍是須要了解它背後的運行機制,這樣才能更專業地使用 Spring Boot。java
本文將嘗試介紹如何在 Spring 中進行異步處理。spring
異步處理適用那些與業務邏輯(橫切關注點)不直接相關或者不做爲其餘業務邏輯輸入的部分,也可在分佈式系統中解耦。shell
*譯註:橫切關注點(cross-cutting concerns)指一些具備橫越多個模塊的行爲,使用傳統的軟件開發方法不可以達到有效模塊化的一類特殊關注點。*app
Spring 中,`@Async`註解能夠標記異步操做。然而,使用`@Async`時有一些限制,僅僅把它加在方法上並不能確保方法會在獨立的線程中執行。若是你只是偶爾用到 `@Async`,須要格外小心。異步
1. @Async 的工做機制async
首先爲方法添加 `Async` 註解。接着,Spring 會基於 `proxyTargetClass` 屬性,爲包含 `Async` 定義的對象建立代理(JDK Proxy/CGlib)。最後,Spring 會嘗試搜索與當前上下文相關的線程池,把該方法做爲獨立的執行路徑提交。確切地說,Spring 會搜索惟一的 `TaskExecutor` bean 或者名爲 `taskExecutor` 的 bean。若是找不到,則使用默認的 `SimpleAsyncTaskExecutor`。分佈式
要完成上面的過程,使用中須要注意幾個限制,不然會出現 `Async` 不起做用的狀況。ide
2. @Async 的限制模塊化
1. 必須在標記 `@ComponentScan` 或 `@configuration` 的類中使用 `@Async`。this
2.1 在類中使用 Async 註解
```java
package com.example.ask2shamik.springAsync.demo;
import java.util.Map;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncMailTrigger {
@Async
public void senMail(Map<String,String> properties) {
System.out.println("Trigger mail in a New Thread :: " + Thread.currentThread().getName());
properties.forEach((K,V)->System.out.println("Key::" + K + " Value ::" + V));
}
}
```
2.2 Caller 類
```java
package com.example.ask2shamik.springAsync.demo;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class AsyncCaller {
@Autowired
AsyncMailTrigger asyncMailTriggerObject;
public void rightWayToCall() {
System.out.println("Calling From rightWayToCall Thread " + Thread.currentThread().getName());
asyncMailTriggerObject.senMail(populateMap());
}
public void wrongWayToCall() {
System.out.println("Calling From wrongWayToCall Thread " + Thread.currentThread().getName());
AsyncMailTrigger asyncMailTriggerObject = new AsyncMailTrigger();
asyncMailTriggerObject.senMail(populateMap());
}
private Map<String,String> populateMap(){
Map<String,String> mailMap= new HashMap<String,String>();
mailMap.put("body", "A Ask2Shamik Article");
return mailMap;
}
}
```
上面的例子中,使用了 `@Autowired` 的 `AsyncMailTrigger` 受 `@ComponentScan` 管理,於是會建立新線程執行。而 `WrongWayToCall` 方法中建立的局部對象,不受 `@ComponentScan` 管理,不會建立新線程。
2.3 輸出
```shell
Calling From rightWayToCall Thread main
2019-03-09 14:08:28.893 INFO 8468 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
Trigger mail in a New Thread :: task-1
Key::body Value ::A Ask2Shamik Article
++++++++++++++++
Calling From wrongWayToCall Thread main
Trigger mail in a New Thread :: main
Key::body Value ::A Ask2Shamik Article
```
2. 不要在 `private` 方法上使用 `@Async` 註解。因爲在運行時不能建立代理,因此不起做用。
```java
@Async
private void senMail() {
System.out.println("A proxy on Private method " + Thread.currentThread().getName());
}
```
3. 調用 `methodAsync` 的 `caller` 方法與 `@Async` 方法應該在不一樣的類中定義。不然,儘管建立了代理對象,但 `caller` 會繞過代理直接調用方法,不會建立新線程。
(https://dzone.com/storage/temp/11422746-springasync.jpg)
2.4 示例
```java
package com.example.ask2shamik.springAsync.demo;
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() {
System.out.println("Calling From rightWayToCall Thread " + Thread.currentThread().getName());
asyncMailTriggerObject.senMail(populateMap());
}
public void wrongWayToCall() {
System.out.println("Calling From wrongWayToCall Thread " + Thread.currentThread().getName());
this.senMail(populateMap());
}
private Map<String,String> populateMap(){
Map<String,String> mailMap= new HashMap<String,String>();
mailMap.put("body", "A Ask2Shamik Article");
return mailMap;
}
@Async
public void senMail(Map<String,String> properties) {
System.out.println("Trigger mail in a New Thread :: " + Thread.currentThread().getName());
properties.forEach((K,V)->System.out.println("Key::" + K + " Value ::" + V));
}
}
```
最後,在執行的時候應當使用 `@EnableAsync` 註解。它的做用是讓 Spring 在後臺線程池中提交 `@Async` 方法。要自定義 `Executor` 可本身實現 bean。在接下來的文章中會給出具體的示例。
```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
@EnableAsync
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();
Thread.sleep(1000);
System.out.println("++++++++++++++++");
Thread.sleep(1000);
caller.wrongWayToCall();
};
}
}
```
3. 總結
但願經過本文的講解,能夠幫助理解 Async 的內部機制及使用中的限制。下一篇會探討 Async 中的異常處理。敬請期待!