亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Spring @Async 注解異步調(diào)用失效的五種解決方案

 更新時(shí)間:2025年05月02日 11:24:01   投稿:yin  
加上了@Async 注解,期待它能異步執(zhí)行,結(jié)果發(fā)現(xiàn)它還是同步執(zhí)行的?更困惑的是,同樣的注解在其他地方卻能正常工作,這個(gè)問(wèn)題困擾了很多 Java 開(kāi)發(fā)者,尤其是當(dāng)你在同一個(gè)類中調(diào)用帶有@Async 注解的方法時(shí),今天,我們就來(lái)深入解析這個(gè)問(wèn)題的原因,并提供多種實(shí)用的解決方案

給一個(gè)方法加上了@Async 注解,期待它能異步執(zhí)行,結(jié)果發(fā)現(xiàn)它還是同步執(zhí)行的?更困惑的是,同樣的注解在其他地方卻能正常工作。這個(gè)問(wèn)題困擾了很多 Java 開(kāi)發(fā)者,尤其是當(dāng)你在同一個(gè)類中調(diào)用帶有@Async 注解的方法時(shí)。今天,我們就來(lái)深入解析這個(gè)問(wèn)題的原因,并提供多種實(shí)用的解決方案。

Spring @Async 的正常工作原理

在討論內(nèi)部調(diào)用問(wèn)題前,我們先了解一下@Async 注解的基本工作原理。

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

// 簡(jiǎn)單的用戶類
class User {
    private String email;
    private String name;

    // 默認(rèn)構(gòu)造器(Spring Bean實(shí)例化需要)
    public User() {}

    public User(String email, String name) {
        this.email = email;
        this.name = name;
    }

    public String getEmail() { return email; }
    public String getName() { return name; }
    public void setEmail(String email) { this.email = email; }
    public void setName(String name) { this.name = name; }
}

@Service
public class EmailService {

    @Async
    public void sendEmail(String to, String content) {
        // 耗時(shí)的郵件發(fā)送邏輯
        System.out.println("發(fā)送郵件中... 當(dāng)前線程: " + Thread.currentThread().getName());
    }
}

@Service
public class UserService {
    @Autowired
    private EmailService emailService;

    public void registerUser(User user) {
        // 用戶注冊(cè)邏輯
        System.out.println("注冊(cè)用戶中... 當(dāng)前線程: " + Thread.currentThread().getName());

        // 異步發(fā)送歡迎郵件
        emailService.sendEmail(user.getEmail(), "歡迎注冊(cè)!");

        // 注冊(cè)完成,立即返回
        System.out.println("注冊(cè)完成!");
    }
}

Spring @Async 的工作原理如下:

Spring @Async 內(nèi)部調(diào)用失效問(wèn)題:五種解決方案實(shí)戰(zhàn)分析_TaskExecutor

Spring 通過(guò) AOP 代理實(shí)現(xiàn)@Async 功能。當(dāng)一個(gè)方法被@Async 注解標(biāo)記時(shí),Spring 會(huì)創(chuàng)建一個(gè)代理對(duì)象。當(dāng)外部代碼調(diào)用該方法時(shí),調(diào)用實(shí)際上首先被代理對(duì)象攔截,然后代理將任務(wù)提交到線程池異步執(zhí)行。

Spring 默認(rèn)對(duì)實(shí)現(xiàn)接口的類使用 JDK 動(dòng)態(tài)代理,對(duì)非接口類使用 CGLIB 代理。但無(wú)論哪種代理,重要的是調(diào)用必須經(jīng)過(guò)代理對(duì)象,才能觸發(fā)@Async 的處理邏輯。

內(nèi)部調(diào)用問(wèn)題

