New 的對(duì)象也能被 Spring 注入?AspectJ LTW 打破限制!
環(huán)境:SpringBoot3.4.2
1. 簡(jiǎn)介
在傳統(tǒng) Spring 應(yīng)用中,依賴注入(DI)僅限于由 Spring 容器管理的 Bean。然而,在實(shí)際開發(fā)中,常遇到需通過 new 關(guān)鍵字直接創(chuàng)建對(duì)象的場(chǎng)景,如領(lǐng)域?qū)嶓w、DTO 轉(zhuǎn)換、工廠模式或第三方庫(kù)回調(diào)。這些對(duì)象脫離了 Spring 容器的生命周期,無(wú)法使用 @Autowired 注入服務(wù),導(dǎo)致業(yè)務(wù)邏輯與數(shù)據(jù)訪問耦合,難以測(cè)試與維護(hù)。
為此,Spring 提供了基于 AspectJ 加載期織入(LTW)的解決方案。通過 @Configurable 注解與 META-INF/aop.xml 配置,結(jié)合 JVM -javaagent 機(jī)制,可在類加載時(shí)動(dòng)態(tài)織入依賴注入邏輯,使 new 出的對(duì)象也能自動(dòng)裝配 Spring Bean。該技術(shù)突破容器邊界,實(shí)現(xiàn)了真正意義上的全鏈路依賴注入,為復(fù)雜場(chǎng)景下的對(duì)象管理提供了強(qiáng)大支持。
本篇文章會(huì)詳細(xì)介紹LTW技術(shù)的完整實(shí)現(xiàn)過程。
2.實(shí)戰(zhàn)案例
2.1 引入Aspect依賴
要啟用基于 AspectJ 的配置,需要添加 AspectJ Weaver 依賴項(xiàng)。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>引入 spring-aspects 依賴以支持 @Configurable 等注解的織入,提供 AspectJ 與 Spring 集成的切面實(shí)現(xiàn),是實(shí)現(xiàn)加載期織入(LTW)和自動(dòng)依賴注入的基礎(chǔ)。
2.2 開啟非管理Bean注入功能
@SpringBootApplication
@EnableSpringConfigured
@EnableLoadTimeWeaving
public class App {
}- @EnableSpringConfigured:注解向當(dāng)前應(yīng)用上下文發(fā)出信號(hào),以將依賴注入應(yīng)用于那些在Spring Bean工廠之外實(shí)例化的非托管類(通常是使用@Configurable注解標(biāo)注的類)。
- @EnableLoadTimeWeaving:注解的作用是啟用 Spring 的加載期織入(Load-Time Weaving, LTW)機(jī)制,允許在類加載時(shí)由 AspectJ 對(duì)字節(jié)碼進(jìn)行增強(qiáng),從而實(shí)現(xiàn)對(duì) new 創(chuàng)建的對(duì)象(如標(biāo)記了 @Configurable 的類)進(jìn)行依賴注入,突破 Spring 容器的管理邊界。
2.3 定義不受Spring管理的Bean
@Configurable
public class CommonService {
@Resource
private UserService userService ;
public void getUser() {
this.userService.query() ;
}
}@Configurable 注解指示 Spring 應(yīng)通過 AspectJ 加載期織入(LTW)機(jī)制,對(duì)該類的實(shí)例進(jìn)行依賴注入。在該示例中,CommonService 類可以通過 new 關(guān)鍵字創(chuàng)建,并借助 @Configurable,Spring 仍能為其注入所需的 Bean。該類中通過 @Resource 注解注入了由 Spring 容器管理的 UserService Bean,使得即使脫離容器直接實(shí)例化,也能正常使用 userService.query() 等業(yè)務(wù)方法,實(shí)現(xiàn)與 Spring 管理對(duì)象的無(wú)縫集成。
定義UserService受管理的Bean
@Service
public class UserService {
public void query() {
System.err.println("查詢用戶信息...") ;
}
}2.4 測(cè)試new創(chuàng)建CommonService
接下來,我們直接通過定義Runner進(jìn)行測(cè)試,通過new創(chuàng)建CommonService后測(cè)試是否可以正確的注入U(xiǎn)serService。
@Component
public class TestRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
CommonService cs = new CommonService() ;
cs.getUser() ;
}
}2.5 啟用AspectJ織入
要使 @EnableSpringConfigured 生效,必須啟用 AspectJ 織入。這可以在運(yùn)行時(shí)或編譯時(shí)完成。
運(yùn)行時(shí)織入,添加 spring-instrument 依賴,并將 AspectJ 織入器作為 Java Agent 啟用。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
</dependency>2.6 啟動(dòng)應(yīng)用測(cè)試
在完成以上步驟后,啟動(dòng)應(yīng)用,你將看到如下的錯(cuò)誤:
圖片
錯(cuò)誤信息非常明確的告訴你還需要使用-javaagent:spring-instrument-xxx.jar參數(shù)。
-javaagent:D:\java\maven\org\springframework\spring-instrument\6.2.7\spring-instrument-6.2.7.jar再次啟動(dòng)應(yīng)用,這次不會(huì)再有錯(cuò)誤,但是將有一堆如下信息:
圖片
一堆error日志,并且我們程序也正確的運(yùn)行了。
解決error日志
我們可以在META-INF下新建aop.xml文件,進(jìn)行相關(guān)的配置,如下:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-Xlint:ignore">
<include within="com.pack..*"/>
</weaver>
</aspectj>- <weaver optinotallow="-Xlint:ignore">:?jiǎn)⒂?AspectJ 織入器,并忽略所有織入過程中的警告信息(如無(wú)法找到類、注解等),避免日志污染,但需確保忽略不會(huì)掩蓋關(guān)鍵問題。
- <include within="com.pack..*"/>:指定僅對(duì) com.pack 包及其子包下的類進(jìn)行織入處理,提高性能并避免對(duì)無(wú)關(guān)類(如第三方庫(kù))進(jìn)行不必要的織入。
2.7 添加增強(qiáng)功能
我們已經(jīng)使用了 META-INF/aop.xml 文件,但它不僅僅用于簡(jiǎn)單配置(如忽略警告信息),還可以在其中定義切面織入規(guī)則,實(shí)現(xiàn)對(duì)目標(biāo)類的 AOP 增強(qiáng),從而擴(kuò)展功能。
新建切面
@Aspect
public class ProfilingAspect {
@Around("methodsToBeProfiled()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
StopWatch sw = new StopWatch(getClass().getSimpleName());
try {
sw.start(pjp.getSignature().getName());
return pjp.proceed();
} finally {
sw.stop();
System.err.println(sw.prettyPrint());
}
}
@Pointcut("execution(public * com.pack..*.*(..))")
public void methodsToBeProfiled() {
}
}修改aop.xml文件
<aspectj>
<weaver options="-Xlint:ignore">
<include within="com.pack..*"/>
</weaver>
<aspects>
<aspect name="com.pack.mgr.aspect.ProfilingAspect"/>
</aspects>
</aspectj>再次啟動(dòng)應(yīng)用,控制臺(tái)輸出:

圖片
























