解析SpringBoot中使用LoadTimeWeaving技術(shù)實(shí)現(xiàn)AOP功能
1.SpringBoot AOP功能
1.1 LTW與不同的切面織入時(shí)機(jī)
AOP——面向切面編程,通過(guò)為目標(biāo)類織入切面的方式,實(shí)現(xiàn)對(duì)目標(biāo)類功能的增強(qiáng)。按切面被織如到目標(biāo)類中的時(shí)間劃分,主要有以下幾種:
- 1.運(yùn)行期織入
這是最常見(jiàn)的,比如在運(yùn)行期通過(guò)為目標(biāo)類生成動(dòng)態(tài)代理的方式實(shí)現(xiàn)AOP就屬于運(yùn)行期織入,這也是Spring AOP中的默認(rèn)實(shí)現(xiàn),并且提供了兩種創(chuàng)建動(dòng)態(tài)代理的方式:JDK自帶的針對(duì)接口的動(dòng)態(tài)代理和使用CGLib動(dòng)態(tài)創(chuàng)建子類的方式創(chuàng)建動(dòng)態(tài)代理。
- 2.編譯期織入
使用特殊的編譯器在編譯期將切面織入目標(biāo)類,這種比較少見(jiàn),因?yàn)樾枰厥獾木幾g器的支持。
- 3.類加載期織入
通過(guò)字節(jié)碼編輯技術(shù)在類加載期將切面織入目標(biāo)類中,這是本篇介紹的重點(diǎn)。它的核心思想是:在目標(biāo)類的class文件被JVM加載前,通過(guò)自定義類加載器或者類文件轉(zhuǎn)換器將橫切邏輯織入到目標(biāo)類的class文件中,然后將修改后class文件交給JVM加載。這種織入方式可以簡(jiǎn)稱為L(zhǎng)TW(LoadTimeWeaving)。
1.2 JDK實(shí)現(xiàn)LTW的原理
可以使用JKD的代理功能讓代理器訪問(wèn)到JVM的底層組件,借此向JVM注冊(cè)類文件轉(zhuǎn)換器,在類加載時(shí)對(duì)類文件的字節(jié)碼進(jìn)行轉(zhuǎn)換。具體而言,java.lang.instrument包下定義了ClassFileTransformer接口,該接口的作用如下面的注釋所描述
* An agent provides an implementation of this interface in order
* to transform class files.
* The transformation occurs before the class is defined by the JVM.
可以通過(guò)實(shí)現(xiàn)該接口,并重寫如下抽象方法自定義類文件轉(zhuǎn)換規(guī)則
byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException;
classfileBuffer是原始類文件對(duì)應(yīng)的字節(jié)碼數(shù)組,返回的byte[]為轉(zhuǎn)化后的字節(jié)碼數(shù)組,如果返回null,則表示不進(jìn)行字節(jié)碼處理。
而java.lang.instrument包下的Instrumentation接口則可以將我們自定義的ClassTransFormer向JVM內(nèi)部的組件進(jìn)行注冊(cè)
void addTransformer(ClassFileTransformer transformer);
在實(shí)際使用中,可以通過(guò)JVM的-javaagent代理參數(shù)在啟動(dòng)時(shí)獲取JVM內(nèi)部組件的引用,將ClassFileTransformer實(shí)例注冊(cè)到JVM中,JVM在加載Class文件時(shí),會(huì)先調(diào)用這個(gè)ClassTransformer的transform()方法對(duì)Class文件的字節(jié)碼進(jìn)行轉(zhuǎn)換,比如織入切面中定義的橫切邏輯,實(shí)現(xiàn)AOP功能。整個(gè)過(guò)程可以入下所示
1.3 如何在Spring中實(shí)現(xiàn)LTW
Spring中默認(rèn)通過(guò)運(yùn)行期生成動(dòng)態(tài)代理的方式實(shí)現(xiàn)切面的織入,實(shí)現(xiàn)AOP功能,但是Spring也可以使用LTW技術(shù)來(lái)實(shí)現(xiàn)AOP,并且提供了細(xì)粒度的控制,支持在單個(gè)ClassLoader范圍內(nèi)實(shí)施類文件轉(zhuǎn)換。
Spring中的org.springframework.instrument.classloading.LoadTimeWeaver接口定義了為類加載器添加ClassFileTransfomer的抽象
* Defines the contract for adding one or more * {@link ClassFileTransformer ClassFileTransformers} to a {@link ClassLoader}. * public interface LoadTimeWeaver {
Spring的LTW支持AspectJ定義的切面,既可以是直接使用AspectJ語(yǔ)法定義的切面,也可以是使用@AspectJ注解,通過(guò)java類定義的切面。Spring LTW通過(guò)讀取classpath下META-INF/aop.xml文件,獲取切面類和要被切面織入的目標(biāo)類的相關(guān)信息,通過(guò)LoadTimeWeaver在ClassLoader加載類文件時(shí)將切面織入目標(biāo)類中,其工作原理如下所示
Spring中可以通過(guò)LoadTimeWeaver將Spring提供的ClassFileTransformer注冊(cè)到ClassLoader中。在類加載期,注冊(cè)的ClassFileTransformer讀取類路徑下META-INF/aop.xml文件中定義的切面類和目標(biāo)類信息,在目標(biāo)類的class文件真正被VM加載前織入切面信息,生成新的Class文件字節(jié)碼,然后交給VM加載。因而之后創(chuàng)建的目標(biāo)類的實(shí)例,就已經(jīng)實(shí)現(xiàn)了AOP功能。
2. Springboot中使用LTW實(shí)現(xiàn)AOP的例子
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的AOP需求,在方法調(diào)用前后打印出開(kāi)始和結(jié)束的日志信息。
相關(guān)的maven依賴和插件
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <argLine> -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar" -javaagent:"${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar" <!-- -Dspring.profiles.active=test--> </argLine> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <agent> ${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar </agent> <agent> ${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar </agent> </configuration> </plugin> </plugins> </build>
這里通過(guò)maven插件的方式為JVM設(shè)置代理,通過(guò)-javaagent參數(shù)指定織入器類包的路徑,這樣就可以在類加載期將切面織入,更多關(guān)于javaagent的知識(shí)可以參考javaagent
織入目標(biāo)類
/** * @author: takumiCX * @create: 2018-12-19 **/ @Component public class LtwBean { public void test(){ System.out.println("process......."); } }
只有一個(gè)test()方法,通過(guò)@Componet注解向容器注冊(cè)。
切面類
/** * @author: takumiCX * @create: 2018-12-19 **/ @Aspect public class LogMethodInvokeAspect { @Pointcut("execution(public * com.takumiCX.ltw.*.*(..))") public void pointCut(){ } @Around("pointCut()") public void advise(ProceedingJoinPoint pjp) throws Throwable { Signature signature = pjp.getSignature(); System.out.println(signature+" start..... "); pjp.proceed(); System.out.println(signature+" end......"); } }
@Aspect注解表示這是一個(gè)切面類
配置類
/** * @author: takumiCX * @create: 2018-12-19 **/ @Configuration @ComponentScan("com.takumiCX.ltw") @EnableLoadTimeWeaving(aspectjWeaving=AUTODETECT) public class CustomLtwConfig{ }
通過(guò)@@EnableLoadTimeWeaving開(kāi)啟LTW功能,可以通過(guò)屬性aspectjWeaving指定LTW的開(kāi)啟策略
- ENABLED
開(kāi)啟LTW
- DISABLED
不開(kāi)啟LTW
- AUTODETECT
如果類路徑下能讀取到META-INF/aop.xml文件,則開(kāi)啟LTW,否則關(guān)閉
- 在META-INF文件夾下編寫aop.xml文件
aop.xml文件內(nèi)容
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"> <aspectj> <!--要織入切面的目標(biāo)類--> <weaver> <include within="com.takumiCX.ltw..*" /> </weaver> <!--切面類--> <aspects> <aspect name="com.takumiCX.ltw.aspect.LogMethodInvokeAspect" /> </aspects> </aspectj>
這樣我們的Spring容器就能加載該文件讀取到描述目標(biāo)類和切面類的相關(guān)信息,容器在加載目標(biāo)類的class文件到j(luò)vm之前,會(huì)將切面類中定義的增強(qiáng)邏輯織入到class文件中,真正加載到j(luò)vm中的是織入切面后的class文件,因而通過(guò)該class文件創(chuàng)建出的目標(biāo)類的實(shí)例,不需要經(jīng)過(guò)動(dòng)態(tài)代理就能實(shí)現(xiàn)AOP相關(guān)功能。
測(cè)試類
/** * @author: takumiCX * @create: 2018-12-20 **/ @RunWith(SpringRunner.class) @SpringBootTest(classes ={CustomLtwConfig.class}) public class LTWTest { @Autowired private LtwBean ltwBean; @Test public void testLTW() throws InterruptedException { ltwBean.test(); } }
最后的結(jié)果如下
方法調(diào)用前后分別記錄的開(kāi)始和結(jié)束的日志信息,說(shuō)明我們的切面成功的織入到了目標(biāo)類。但是這里可能有一個(gè)疑問(wèn),這真的是LTW(Load TimeWeaving)通過(guò)在類加載期織入切面起到的作用嗎?有沒(méi)有可能是LTW沒(méi)起作用,是Spring AOP默認(rèn)通過(guò)運(yùn)行期生成動(dòng)態(tài)代理的方式實(shí)現(xiàn)的AOP。
我們的LogMethodInvokeAspect切面類上并沒(méi)有加@Component注解向容器注冊(cè),并且配置類CustomLtwConfig上也沒(méi)有加@EnableAspectJAutoProxy注解開(kāi)啟Aspectj的運(yùn)行時(shí)動(dòng)態(tài)代理,所以這里基于動(dòng)態(tài)代理的AOP并不會(huì)生效。
為了驗(yàn)證我們的想法,將aop.xml文件刪除
重新運(yùn)行測(cè)試代碼
AOP沒(méi)起到作用,說(shuō)明剛才的AOP功能確實(shí)是通過(guò)LTW技術(shù)實(shí)現(xiàn)的。
當(dāng)我們給切面類加上@Component注解,給配置類加上@EnableAspectJAutoProxy
/** * @author: takumiCX * @create: 2018-12-19 **/ @Aspect @Component public class LogMethodInvokeAspect {
/** * @author: takumiCX * @create: 2018-12-19 **/ @Configuration @ComponentScan("com.takumiCX.ltw") @EnableAspectJAutoProxy @EnableLoadTimeWeaving(aspectjWeaving=AUTODETECT) public class CustomLtwConfig{ }
再次運(yùn)行測(cè)試類時(shí),發(fā)現(xiàn)AOP又生效了,這時(shí)候類路徑下并沒(méi)有aop.xml,所以這時(shí)候AOP是Spring在運(yùn)行期通過(guò)動(dòng)態(tài)代理的方式實(shí)現(xiàn)的。
3. 參考資料
《精通Spring4.x企業(yè)應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)》
《spring揭秘》
https://sexycoding.iteye.com/blog/1062372
到此這篇關(guān)于SpringBoot中使用LoadTimeWeaving技術(shù)實(shí)現(xiàn)AOP功能的文章就介紹到這了,更多相關(guān)SpringBoot AOP功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決Intellij IDEA 使用Spring-boot-devTools無(wú)效的問(wèn)題
下面小編就為大家?guī)?lái)一篇解決Intellij IDEA 使用Spring-boot-devTools無(wú)效的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07Java實(shí)現(xiàn)簡(jiǎn)單通訊錄管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單通訊錄管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07使用mybatisPlus生成oracle自增序列遇到的坑及解決
這篇文章主要介紹了使用mybatisPlus生成oracle自增序列遇到的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Spring調(diào)度框架EnableScheduling&Scheduled源碼解析
這篇文章主要介紹了Spring調(diào)度框架EnableScheduling&Scheduled源碼解析,@EnableScheduling&Scheduled定時(shí)調(diào)度框架,本著不僅知其然還要知其所以然的指導(dǎo)思想,下面對(duì)該調(diào)度框架進(jìn)行源碼解析,以便更好的理解其執(zhí)行過(guò)程,需要的朋友可以參考下2024-01-01JAVA將中文轉(zhuǎn)換為拼音簡(jiǎn)單實(shí)現(xiàn)方法
拼音轉(zhuǎn)換是中文處理的常見(jiàn)需求,TinyPinyin、HanLP、pinyin4j是常用的本地拼音轉(zhuǎn)換庫(kù),各有特點(diǎn),開(kāi)發(fā)者可根據(jù)具體需求選擇合適的拼音轉(zhuǎn)換工具,需要的朋友可以參考下2024-10-10詳解Spring Boot使用系統(tǒng)參數(shù)表提升系統(tǒng)的靈活性
Spring Boot項(xiàng)目中常有一些相對(duì)穩(wěn)定的參數(shù)設(shè)置項(xiàng),其作用范圍是系統(tǒng)級(jí)的或模塊級(jí)的,這些參數(shù)稱為系統(tǒng)參數(shù)。這些變量以參數(shù)形式進(jìn)行配置,從而提高變動(dòng)和擴(kuò)展的靈活性,保持代碼的穩(wěn)定性2021-06-06java學(xué)生管理系統(tǒng)界面簡(jiǎn)單實(shí)現(xiàn)(全)
這篇文章主要為大家詳細(xì)介紹了java學(xué)生管理系統(tǒng)界面的簡(jiǎn)單實(shí)現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01