問(wèn)題出現(xiàn)在同一個(gè)類中調(diào)用自己的@Async 方法時(shí):

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class NotificationService {

    public void notifyAll(List<User> users, String message) {
        System.out.println("開(kāi)始通知所有用戶... 當(dāng)前線程: " + Thread.currentThread().getName());

        for (User user : users) {
            // 調(diào)用同一個(gè)類中的@Async方法
            sendNotification(user, message);  // 問(wèn)題:這里變成了同步調(diào)用!
        }

        System.out.println("通知流程初始化完成!");  // 實(shí)際要等所有通知發(fā)送完才會(huì)執(zhí)行到這里
    }

    @Async
    public void sendNotification(User user, String message) {
        // 模擬耗時(shí)操作
        try {
            System.out.println("正在發(fā)送通知給" + user.getName() +
                    "... 當(dāng)前線程: " + Thread.currentThread().getName());
            Thread.sleep(1000); // 模擬耗時(shí)操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

上面的代碼中,雖然sendNotification方法標(biāo)記了@Async,但當(dāng)在notifyAll方法中調(diào)用它時(shí),它還是會(huì)同步執(zhí)行,這不是我們預(yù)期的行為。

為什么內(nèi)部調(diào)用會(huì)失效?

Spring @Async 內(nèi)部調(diào)用失效問(wèn)題:五種解決方案實(shí)戰(zhàn)分析_TaskExecutor_02

內(nèi)部調(diào)用失效的核心原因是:Spring 的 AOP 是基于代理實(shí)現(xiàn)的,而內(nèi)部方法調(diào)用會(huì)繞過(guò)代理機(jī)制

當(dāng)你在一個(gè)類中直接調(diào)用同一個(gè)類的方法時(shí)(即使用this.method()或簡(jiǎn)單的method()),這種調(diào)用是通過(guò) Java 的常規(guī)方法調(diào)用機(jī)制直接執(zhí)行的,完全繞過(guò)了 Spring 創(chuàng)建的代理對(duì)象。沒(méi)有經(jīng)過(guò)代理,@Async 注解就無(wú)法被識(shí)別和處理,因此方法會(huì)按普通方法同步執(zhí)行。

從源碼角度看,Spring 通過(guò)AsyncAnnotationBeanPostProcessor處理帶有@Async 注解的方法,創(chuàng)建代理對(duì)象。當(dāng)方法調(diào)用經(jīng)過(guò)代理時(shí),代理會(huì)檢測(cè)注解并將任務(wù)提交給配置的TaskExecutor(Spring 用于執(zhí)行異步任務(wù)的核心接口,提供線程池管理等功能)。內(nèi)部調(diào)用直接執(zhí)行原始方法,根本不經(jīng)過(guò)這個(gè)處理流程。

五種解決方案

方案 1:自我注入(Self-Injection)

最簡(jiǎn)單的方法是在類中注入自己:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;

@Service
public class NotificationService {

    @Autowired
    private NotificationService self;  // 注入自己的代理對(duì)象

    public void notifyAll(List<User> users, String message) {
        System.out.println("開(kāi)始通知所有用戶... 當(dāng)前線程: " + Thread.currentThread().getName());

        for (User user : users) {
            // 通過(guò)自注入的引用調(diào)用@Async方法
            self.sendNotification(user, message);  // 現(xiàn)在是異步調(diào)用!
        }

        System.out.println("通知流程初始化完成!");  // 立即執(zhí)行,不等待通知完成
    }

    @Async
    public void sendNotification(User user, String message) {
        // 實(shí)現(xiàn)同前...
    }
}

工作原理:當(dāng) Spring 注入self字段時(shí),它實(shí)際上注入的是一個(gè)代理對(duì)象,而不是原始對(duì)象。通過(guò)代理調(diào)用方法,確保@Async 注解能被正確處理。

優(yōu)點(diǎn)

  • 實(shí)現(xiàn)簡(jiǎn)單,僅需添加一個(gè)自引用字段,無(wú)需修改方法邏輯
  • 不改變?cè)械念惤Y(jié)構(gòu)

缺點(diǎn)

  • 可能導(dǎo)致循環(huán)依賴問(wèn)題(不過(guò) Spring 通常能處理這類循環(huán)依賴)
  • 代碼看起來(lái)可能有點(diǎn)奇怪,自注入不是一種常見(jiàn)模式
  • 如果服務(wù)類需要序列化,代理對(duì)象可能導(dǎo)致序列化問(wèn)題

方案 2:使用 ApplicationContext 獲取代理對(duì)象

通過(guò) Spring 的 ApplicationContext 手動(dòng)獲取代理對(duì)象:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import java.util.List;

@Service
public class NotificationService {

    @Autowired
    private ApplicationContext applicationContext;

    public void notifyAll(List<User> users, String message) {
        System.out.println("開(kāi)始通知所有用戶... 當(dāng)前線程: " + Thread.currentThread().getName());

        // 獲取代理對(duì)象
        NotificationService proxy = applicationContext.getBean(NotificationService.class);

        for (User user : users) {
            // 通過(guò)代理對(duì)象調(diào)用@Async方法
            proxy.sendNotification(user, message);  // 異步調(diào)用成功
        }

        System.out.println("通知流程初始化完成!");
    }

    @Async
    public void sendNotification(User user, String message) {
        // 實(shí)現(xiàn)同前...
    }
}

工作原理:從 ApplicationContext 獲取的 bean 總是代理對(duì)象(如果應(yīng)該被代理的話)。通過(guò)這個(gè)代理調(diào)用方法會(huì)觸發(fā)所有 AOP 切面,包括@Async。

優(yōu)點(diǎn)

  • 清晰明了,顯式獲取代理對(duì)象
  • 不需要添加額外的字段

缺點(diǎn)

  • 增加了對(duì) ApplicationContext 的依賴
  • 每次調(diào)用前都需要獲取 bean,略顯冗余

方案 3:使用 AopContext 獲取代理對(duì)象

利用 Spring AOP 提供的工具類獲取當(dāng)前代理:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.aop.framework.AopContext;
import java.util.List;

@Configuration
@EnableAsync
@EnableAspectJAutoProxy(exposeProxy = true)  // 重要:暴露代理對(duì)象
public class AsyncConfig {
    // 異步配置...
}

@Service
public class NotificationService {

    public void notifyAll(List<User> users, String message) {
        System.out.println("開(kāi)始通知所有用戶... 當(dāng)前線程: " + Thread.currentThread().getName());

        // 獲取當(dāng)前代理對(duì)象
        NotificationService proxy = (NotificationService) AopContext.currentProxy();

        for (User user : users) {
            // 通過(guò)代理對(duì)象調(diào)用@Async方法
            proxy.sendNotification(user, message);  // 異步調(diào)用成功
        }

        System.out.println("通知流程初始化完成!");
    }

    @Async
    public void sendNotification(User user, String message) {
        // 實(shí)現(xiàn)同前...
    }
}

工作原理:Spring AOP 提供了AopContext.currentProxy()方法來(lái)獲取當(dāng)前的代理對(duì)象。調(diào)用方法時(shí),使用這個(gè)代理對(duì)象而不是this

注意事項(xiàng):必須在配置中設(shè)置@EnableAspectJAutoProxy(exposeProxy = true)來(lái)暴露代理對(duì)象,否則會(huì)拋出異常。

優(yōu)點(diǎn)

  • 無(wú)需注入其他對(duì)象
  • 代碼清晰,直接使用 AOP 上下文

缺點(diǎn)

  • 需要顯式配置exposeProxy = true
  • 依賴 Spring AOP 的特定 API

方案 4:拆分為單獨(dú)的服務(wù)類

將異步方法拆分到單獨(dú)的服務(wù)類中:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;

@Service
public class AsyncNotificationService {

    @Async
    public void sendNotification(User user, String message) {
        // 模擬耗時(shí)操作
        try {
            System.out.println("正在發(fā)送通知給" + user.getName() +
                    "... 當(dāng)前線程: " + Thread.currentThread().getName());
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

@Service
public class NotificationService {

    @Autowired
    private AsyncNotificationService asyncService;

    public void notifyAll(List<User> users, String message) {
        System.out.println("開(kāi)始通知所有用戶... 當(dāng)前線程: " + Thread.currentThread().getName());

        for (User user : users) {
            // 調(diào)用專門的異步服務(wù)
            asyncService.sendNotification(user, message);  // 正常異步調(diào)用
        }

        System.out.println("通知流程初始化完成!");
    }
}

工作原理:將需要異步執(zhí)行的方法移動(dòng)到專門的服務(wù)類中,然后通過(guò)依賴注入使用這個(gè)服務(wù)。這樣,調(diào)用總是通過(guò) Spring 代理對(duì)象進(jìn)行的。

優(yōu)點(diǎn)

  • 符合單一職責(zé)原則,代碼組織更清晰
  • 避免了所有與代理相關(guān)的問(wèn)題
  • 可以更好地對(duì)異步操作進(jìn)行組織和管理
  • 更符合依賴倒置原則,便于單元測(cè)試和模擬測(cè)試

缺點(diǎn)

  • 需要?jiǎng)?chuàng)建額外的類
  • 可能導(dǎo)致類的數(shù)量增加

方案 5:手動(dòng)使用 TaskExecutor

完全放棄@Async 注解,手動(dòng)使用 Spring 的 TaskExecutor:

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import java.util.List;
import java.util.concurrent.CompletableFuture;

@Service
public class NotificationService {

    @Autowired
    private TaskExecutor taskExecutor;  // Spring提供的任務(wù)執(zhí)行器接口

    public void notifyAll(List<User> users, String message) {
        System.out.println("開(kāi)始通知所有用戶... 當(dāng)前線程: " + Thread.currentThread().getName());

        for (User user : users) {
            // 手動(dòng)提交任務(wù)到執(zhí)行器
            taskExecutor.execute(() -> {
                sendNotification(user, message);  // 異步執(zhí)行
            });

            // 如需獲取返回值,可以使用CompletableFuture
            CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
                return sendNotificationWithResult(user, message);
            }, taskExecutor);

            // 非阻塞處理結(jié)果
            future.thenAccept(result -> {
                System.out.println("通知結(jié)果: " + result);
            });

            // 鏈?zhǔn)讲僮魇纠恨D(zhuǎn)換結(jié)果并組合多個(gè)異步操作
            CompletableFuture<Integer> processedFuture = future
                .thenApply(result -> result.length())  // 轉(zhuǎn)換結(jié)果
                .thenCombine(  // 組合另一個(gè)異步操作
                    CompletableFuture.supplyAsync(() -> user.getName().length()),
                    (len1, len2) -> len1 + len2
                );

            // 非阻塞異常處理
            processedFuture.exceptionally(ex -> {
                System.err.println("處理失敗: " + ex.getMessage());
                return -1;
            });
        }

        System.out.println("通知流程初始化完成!");
    }

    // 注意:不再需要@Async注解
    public void sendNotification(User user, String message) {
        // 實(shí)現(xiàn)同前...
    }

    public String sendNotificationWithResult(User user, String message) {
        // 返回通知結(jié)果
        return "已通知" + user.getName();
    }
}

工作原理:直接使用 Spring 的 TaskExecutor 提交任務(wù),完全繞過(guò) AOP 代理機(jī)制。

優(yōu)點(diǎn)

  • 完全控制異步執(zhí)行的方式和時(shí)機(jī)
  • 不依賴 AOP 代理,更直接和透明
  • 可以更細(xì)粒度地控制任務(wù)執(zhí)行(如添加超時(shí)、錯(cuò)誤處理等)
  • 支持靈活的返回值處理,結(jié)合 CompletableFuture 實(shí)現(xiàn)非阻塞編程
  • 支持復(fù)雜的異步編排(如鏈?zhǔn)讲僮鳌⒔M合多個(gè)異步任務(wù))

