歡迎光臨
每天分享高質量文章

Spring Aysnc 最佳實踐(1):原理與限制

(給ImportNew加星標,提高Java技能)

 

編譯:唐尤華

連結: dzone.com/articles/effective-advice-on-spring-async-part-1

據我觀察,無論是初級新手還是高階開發者都流行用 Spring Boot 構建自己的程式。Spring Boot “約定優於配置”的風格讓大家在開發時能專註於業務邏輯。需要的時候,查閱 Spring Boot 教程就可以很方便地瞭解 Spring 的工作機制。儘管大多數時候只要新增幾個註解就可以搞定,但有時候還是需要瞭解它背後的執行機制,這樣才能更專業地使用 Spring Boot。

 

本文將嘗試介紹如何在 Spring 中進行非同步處理。

 

非同步處理適用那些與業務邏輯(橫切關註點)不直接相關或者不作為其他業務邏輯輸入的部分,也可在分散式系統中解耦。

 

*譯註:橫切關註點(cross-cutting concerns)指一些具有橫越多個模組的行為,使用傳統的軟體開發方法不能夠達到有效模組化的一類特殊關註點。*

 

Spring 中,`@Async`註解可以標記非同步操作。然而,使用`@Async`時有一些限制,僅僅把它加在方法上並不能確保方法會在獨立的執行緒中執行。如果你只是偶爾用到 `@Async`,需要格外當心。

 

1. @Async 的工作機制

 

首先為方法新增 `Async` 註解。接著,Spring 會基於 `proxyTargetClass` 屬性,為包含 `Async` 定義的物件建立代理(JDK Proxy/CGlib)。最後,Spring 會嘗試搜尋與當前背景關係相關的執行緒池,把該方法作為獨立的執行路徑提交。確切地說,Spring 會搜尋唯一的 `TaskExecutor` bean 或者名為 `taskExecutor` 的 bean。如果找不到,則使用預設的 `SimpleAsyncTaskExecutor`。

 

要完成上面的過程,使用中需要註意幾個限制,否則會出現 `Async` 不起作用的情況。

 

2. @Async 的限制

 

1. 必須在標記 `@ComponentScan` 或 `@configuration` 的類中使用 `@Async`。

 

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 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 populateMap(){
        Map mailMap= new HashMap();
        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 populateMap(){
        Map mailMap= new HashMap();
        mailMap.put("body", "A Ask2Shamik Article");
        return mailMap;
    }

    @Async
    public void senMail(Map 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 中的異常處理。敬請期待!

    贊(0)

    分享創造快樂