Spring @Async 注解異步調(diào)用失效的五種解決方案
給一個(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 通過(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ì)失效?
內(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)該返回Future
或CompletableFuture
。在處理內(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í)踐建議:
- 合理配置線程池:默認(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(); } }
- 適當(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; });
- 慎用方案選擇:
- 對(duì)于簡(jiǎn)單場(chǎng)景,自我注入(方案 1)最簡(jiǎn)單直接
- 對(duì)于復(fù)雜業(yè)務(wù)邏輯,拆分服務(wù)(方案 4)是更好的架構(gòu)選擇
- 如果需要細(xì)粒度控制,直接使用 TaskExecutor(方案 5)是最靈活的選擇
- 注意事務(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ù) } }
- 驗(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ì)比
總結(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í)例
讀萬(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èn)題,下面這篇文章主要給大家介紹了關(guān)于登錄EasyConnect后無(wú)法通過(guò)jdbc訪問(wèn)服務(wù)器數(shù)據(jù)庫(kù)問(wèn)題的解決方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02java常用工具類 XML工具類、數(shù)據(jù)驗(yàn)證工具類
這篇文章主要為大家詳細(xì)介紹了java常用工具類,包括XML工具類、數(shù)據(jù)驗(yàn)證工具類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05SpringBoot中忽略實(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-11Java實(shí)現(xiàn)簡(jiǎn)易五子棋小游戲
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)易五子棋小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05在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解析Hibernate + MySQL中文亂碼問(wèn)題
如果持久化的類中有包括了漢字的String對(duì)象,那么對(duì)應(yīng)到數(shù)據(jù)庫(kù)中漢字的部分就會(huì)是亂碼。這主要是由于MySQL數(shù)據(jù)表的字符集與我們當(dāng)前使用的本地字符集不相同造成的2013-07-07