缺點(diǎn)

  • 失去了@Async 的聲明式便利性
  • 需要更多的手動(dòng)編碼
  • 需要移除@Async 注解,修改方法簽名和調(diào)用邏輯,代碼侵入性高

針對(duì)返回值的異步方法

如果你的@Async 方法有返回值,它應(yīng)該返回FutureCompletableFuture。在處理內(nèi)部調(diào)用時(shí),上述解決方案同樣適用:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;

// 示例業(yè)務(wù)類
class ReportRequest {
    private String id;

    // 默認(rèn)構(gòu)造器
    public ReportRequest() {}

    public ReportRequest(String id) { this.id = id; }
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
}

class Report {
    private String id;
    private String content;

    // 默認(rèn)構(gòu)造器
    public Report() {}

    public Report(String id, String content) {
        this.id = id;
        this.content = content;
    }
}

@Service
public class ReportService {

    @Autowired
    private ReportService self;  // 使用方案1:自我注入

    public void generateReports(List<ReportRequest> requests) {
        List<CompletableFuture<Report>> futures = new ArrayList<>();

        for (ReportRequest request : requests) {
            // 通過(guò)代理調(diào)用返回CompletableFuture的異步方法
            CompletableFuture<Report> future = self.generateReport(request);
            futures.add(future);
        }

        // 等待所有報(bào)告生成完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        // 處理結(jié)果
        for (CompletableFuture<Report> future : futures) {
            Report report = future.join();
            // 處理報(bào)告...
        }
    }

