介紹
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LoadTimeWeavingConfiguration.class)
public @interface EnableLoadTimeWeaving {
/**
* Whether AspectJ weaving should be enabled.
*/
AspectJWeaving aspectjWeaving() default AspectJWeaving.AUTODETECT;
/**
* AspectJ weaving enablement options.
*/
enum AspectJWeaving {
/**
* Switches on Spring-based AspectJ load-time weaving.
*/
ENABLED,
/**
* Switches off Spring-based AspectJ load-time weaving (even if a
* "META-INF/aop.xml" resource is present on the classpath).
*/
DISABLED,
/**
* Switches on AspectJ load-time weaving if a "META-INF/aop.xml" resource
* is present in the classpath. If there is no such resource, then AspectJ
* load-time weaving will be switched off.
*/
AUTODETECT;
}
}
說明:
作用:
用於切換不同場景下實現增強。
屬性:
aspectjWeaving:是否開啟LTW的支持。
ENABLED 開啟LTW
DISABLED 不開啟LTW
AUTODETECT 如果類路徑下能讀取到META‐INF/aop.xml文件,則開啟LTW,否則關閉
使用場景:
在Java 語言中,從織入切面的方式上來看,存在三種織入方式:編譯期織入、類加載期織入和運行期織入。編譯期織入是指在Java編譯期,采用特殊的編譯器,將切面織入到Java類中;而類加載期織入則指通過特殊的類加載器,在類字節碼加載到JVM時,織入切面;運行期織入則是采用CGLib工具或JDK動態代理進行切面的織入。
AspectJ提供了兩種切面織入方式,第一種通過特殊編譯器,在編譯期,將AspectJ語言編寫的切面類織入到Java類中,可以通過一個Ant或Maven任務來完成這個操作;第二種方式是類加載期織入,也簡稱為LTW(Load Time Weaving)
官網說明
假設您是一位負責診斷系統中某些性能問題的原因的應用程序開發人員。與其使用分析工具,不如使用一個簡單的分析方面,使我們能夠快速獲得一些性能 Metrics。然后,我們可以立即在該特定區域應用更細粒度的分析工具。
Note
此處提供的示例使用 XML 配置。您還可以將Java configuration配置和使用@AspectJ。具體來說,您可以使用@EnableLoadTimeWeaving
Comments 替代<context:load-time-weaver/>
(有關詳細信息,請參見below)。
下面的示例顯示了配置方面,它不是花哨的-它是基於時間的探查器,它使用@AspectJ 樣式的方面聲明:
package foo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;
@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.out.println(sw.prettyPrint());
}
}
@Pointcut("execution(public * foo..*.*(..))")
public void methodsToBeProfiled(){}
}
我們還需要創建一個META-INF/aop.xml
文件,以通知 AspectJ 編織者我們要將ProfilingAspect
編織到類中。此文件約定,即在 JavaClasspath 上名為META-INF/aop.xml
的文件,是標准 AspectJ。下面的示例顯示aop.xml
文件:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="foo.*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="foo.ProfilingAspect"/>
</aspects>
</aspectj>
現在,我們可以 continue 進行配置中特定於 Spring 的部分。我們需要配置一個LoadTimeWeaver
(稍后說明)。此加載時織布器是必不可少的組件,負責將一個或多個META-INF/aop.xml
文件中的方面配置編織到應用程序的類中。好處是,它不需要很多配置(您可以指定一些其他選項,但是稍后會詳細介紹),如以下示例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- a service object; we will be profiling its methods -->
<bean id="entitlementCalculationService"
class="foo.StubEntitlementCalculationService"/>
<!-- this switches on the load-time weaving -->
<context:load-time-weaver/>
</beans>
現在,所有必需的構件(方面,META-INF/aop.xml
文件和 Spring 配置)都就位了,我們可以使用main(..)
方法創建以下驅動程序類,以演示實際的 LTW:
package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);
EntitlementCalculationService entitlementCalculationService
= (EntitlementCalculationService) ctx.getBean("entitlementCalculationService");
// the profiling aspect is 'woven' around this method execution
entitlementCalculationService.calculateEntitlement();
}
}
我們還有最后一件事要做。本節的引言確實說過,可以使用 Spring 以ClassLoader
為基礎選擇性地打開 LTW,這是事實。但是,在此示例中,我們使用 Java 代理(Spring 隨附)打開 LTW。我們使用以下命令運行前面顯示的Main
類:
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
-javaagent
是用於指定和啟用代理來檢測在 JVM 上運行的程序的標志。 Spring 框架附帶了這樣的代理InstrumentationSavingAgent
,該代理打包在spring-instrument.jar
中,在上一示例中,該代理作為-javaagent
自變量的值提供。
Main
程序的執行輸出類似於下一個示例。 (我在calculateEntitlement()
實現中引入了Thread.sleep(..)
語句,以便探查器實際上捕獲的不是 0 毫秒(01234
毫秒不是 AOP 引入的開銷)。以下清單顯示了運行探查器時得到的輸出:
Calculating entitlement
StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms % Task name
------ ----- ----------------------------
01234 100% calculateEntitlement
由於此 LTW 是通過使用成熟的 AspectJ 來實現的,因此我們不僅限於建議 Spring Bean。 Main
程序的以下細微變化會產生相同的結果:
package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("beans.xml", Main.class);
EntitlementCalculationService entitlementCalculationService =
new StubEntitlementCalculationService();
// the profiling aspect will be 'woven' around this method execution
entitlementCalculationService.calculateEntitlement();
}
}
注意,在前面的程序中,我們如何引導 Spring 容器,然后完全在 Spring 上下文之外創建StubEntitlementCalculationService
的新實例。剖析建議仍會被應用。
誠然,這個例子很簡單。但是,在前面的示例中已經介紹了 Spring 對 LTW 支持的基礎,本節的其余部分詳細解釋了每一位配置和用法的“原因”。
示例代碼
@Configuration
@ComponentScan("com.dalianpai.spring5.aop")
@EnableLoadTimeWeaving(aspectjWeaving=EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class SpringConfiguration {
}
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="com.dalianpai.spring5..*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="com.dalianpai.spring5.aop.utils.LoadTimeWeavingAspect"/>
</aspects>
</aspectj>
@Aspect
//@Component
public class LoadTimeWeavingAspect {
/**
* 切入點表達式
*/
@Pointcut("execution(* com.dalianpai.spring5.aop.service.impl.*.*(..))")
public void pointcut() {
}
@Around("pointcut()")
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.out.println(sw.prettyPrint());
}
}
}