強大!SpringBoot通過三種方式實現(xiàn)AOP切面,第三種方式性能極佳
環(huán)境:SpringBoot2.7.18
1. 簡介
Spring框架通過多種機制增強代碼功能,實現(xiàn)切面編程(AOP)。核心之一是通過動態(tài)代理技術(shù),在運行時為Bean織入(動態(tài)代理)額外功能(如日志、安全等),無需修改源代碼。此外,利用Java Agent技術(shù)(如AspectJ Weaver),可以在JVM層面攔截類加載過程,動態(tài)修改類字節(jié)碼,從而實現(xiàn)更廣泛的AOP支持。最后,AspectJ-Maven-Plugin編譯插件在編譯時直接修改源代碼或字節(jié)碼,確保切面邏輯與業(yè)務代碼無縫集成,優(yōu)化了性能并減少了運行時開銷。這些技術(shù)使得開發(fā)者能更靈活地管理橫切關(guān)注點,提升代碼模塊性和可維護性。接下來我們將詳細的介紹這3種AOP實現(xiàn)的方式。
2. 實戰(zhàn)案例
2.1 準備環(huán)境
Service類
@Service
public class UserService {
public void save() {
System.out.println("save...") ;
}
}
接下來的所有示例都將圍繞著上面這個Service。
運行測試類
SpringApplication app = new SpringApplication(AppApplication.class) ;
app.setWebApplicationType(WebApplicationType.NONE) ;
ConfigurableApplicationContext context = app.run(args) ;
UserService us = context.getBean(UserService.class) ;
us.save() ;
啟動測試類
2.2 代理方式
該種方式,是我們工作中用的最為普遍的方式,因為該種方式靈活,無需修改代碼,適用于運行時切面增強,易于理解和集成。如下示例:
定義切面
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.pack..*Service.*(..))")
private void log() {}
@Before("log()")
public void before(JoinPoint jp) {
System.out.println("before, " + jp.getSignature()) ;
}
}
動態(tài)代理方式,只需要定義上面的切面Bean類。
運行測試代碼,輸出結(jié)果
before, void com.pack.aop.agent.UserService.save()
save...
通過動態(tài)代理方式,只需要在項目中定義@Aspect切面即可完成增強邏輯。我們將獲取的UserService Class打印如下:
圖片
通過CGLIB生成了代理類。
2.3 Agent增強方式
該種方式是在JVM層面攔截,支持更廣泛的AOP場景,性能優(yōu)化潛力大(相比較于上面代理方式)。要實現(xiàn)這種方式,我們首先需要定義aop.xml文件(META-INF中)
<aspectj>
<weaver>
<!-- 對哪些類進行增強 -->
<include within="com.pack.aop.agent..*" />
</weaver>
<!-- 定義切面類,可以定義多個 -->
<aspects>
<aspect name="com.pack.aop.agent.LogAspect" />
</aspects>
</aspectj>
接下來就運行時還需要配置jvm運行時參數(shù)
-javaagent:d:/maven/org/aspectj/aspectjweaver/1.9.7/aspectjweaver-1.9.7.jar
注:這里的版本最后根據(jù)你當前環(huán)境的版本來指定。
運行測試代碼:
圖片
我們的業(yè)務代碼被增強了,同時UserService并沒有創(chuàng)建代理。通過反編譯查看UserService。
圖片
編譯后的字節(jié)碼也沒有任何的變化。Agent的原理就在進行類加載時對類進行增強。
2.4 編譯插件方式
動態(tài)代理的方式,通過對目標類生成代理,在執(zhí)行目標方法前執(zhí)行增強邏輯Advice,這種方式多少對性能是有影響的。而編譯插件方式是在編譯時增強,性能最佳,深度集成,減少運行時開銷。
添加maven插件
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
<skip>true</skip>
</configuration>
<executions>
<execution>
<configuration>
<skip>false</skip>
</configuration>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
接下來我們可以將LogAspect類上的@Component注解刪除了,現(xiàn)在不需要了。重新編譯項目
mvn clean compile
再次運行測試代碼;
圖片
我們的代碼同樣被增強了,同時打印了UserService類,該類并沒有被代理。反編譯該類。
圖片
通過反編譯得知,在編譯階段就對我們的代碼進行了增強。這也是此種方式性能最佳的原因。
總結(jié):以上三種方式增強代碼:動態(tài)代理靈活輕量,運行時織入;Java Agent在JVM層面攔截類加載,支持廣泛AOP場景,性能優(yōu)化潛力大但配置相對復雜;AspectJ-Maven-Plugin編譯時修改字節(jié)碼,減少運行時開銷,支持復雜邏輯但需重新編譯。