    @Async
    public CompletableFuture<Report> generateReport(ReportRequest request) {
        // 模擬耗時(shí)的報(bào)告生成
        try {
            System.out.println("生成報(bào)告中... 當(dāng)前線程: " + Thread.currentThread().getName());
            Thread.sleep(2000);
            Report report = new Report(request.getId(), "報(bào)告內(nèi)容...");
            return CompletableFuture.completedFuture(report);
        } catch (Exception e) {
            CompletableFuture<Report> future = new CompletableFuture<>();
            future.completeExceptionally(e);
            return future;
        }
    }
}

異常處理與實(shí)踐建議

異步方法的異常處理需要特別注意:異步執(zhí)行的方法拋出的異常不會(huì)傳播到調(diào)用方,因?yàn)楫惓0l(fā)生在不同的線程中。

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import org.springframework.scheduling.annotation.AsyncResult;

@Service
public class RobustNotificationService {

    @Autowired
    private RobustNotificationService self;
    private static final Logger logger = LoggerFactory.getLogger(RobustNotificationService.class);

    public void notifyAll(List<User> users, String message) {
        for (User user : users) {
            // 錯(cuò)誤:無(wú)法捕獲異步方法的異常,因?yàn)楫惓0l(fā)生在另一個(gè)線程
            // try {
            //     self.sendNotification(user, message);
            // } catch (Exception e) {
            //     logger.error("Failed to send notification to user: " + user.getId(), e);
            // }

            // 正確方式1:使用全局異常處理器(在AsyncConfigurer中配置)
            self.sendNotification(user, message);

            // 正確方式2:如果方法返回Future,可以通過(guò)future捕獲異常
            Future<"通知發(fā)送失敗: " + user.getName(), e);
                // 處理失敗情況
            }

