SpringBoot中使用LoadTimeWeaving技術實現AOP功能

1. 關於LoadTimeWeaving

1.1 LTW與不一樣的切面織入時機java

AOP——面向切面編程,經過爲目標類織入切面的方式,實現對目標類功能的加強。按切面被織如到目標類中的時間劃分,主要有如下幾種:spring

  • 1.運行期織入apache

  • 這是最多見的,好比在運行期經過爲目標類生成動態代理的方式實現AOP就屬於運行期織入,這也是Spring AOP中的默認實現,而且提供了兩種建立動態代理的方式:JDK自帶的針對接口的動態代理和使用CGLib動態建立子類的方式建立動態代理。編程

  • 2.編譯期織入數組

  • 使用特殊的編譯器在編譯期將切面織入目標類,這種比較少見,由於須要特殊的編譯器的支持。bash

  • 3.類加載期織入eclipse

  • 經過字節碼編輯技術在類加載期將切面織入目標類中,這是本篇介紹的重點。它的核心思想是:在目標類的class文件被JVM加載前,經過自定義類加載器或者類文件轉換器將橫切邏輯織入到目標類的class文件中,而後將修改後class文件交給JVM加載。這種織入方式能夠簡稱爲LTW(LoadTimeWeaving)。jvm

1.2 JDK實現LTW的原理maven

能夠使用JKD的代理功能讓代理器訪問到JVM的底層組件,藉此向JVM註冊類文件轉換器,在類加載時對類文件的字節碼進行轉換。具體而言,java.lang.instrument包下定義了ClassFileTransformer接口,該接口的做用以下面的註釋所描述ide

* 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.複製代碼

能夠經過實現該接口,並重寫以下抽象方法自定義類文件轉換規則

byte[]
transform( ClassLoader loader,
 String className,
 Class<?> classBeingRedefined,
 ProtectionDomain protectionDomain,
 byte[] classfileBuffer)
 throws IllegalClassFormatException;複製代碼

classfileBuffer是原始類文件對應的字節碼數組,返回的byte[]爲轉化後的字節碼數組,若是返回null,則表示不進行字節碼處理。

而java.lang.instrument包下的Instrumentation接口則能夠將咱們自定義的ClassTransFormer向JVM內部的組件進行註冊

addTransformer(ClassFileTransformer transformer);複製代碼

在實際使用中,能夠經過JVM的-javaagent代理參數在啓動時獲取JVM內部組件的引用,將ClassFileTransformer實例註冊到JVM中,JVM在加載Class文件時,會先調用這個ClassTransformer的transform()方法對Class文件的字節碼進行轉換,好比織入切面中定義的橫切邏輯,實現AOP功能。整個過程能夠入下所示

SpringBoot中使用LoadTimeWeaving技術實現AOP功能

1.3 如何在Spring中實現LTW

Spring中默認經過運行期生成動態代理的方式實現切面的織入,實現AOP功能,可是Spring也能夠使用LTW技術來實現AOP,而且提供了細粒度的控制,支持在單個ClassLoader範圍內實施類文件轉換。

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語法定義的切面,也能夠是使用@AspectJ註解,經過java類定義的切面。Spring LTW經過讀取classpath下META-INF/aop.xml文件,獲取切面類和要被切面織入的目標類的相關信息,經過LoadTimeWeaver在ClassLoader加載類文件時將切面織入目標類中,其工做原理以下所示

SpringBoot中使用LoadTimeWeaving技術實現AOP功能


Spring中能夠經過LoadTimeWeaver將Spring提供的ClassFileTransformer註冊到ClassLoader中。在類加載期,註冊的ClassFileTransformer讀取類路徑下META-INF/aop.xml文件中定義的切面類和目標類信息,在目標類的class文件真正被VM加載前織入切面信息,生成新的Class文件字節碼,而後交給VM加載。於是以後建立的目標類的實例,就已經實現了AOP功能。

2. Springboot中使用LTW實現AOP的例子

實現一個簡單的AOP需求,在方法調用先後打印出開始和結束的日誌信息。

  • 相關的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>複製代碼

這裏經過maven插件的方式爲JVM設置代理,經過-javaagent參數指定織入器類包的路徑,這樣就能夠在類加載期將切面織入,更多關於javaagent的知識能夠參考javaagent

  • 織入目標類

/**
 * @author: takumiCX
 * @create: 2018-12-19
 **/
@Component
public class LtwBean {
 public void test(){
 System.out.println("process.......");
 }
}複製代碼

只有一個test()方法,經過@Componet註解向容器註冊。

  • 切面類

/**
 * @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註解表示這是一個切面類

  • 配置類

/**
 * @author: takumiCX
 * @create: 2018-12-19
 **/
@Configuration
@ComponentScan("com.takumiCX.ltw")
@EnableLoadTimeWeaving(aspectjWeaving=AUTODETECT)
public class CustomLtwConfig{
}複製代碼

經過@@EnableLoadTimeWeaving開啓LTW功能,能夠經過屬性aspectjWeaving指定LTW的開啓策略

  1. ENABLED

  2. 開啓LTW

  3. DISABLED

  4. 不開啓LTW

  5. AUTODETECT

  6. 若是類路徑下能讀取到META-INF/aop.xml文件,則開啓LTW,不然關閉

  • 在META-INF文件夾下編寫aop.xml文件

SpringBoot中使用LoadTimeWeaving技術實現AOP功能

aop.xml文件內容

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
 <!--要織入切面的目標類-->
 <weaver>
 <include within="com.takumiCX.ltw..*" />
 </weaver>
 <!--切面類-->
 <aspects>
 <aspect name="com.takumiCX.ltw.aspect.LogMethodInvokeAspect" />
 </aspects>
</aspectj>複製代碼

這樣咱們的Spring容器就能加載該文件讀取到描述目標類和切面類的相關信息,容器在加載目標類的class文件到jvm以前,會將切面類中定義的加強邏輯織入到class文件中,真正加載到jvm中的是織入切面後的class文件,於是經過該class文件建立出的目標類的實例,不須要通過動態代理就能實現AOP相關功能。

  • 測試類

/**
 * @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();
 }
}複製代碼

最後的結果以下

SpringBoot中使用LoadTimeWeaving技術實現AOP功能

方法調用先後分別記錄的開始和結束的日誌信息,說明咱們的切面成功的織入到了目標類。可是這裏可能有一個疑問,這真的是LTW(Load TimeWeaving)經過在類加載期織入切面起到的做用嗎?有沒有多是LTW沒起做用,是Spring AOP默認經過運行期生成動態代理的方式實現的AOP。

咱們的LogMethodInvokeAspect切面類上並無加@Component註解向容器註冊,而且配置類CustomLtwConfig上也沒有加@EnableAspectJAutoProxy註解開啓Aspectj的運行時動態代理,因此這裏基於動態代理的AOP並不會生效。

爲了驗證咱們的想法,將aop.xml文件刪除

SpringBoot中使用LoadTimeWeaving技術實現AOP功能

從新運行測試代碼

SpringBoot中使用LoadTimeWeaving技術實現AOP功能

AOP沒起到做用,說明剛纔的AOP功能確實是經過LTW技術實現的。

當咱們給切面類加上@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{
}複製代碼

再次運行測試類時,發現AOP又生效了,這時候類路徑下並無aop.xml,因此這時候AOP是Spring在運行期經過動態代理的方式實現的。

SpringBoot中使用LoadTimeWeaving技術實現AOP功能

相關文章
相關標籤/搜索