            // 正確方式3:使用CompletableFuture的異常處理
            CompletableFuture<Void> cf = self.sendNotificationWithCompletableFuture(user, message);
            cf.exceptionally(ex -> {
                logger.error("通知發(fā)送失敗: " + user.getName(), ex);
                return null;
            });
        }
    }

    @Async
    public void sendNotification(User user, String message) {
        try {
            // 通知邏輯...
            if (user.getName() == null) {
                throw new RuntimeException("用戶名不能為空");
            }
        } catch (Exception e) {
            // 記錄詳細(xì)的異常信息,但異常不會(huì)傳播到調(diào)用方
            logger.error("通知失敗: " + user.getName(), e);
            // 異常會(huì)被AsyncUncaughtExceptionHandler處理(如果配置了)
            throw e;
        }
    }

    @Async
    public Future<Void> sendNotificationWithFuture(User user, String message) {
        // 實(shí)現(xiàn)邏輯...
        return new AsyncResult<>(null);
    }

    @Async
    public CompletableFuture<Void> sendNotificationWithCompletableFuture(User user, String message) {
        // 實(shí)現(xiàn)邏輯...
        return CompletableFuture.completedFuture(null);
    }
}

實(shí)踐建議

  1. 合理配置線程池:默認(rèn)情況下,Spring 使用SimpleAsyncTaskExecutor,每次調(diào)用都會(huì)創(chuàng)建新線程,這在生產(chǎn)環(huán)境中是不可接受的。應(yīng)配置適當(dāng)?shù)木€程池:
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);       // 核心線程數(shù)
        executor.setMaxPoolSize(10);       // 最大線程數(shù)
        executor.setQueueCapacity(25);     // 隊(duì)列容量
        executor.setThreadNamePrefix("MyAsync-");

        // 拒絕策略:當(dāng)隊(duì)列滿且線程數(shù)達(dá)到最大時(shí)的處理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 允許核心線程超時(shí),適用于負(fù)載波動(dòng)的場(chǎng)景
        executor.setAllowCoreThreadTimeOut(true);

        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}
  1. 適當(dāng)使用超時(shí)控制:對(duì)于需要獲取結(jié)果的異步方法,添加超時(shí)控制,但要注意阻塞問(wèn)題:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

// 阻塞式超時(shí)控制(慎用,會(huì)阻塞當(dāng)前線程)
CompletableFuture<Report> future = reportService.generateReport(request);
try {
    Report report = future.get(30, TimeUnit.SECONDS); // 設(shè)置30秒超時(shí)
} catch (TimeoutException e) {
    logger.error("報(bào)告生成超時(shí)", e);
    // 處理超時(shí)情況
}

// 更好的非阻塞方式:
future.orTimeout(30, TimeUnit.SECONDS)
      .thenAccept(report -> processReport(report))
      .exceptionally(ex -> {
          if (ex instanceof TimeoutException) {
              logger.error("報(bào)告生成超時(shí)");
          } else {
              logger.error("報(bào)告生成失敗", ex);
          }
          return null;
      });
  1. 慎用方案選擇
  • 對(duì)于簡(jiǎn)單場(chǎng)景,自我注入(方案 1)最簡(jiǎn)單直接
  • 對(duì)于復(fù)雜業(yè)務(wù)邏輯,拆分服務(wù)(方案 4)是更好的架構(gòu)選擇
  • 如果需要細(xì)粒度控制,直接使用 TaskExecutor(方案 5)是最靈活的選擇
  1. 注意事務(wù)傳播: 異步方法執(zhí)行在單獨(dú)的線程中,會(huì)導(dǎo)致事務(wù)傳播行為失效。Spring 的事務(wù)上下文通過(guò)ThreadLocal與當(dāng)前線程綁定,異步方法在新線程中執(zhí)行時(shí),無(wú)法訪問(wèn)調(diào)用方的ThreadLocal數(shù)據(jù),因此必須在異步方法上單獨(dú)聲明@Transactional以創(chuàng)建新事務(wù)。
@Service
public class TransactionService {

    @Autowired
    private TransactionService self;

    @Transactional
    public void saveWithTransaction(Entity entity) {
        // 事務(wù)操作...

        // 錯(cuò)誤:異步方法在新線程中執(zhí)行,當(dāng)前事務(wù)不會(huì)傳播
        self.asyncOperation(entity); // 不會(huì)共享當(dāng)前事務(wù)
    }

    @Async
    @Transactional // 必須單獨(dú)添加事務(wù)注解,會(huì)創(chuàng)建新的事務(wù)
    public void asyncOperation(Entity entity) {
        // 此方法將有自己的事務(wù),而非繼承調(diào)用方的事務(wù)
    }
}
  1. 驗(yàn)證異步執(zhí)行
// 在測(cè)試類中驗(yàn)證異步執(zhí)行
@SpringBootTest
public class AsyncServiceTest {

    @Autowired
    private NotificationService service;

    @Test
    public void testAsyncExecution() throws Exception {
        // 記錄主線程名稱
        String mainThread = Thread.currentThread().getName();

        // 保存異步線程名稱
        final String[] asyncThread = new String[1];
        CountDownLatch latch = new CountDownLatch(1);

        User user = new User();
        user.setName("TestUser");

        // 重寫異步方法以捕獲線程名稱
        service.sendNotificationWithCompletableFuture(user, "test")
               .thenAccept(v -> {
                   asyncThread[0] = Thread.currentThread().getName();
                   latch.countDown();
               });

        // 等待異步操作完成
        latch.await(5, TimeUnit.SECONDS);

        // 驗(yàn)證線程不同
        assertThat(mainThread).isNotEqualTo(asyncThread[0]);
        assertThat(asyncThread[0]).startsWith("MyAsync-");
    }
}

五種方案對(duì)比

Spring @Async 內(nèi)部調(diào)用失效問(wèn)題:五種解決方案實(shí)戰(zhàn)分析_TaskExecutor_03

總結(jié)

解決方案

實(shí)現(xiàn)復(fù)雜度

代碼侵入性

額外依賴

架構(gòu)清晰度

適用場(chǎng)景

自我注入

(僅添加一個(gè)自注入字段,無(wú)方法邏輯修改)

無(wú)

簡(jiǎn)單項(xiàng)目,快速解決

ApplicationContext

ApplicationContext

需要明確控制代理獲取

AopContext

需開(kāi)啟 exposeProxy

不想增加依賴字段

拆分服務(wù)

無(wú)

大型項(xiàng)目,關(guān)注點(diǎn)分離

手動(dòng) TaskExecutor

(需修改方法注解和調(diào)用邏輯)

TaskExecutor

需要精細(xì)控制異步執(zhí)行

需靈活處理返回值

需要復(fù)雜異步編排

相關(guān)文章

  • Java 實(shí)現(xiàn)完整功能的學(xué)生管理系統(tǒng)實(shí)例

    Java 實(shí)現(xiàn)完整功能的學(xué)生管理系統(tǒng)實(shí)例

    讀萬(wàn)卷書不如行萬(wàn)里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java實(shí)現(xiàn)一個(gè)完整版學(xué)生管理系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平
    2021-11-11
  • 登錄EasyConnect后無(wú)法通過(guò)jdbc訪問(wèn)服務(wù)器數(shù)據(jù)庫(kù)問(wèn)題的解決方法

    登錄EasyConnect后無(wú)法通過(guò)jdbc訪問(wèn)服務(wù)器數(shù)據(jù)庫(kù)問(wèn)題的解決方法

    描述一下近期使用EasyConnect遇到的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于登錄EasyConnect后無(wú)法通過(guò)jdbc訪問(wèn)服務(wù)器數(shù)據(jù)庫(kù)問(wèn)題的解決方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • java常用工具類 XML工具類、數(shù)據(jù)驗(yàn)證工具類

    java常用工具類 XML工具類、數(shù)據(jù)驗(yàn)證工具類

    這篇文章主要為大家詳細(xì)介紹了java常用工具類,包括XML工具類、數(shù)據(jù)驗(yàn)證工具類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • java打jar包的幾種方式詳解

    java打jar包的幾種方式詳解

    這篇文章主要介紹了java打jar包的幾種方式,本文分步驟給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-11-11
  • SpringBoot中忽略實(shí)體類中的某個(gè)屬性不返回給前端的方法(示例詳解)

    SpringBoot中忽略實(shí)體類中的某個(gè)屬性不返回給前端的方法(示例詳解)

    本文介紹了在Spring Boot中使用Jackson和Fastjson忽略實(shí)體類屬性不返回給前端的方法,在Jackson中,同時(shí)使用@JsonProperty和@JsonIgnore時(shí),@JsonIgnore可能失效,Fastjson中可以使用@JSONField(serialize=false)來(lái)實(shí)現(xiàn),本文結(jié)合實(shí)例代碼介紹的非常詳細(xì),需要的朋友參考下吧
    2024-11-11
  • Java實(shí)現(xiàn)簡(jiǎn)易五子棋小游戲

    Java實(shí)現(xiàn)簡(jiǎn)易五子棋小游戲

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)易五子棋小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • 在Spring中使用Knife4j進(jìn)行API文檔生成與管理的操作方法

    在Spring中使用Knife4j進(jìn)行API文檔生成與管理的操作方法

    Knife4j 是為Java MVC 框架(如Spring Boot、Spring MVC等)集成 Swagger 生成 API 文檔的增強(qiáng)解決方案,它基于 Swagger 的核心功能,通過(guò)定制化的前端界面和一些額外的特性,本文介紹了在Spring中使用Knife4j進(jìn)行API文檔生成與管理的操作方法,需要的朋友可以參考下
    2024-12-12
  • spring如何解決循環(huán)依賴問(wèn)題

    spring如何解決循環(huán)依賴問(wèn)題

    Spring在單例模式下用三級(jí)緩存設(shè)計(jì)解決setter方法注入bean屬性循環(huán)依賴問(wèn)題,但無(wú)法解決多例Bean和構(gòu)造方法注入?yún)?shù)的循環(huán)依賴,三級(jí)緩存通過(guò)A、B兩對(duì)象互相注入屬性的過(guò)程解決循環(huán)依賴,其中,構(gòu)造方法的循環(huán)依賴無(wú)法解決是因?yàn)閯?chuàng)建對(duì)象會(huì)走構(gòu)造方法
    2024-10-10
  • java的Arrays工具類實(shí)戰(zhàn)

    java的Arrays工具類實(shí)戰(zhàn)

    java.util.Arrays類能方便地操作數(shù)組,它提供的所有方法都是靜態(tài)的。Arrays作為一個(gè)工具類,能很好的操作數(shù)組。下面介紹主要使用的幾個(gè)函數(shù)
    2016-12-12
  • 解析Hibernate + MySQL中文亂碼問(wèn)題

    解析Hibernate + MySQL中文亂碼問(wèn)題

    如果持久化的類中有包括了漢字的String對(duì)象,那么對(duì)應(yīng)到數(shù)據(jù)庫(kù)中漢字的部分就會(huì)是亂碼。這主要是由于MySQL數(shù)據(jù)表的字符集與我們當(dāng)前使用的本地字符集不相同造成的
    2013-07-07

最新評(píng)論