在 Spring 應用中使用 AspectJ
到目前為止,本章所涵蓋的一切都是純粹的 Spring AOP。在本節中,如果你的需求超出了 Spring AOP 獨自提供的功能,我們將探討如何使用 AspectJ 編譯器或織入器來替代或補充 Spring AOP。
Spring 附帶了一個小的 AspectJ 切面庫,可在你的發行版中作為 spring-aspects.jar 獨立使用。你需要將其新增到你的 classpath 中才能使用其中的切面。使用 AspectJ 為領域物件注入依賴與 Spring 和 用於 AspectJ 的其他 Spring 切面 討論了該庫的內容以及如何使用它。使用 Spring IoC 配置 AspectJ 切面 討論瞭如何依賴注入使用 AspectJ 編譯器織入的 AspectJ 切面。最後,Spring Framework 中使用 AspectJ 進行載入時織入 介紹了為使用 AspectJ 的 Spring 應用程式進行載入時織入。
使用 AspectJ 為領域物件注入依賴與 Spring
Spring 容器例項化並配置在應用程式上下文中定義的 bean。也可以要求 bean 工廠配置一個預先存在的物件,前提是給出包含要應用的配置的 bean 定義的名稱。spring-aspects.jar 包含一個註解驅動的切面,它利用此功能允許任何物件的依賴注入。該支援旨在用於在任何容器控制之外建立的物件。領域物件通常屬於此類別,因為它們通常是使用 new 運算子以程式設計方式建立的,或者由 ORM 工具作為資料庫查詢的結果建立的。
@Configurable 註解將一個類標記為符合 Spring 驅動的配置條件。在最簡單的情況下,你可以純粹將其用作標記註解,如下例所示:
-
Java
-
Kotlin
package com.xyz.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable
public class Account {
// ...
}
package com.xyz.domain
import org.springframework.beans.factory.annotation.Configurable
@Configurable
class Account {
// ...
}
以這種方式用作標記介面時,Spring 透過使用與完全限定型別名 (com.xyz.domain.Account) 同名的 bean 定義(通常是原型作用域的)來配置帶註解型別(在本例中為 Account)的新例項。由於透過 XML 定義的 bean 的預設名稱是其型別的完全限定名,因此宣告原型定義的一種便捷方法是省略 id 屬性,如下例所示:
<bean class="com.xyz.domain.Account" scope="prototype">
<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>
如果你想明確指定要使用的原型 bean 定義的名稱,可以直接在註解中進行,如下例所示:
-
Java
-
Kotlin
package com.xyz.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("account")
public class Account {
// ...
}
package com.xyz.domain
import org.springframework.beans.factory.annotation.Configurable
@Configurable("account")
class Account {
// ...
}
Spring 現在會查詢名為 account 的 bean 定義,並將其用作配置新 Account 例項的定義。
你還可以使用自動裝配來避免完全指定專用的 bean 定義。要讓 Spring 應用自動裝配,請使用 @Configurable 註解的 autowire 屬性。你可以分別指定 @Configurable(autowire=Autowire.BY_TYPE) 或 @Configurable(autowire=Autowire.BY_NAME) 以按型別或按名稱自動裝配。作為替代方案,最好透過欄位或方法級別的 @Autowired 或 @Inject 為你的 @Configurable bean 指定顯式的、註解驅動的依賴注入(有關詳細資訊,請參閱 基於註解的容器配置)。
最後,你可以透過使用 dependencyCheck 屬性(例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true))為新建立和配置的物件中的物件引用啟用 Spring 依賴檢查。如果此屬性設定為 true,Spring 會在配置後驗證所有屬性(非基本型別或集合)是否已設定。
請注意,單獨使用註解不起任何作用。是 spring-aspects.jar 中的 AnnotationBeanConfigurerAspect 對註解的存在進行操作。本質上,該切面表示:“從帶有 @Configurable 註解型別的新物件初始化返回後,根據註解的屬性使用 Spring 配置新建立的物件”。在此上下文中,“初始化”指的是新例項化的物件(例如,使用 new 運算子例項化的物件)以及正在進行反序列化的 Serializable 物件(例如,透過 readResolve())。
|
上一段中的一個關鍵詞是“本質上”。對於大多數情況,“從新物件初始化返回後”的確切語義是可以的。在這種情況下,“初始化後”表示在物件構造完成後注入依賴項。這意味著依賴項在類的建構函式體中不可用。如果你希望在建構函式體執行之前注入依賴項,從而使其在建構函式體中可用,則需要在
你可以在 AspectJ 此附錄 的 AspectJ 程式設計指南 中找到有關各種切入點型別語言語義的更多資訊。 |
要使其正常工作,帶註解的型別必須透過 AspectJ 織入器進行織入。你可以使用構建時 Ant 或 Maven 任務來完成此操作(例如,請參閱 AspectJ 開發環境指南),或者進行載入時織入(請參閱 Spring Framework 中使用 AspectJ 進行載入時織入)。AnnotationBeanConfigurerAspect 本身需要由 Spring 配置(以便獲取對將用於配置新物件的 bean 工廠的引用)。你可以按如下方式定義相關配置:
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableSpringConfigured
public class ApplicationConfiguration {
}
@Configuration
@EnableSpringConfigured
class ApplicationConfiguration
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:spring-configured />
</beans>
在切面配置之前建立的 @Configurable 物件例項會導致除錯日誌中發出訊息,並且不會對物件進行配置。一個示例可能是 Spring 配置中的一個 bean,它在被 Spring 初始化時建立領域物件。在這種情況下,你可以使用 depends-on bean 屬性手動指定該 bean 依賴於配置切面。以下示例演示瞭如何使用 depends-on 屬性:
<bean id="myService"
class="com.xyz.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
除非你確實打算在執行時依賴其語義,否則不要透過 bean 配置器切面啟用 @Configurable 處理。特別要確保不要在已註冊為容器常規 Spring bean 的 bean 類上使用 @Configurable。這樣做會導致雙重初始化,一次透過容器,一次透過切面。 |
單元測試 @Configurable 物件
@Configurable 支援的目標之一是實現領域物件的獨立單元測試,而無需處理與硬編碼查詢相關的困難。如果 @Configurable 型別未被 AspectJ 織入,則該註解在單元測試期間不起作用。你可以設定被測物件中的模擬或存根屬性引用,並正常進行。如果 @Configurable 型別已被 AspectJ 織入,你仍然可以像往常一樣在容器外部進行單元測試,但每次構建 @Configurable 物件時都會看到一條警告訊息,指示它尚未由 Spring 配置。
使用多個應用程式上下文
用於實現 @Configurable 支援的 AnnotationBeanConfigurerAspect 是一個 AspectJ 單例切面。單例切面的範圍與 static 成員的範圍相同:每個定義型別的 ClassLoader 只有一個切面例項。這意味著,如果你在同一 ClassLoader 層次結構中定義多個應用程式上下文,你需要考慮在哪裡定義 @EnableSpringConfigured bean 以及在哪裡將 spring-aspects.jar 放在 classpath 上。
考慮一個典型的 Spring Web 應用程式配置,它有一個共享的父應用程式上下文,定義了通用的業務服務、支援這些服務所需的一切,以及每個 servlet 一個子應用程式上下文(包含特定於該 servlet 的定義)。所有這些上下文都共存於同一個 ClassLoader 層次結構中,因此 AnnotationBeanConfigurerAspect 只能持有一個引用。在這種情況下,我們建議在共享(父)應用程式上下文中定義 @EnableSpringConfigured bean。這定義了你可能希望注入到領域物件中的服務。結果是,你不能使用 @Configurable 機制配置帶有對子(servlet 特定)上下文中定義的 bean 的引用的領域物件(無論如何,這可能不是你想要的)。
在同一容器中部署多個 Web 應用程式時,請確保每個 Web 應用程式都使用自己的 ClassLoader 載入 spring-aspects.jar 中的型別(例如,將 spring-aspects.jar 放在 WEB-INF/lib 中)。如果 spring-aspects.jar 僅新增到容器範圍的 classpath 中(因此由共享的父 ClassLoader 載入),所有 Web 應用程式都將共享相同的切面例項(這可能不是你想要的)。
用於 AspectJ 的其他 Spring 切面
除了 @Configurable 切面,spring-aspects.jar 還包含一個 AspectJ 切面,你可以用它來驅動 Spring 對用 @Transactional 註解標記的型別和方法的事務管理。這主要用於希望在 Spring 容器之外使用 Spring Framework 事務支援的使用者。
解釋 @Transactional 註解的切面是 AnnotationTransactionAspect。當你使用此切面時,你必須註解實現類(或該類中的方法,或兩者),而不是該類實現的介面(如果有)。AspectJ 遵循 Java 的規則,即介面上的註解不會被繼承。
類上的 @Transactional 註解指定了類中任何公共操作執行的預設事務語義。
類中方法上的 @Transactional 註解會覆蓋類註解(如果存在)給出的預設事務語義。任何可見性的方法都可以被註解,包括私有方法。直接註解非公共方法是獲取此類方法執行的事務邊界的唯一方法。
自 Spring Framework 4.2 起,spring-aspects 提供了一個類似的切面,為標準 jakarta.transaction.Transactional 註解提供了完全相同的功能。有關詳細資訊,請檢視 JtaAnnotationTransactionAspect。 |
對於希望使用 Spring 配置和事務管理支援但不想(或不能)使用註解的 AspectJ 程式設計師,spring-aspects.jar 還包含你可以擴充套件的 abstract 切面,以提供你自己的切入點定義。有關更多資訊,請參閱 AbstractBeanConfigurerAspect 和 AbstractTransactionAspect 切面的原始碼。例如,以下摘錄顯示了你如何編寫一個切面,使用與完全限定類名匹配的原型 bean 定義來配置領域模型中定義的所有物件例項:
public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {
public DomainObjectConfiguration() {
setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
}
// the creation of a new bean (any object in the domain model)
protected pointcut beanCreation(Object beanInstance) :
initialization(new(..)) &&
CommonPointcuts.inDomainModel() &&
this(beanInstance);
}
使用 Spring IoC 配置 AspectJ 切面
當你在 Spring 應用程式中使用 AspectJ 切面時,很自然地會希望並期待能夠使用 Spring 配置這些切面。AspectJ 執行時本身負責切面建立,透過 Spring 配置 AspectJ 建立的切面的方法取決於切面使用的 AspectJ 例項化模型(per-xxx 子句)。
大多數 AspectJ 切面都是單例切面。配置這些切面很容易。你可以像往常一樣建立一個引用切面型別的 bean 定義,幷包含 factory-method="aspectOf" bean 屬性。這確保 Spring 透過 AspectJ 獲取切面例項,而不是嘗試自己建立例項。以下示例顯示瞭如何使用 factory-method="aspectOf" 屬性:
<bean id="profiler" class="com.xyz.profiler.Profiler"
factory-method="aspectOf"> (1)
<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
| 1 | 請注意 factory-method="aspectOf" 屬性。 |
非單例切面更難配置。但是,可以透過建立原型 bean 定義並使用 spring-aspects.jar 中的 @Configurable 支援來配置 AspectJ 執行時建立的切面例項。
如果你有一些要用 AspectJ 織入的 @AspectJ 切面(例如,對領域模型型別使用載入時織入)和一些要與 Spring AOP 一起使用的 @AspectJ 切面,並且這些切面都在 Spring 中配置,則你需要告訴 Spring AOP @AspectJ 自動代理支援在配置中定義的 @AspectJ 切面的哪個確切子集應該用於自動代理。你可以透過在 <aop:aspectj-autoproxy/> 宣告中使用一個或多個 <include/> 元素來完成此操作。每個 <include/> 元素指定一個名稱模式,並且只有名稱與至少一個模式匹配的 bean 才用於 Spring AOP 自動代理配置。以下示例顯示瞭如何使用 <include/> 元素:
<aop:aspectj-autoproxy>
<aop:include name="thisBean"/>
<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
不要被 <aop:aspectj-autoproxy/> 元素的名稱誤導。使用它會導致建立 Spring AOP 代理。這裡使用了 @AspectJ 風格的切面宣告,但 AspectJ 執行時並未涉及。 |
Spring Framework 中使用 AspectJ 進行載入時織入
載入時織入(LTW)是指在應用程式的類檔案載入到 Java 虛擬機器 (JVM) 中時,將 AspectJ 切面織入到這些類檔案中的過程。本節的重點是在 Spring Framework 的特定上下文中配置和使用 LTW。本節不是 LTW 的一般介紹。有關 LTW 的具體細節以及僅使用 AspectJ(完全不涉及 Spring)配置 LTW 的完整詳細資訊,請參閱 AspectJ 開發環境指南的 LTW 部分。
Spring Framework 為 AspectJ LTW 帶來的價值在於能夠對織入過程進行更細粒度的控制。'普通' AspectJ LTW 是透過使用 Java (5+) 代理實現的,該代理透過在啟動 JVM 時指定 VM 引數來開啟。因此,它是一個 JVM 範圍的設定,這在某些情況下可能沒問題,但通常有點太粗糙了。Spring 啟用的 LTW 允許你以每個 ClassLoader 為基礎開啟 LTW,這更細粒度,並且在“單 JVM-多應用程式”環境(例如典型的應用程式伺服器環境)中可能更有意義。
此外,在某些環境中,此支援允許載入時織入,而無需修改應用程式伺服器的啟動指令碼來新增 -javaagent:path/to/aspectjweaver.jar 或(如本節稍後所述)-javaagent:path/to/spring-instrument.jar。開發人員配置應用程式上下文以啟用載入時織入,而不是依賴通常負責部署配置(例如啟動指令碼)的管理員。
現在推銷已結束,我們首先透過一個使用 Spring 的 AspectJ LTW 快速示例,然後詳細介紹示例中引入的元素。有關完整示例,請參閱 基於 Spring Framework 的 Petclinic 示例應用程式。
第一個示例
假設你是一名應用程式開發人員,任務是診斷系統中一些效能問題的原因。我們不打算使用分析工具,而是開啟一個簡單的效能分析切面,讓我們可以快速獲取一些效能指標。然後,我們可以立即對該特定區域應用更細粒度的分析工具。
此處提供的示例使用 XML 配置。你還可以透過 Java 配置 配置和使用 @AspectJ。具體來說,你可以使用 @EnableLoadTimeWeaving 註解作為 <context:load-time-weaver/> 的替代方案(有關詳細資訊,請參閱下文)。 |
以下示例顯示了效能分析切面,它並不花哨。它是一個基於時間的效能分析器,使用 @AspectJ 風格的切面宣告:
-
Java
-
Kotlin
package com.xyz;
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 * com.xyz..*.*(..))")
public void methodsToBeProfiled(){}
}
package com.xyz
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
class ProfilingAspect {
@Around("methodsToBeProfiled()")
fun profile(pjp: ProceedingJoinPoint): Any? {
val sw = StopWatch(javaClass.simpleName)
try {
sw.start(pjp.getSignature().getName())
return pjp.proceed()
} finally {
sw.stop()
println(sw.prettyPrint())
}
}
@Pointcut("execution(public * com.xyz..*.*(..))")
fun methodsToBeProfiled() {
}
}
我們還需要建立一個 META-INF/aop.xml 檔案,以告知 AspectJ 織入器我們希望將 ProfilingAspect 織入到我們的類中。這種檔案約定,即 Java classpath 上存在一個(或多個)名為 META-INF/aop.xml 的檔案,是標準的 AspectJ。以下示例顯示了 aop.xml 檔案:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages and sub-packages -->
<include within="com.xyz..*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="com.xyz.ProfilingAspect"/>
</aspects>
</aspectj>
建議僅織入特定的類(通常是應用程式包中的類,如上面 aop.xml 示例所示),以避免諸如 AspectJ dump 檔案和警告之類的副作用。從效率的角度來看,這也是一種最佳實踐。 |
現在我們可以轉到配置的 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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- a service object; we will be profiling its methods -->
<bean id="entitlementCalculationService"
class="com.xyz.StubEntitlementCalculationService"/>
<!-- this switches on the load-time weaving -->
<context:load-time-weaver/>
</beans>
現在所有必需的工件(切面、META-INF/aop.xml 檔案和 Spring 配置)都已就位,我們可以建立以下帶有 main(..) 方法的驅動程式類來演示 LTW 的實際應用:
-
Java
-
Kotlin
package com.xyz;
// imports
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
EntitlementCalculationService service =
ctx.getBean(EntitlementCalculationService.class);
// the profiling aspect is 'woven' around this method execution
service.calculateEntitlement();
}
}
package com.xyz
// imports
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
val service = ctx.getBean(EntitlementCalculationService.class)
// the profiling aspect is 'woven' around this method execution
service.calculateEntitlement()
}
我們還有最後一件事要做。本節開頭確實說過,可以使用 Spring 在每個 ClassLoader 的基礎上選擇性地開啟 LTW,這是事實。但是,對於此示例,我們使用(Spring 提供的)Java 代理來開啟 LTW。我們使用以下命令執行前面顯示的 Main 類:
java -javaagent:C:/projects/xyz/lib/spring-instrument.jar com.xyz.Main
-javaagent 是一個標誌,用於指定和啟用 代理來檢測在 JVM 上執行的程式。Spring Framework 附帶了這樣一個代理,即 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 程式的輕微修改會產生相同的結果:
-
Java
-
Kotlin
package com.xyz;
// imports
public class Main {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("beans.xml");
EntitlementCalculationService service =
new StubEntitlementCalculationService();
// the profiling aspect will be 'woven' around this method execution
service.calculateEntitlement();
}
}
package com.xyz
// imports
fun main(args: Array<String>) {
ClassPathXmlApplicationContext("beans.xml")
val service = StubEntitlementCalculationService()
// the profiling aspect will be 'woven' around this method execution
service.calculateEntitlement()
}
請注意,在前面的程式中,我們如何引導 Spring 容器,然後完全在 Spring 之外建立 StubEntitlementCalculationService 的新例項。效能分析建議仍然被織入。
誠然,這個例子很簡單。然而,Spring 中 LTW 支援的基礎知識已在前面的例子中全部介紹,本節的其餘部分將詳細解釋每個配置和用法的“為什麼”。
本示例中使用的 ProfilingAspect 可能很基礎,但它非常有用。它是一個很好的開發時切面示例,開發人員可以在開發過程中使用,然後輕鬆地從部署到 UAT 或生產的應用程式構建中排除。 |
切面
你在 LTW 中使用的切面必須是 AspectJ 切面。你可以用 AspectJ 語言本身編寫它們,也可以用 @AspectJ 風格編寫你的切面。你的切面既是有效的 AspectJ 切面,也是有效的 Spring AOP 切面。此外,編譯後的切面類需要在 classpath 上可用。
META-INF/aop.xml
AspectJ LTW 基礎設施透過使用 Java classpath 上(直接或更常見地在 jar 檔案中)的一個或多個 META-INF/aop.xml 檔案進行配置。例如:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages and sub-packages -->
<include within="com.xyz..*"/>
</weaver>
</aspectj>
建議僅織入特定的類(通常是應用程式包中的類,如上面 aop.xml 示例所示),以避免諸如 AspectJ dump 檔案和警告之類的副作用。從效率的角度來看,這也是一種最佳實踐。 |
此檔案的結構和內容在 AspectJ 參考文件 的 LTW 部分中詳細說明。因為 aop.xml 檔案是 100% AspectJ,所以我們在此處不再贅述。
所需庫 (JARs)
至少,你需要以下庫才能使用 Spring Framework 對 AspectJ LTW 的支援:
-
spring-aop.jar -
aspectjweaver.jar
如果你使用 Spring 提供的代理來啟用 instrumentation,你還需要:
-
spring-instrument.jar
Spring 配置
Spring 的 LTW 支援中的關鍵元件是 LoadTimeWeaver 介面(在 org.springframework.instrument.classloading 包中),以及 Spring 發行版附帶的眾多實現。LoadTimeWeaver 負責在執行時向 ClassLoader 新增一個或多個 java.lang.instrument.ClassFileTransformers,這為各種有趣的應用程式打開了大門,其中之一恰好是切面的 LTW。
如果你不熟悉執行時類檔案轉換的概念,請在繼續之前檢視 java.lang.instrument 包的 javadoc API 文件。雖然該文件並非包羅永珍,但至少你可以看到關鍵介面和類(以供你在閱讀本節時參考)。 |
為特定 ApplicationContext 配置 LoadTimeWeaver 可以像新增一行程式碼一樣簡單。(請注意,你幾乎肯定需要使用 ApplicationContext 作為你的 Spring 容器——通常,BeanFactory 不夠,因為 LTW 支援使用 BeanFactoryPostProcessors。)
要啟用 Spring Framework 的 LTW 支援,你需要按如下方式配置 LoadTimeWeaver:
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableLoadTimeWeaving
public class ApplicationConfiguration {
}
@Configuration
@EnableLoadTimeWeaving
class ApplicationConfiguration
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:load-time-weaver />
</beans>
上述配置會自動為你定義和註冊許多 LTW 特定的基礎設施 bean,例如 LoadTimeWeaver 和 AspectJWeavingEnabler。預設的 LoadTimeWeaver 是 DefaultContextLoadTimeWeaver 類,它會嘗試裝飾一個自動檢測到的 LoadTimeWeaver。具體“自動檢測”到的 LoadTimeWeaver 型別取決於你的執行時環境。下表總結了各種 LoadTimeWeaver 實現:
| 執行時環境 | LoadTimeWeaver 實現 |
|---|---|
在 Apache Tomcat 中執行 |
|
在 GlassFish 中執行(限於 EAR 部署) |
|
|
|
JVM 隨 Spring |
|
回退,期望底層 ClassLoader 遵循通用約定(即 |
|
請注意,該表僅列出了使用 DefaultContextLoadTimeWeaver 時自動檢測到的 LoadTimeWeavers。你可以指定要使用的具體 LoadTimeWeaver 實現。
要配置特定的 LoadTimeWeaver,請實現 LoadTimeWeavingConfigurer 介面並重寫 getLoadTimeWeaver() 方法(或使用 XML 等效方法)。以下示例指定了一個 ReflectiveLoadTimeWeaver:
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableLoadTimeWeaving
public class CustomWeaverConfiguration implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
}
@Configuration
@EnableLoadTimeWeaving
class CustomWeaverConfiguration : LoadTimeWeavingConfigurer {
override fun getLoadTimeWeaver(): LoadTimeWeaver {
return ReflectiveLoadTimeWeaver()
}
}
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:load-time-weaver
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</beans>
透過配置定義和註冊的 LoadTimeWeaver 以後可以從 Spring 容器中使用眾所周知的名稱 loadTimeWeaver 檢索。請記住,LoadTimeWeaver 僅作為 Spring 的 LTW 基礎設施新增一個或多個 ClassFileTransformers 的機制而存在。實際執行 LTW 的 ClassFileTransformer 是 ClassPreProcessorAgentAdapter(來自 org.aspectj.weaver.loadtime 包)類。有關更多詳細資訊,請參閱 ClassPreProcessorAgentAdapter 類的類級別 javadoc,因為織入實際如何實現的細節超出了本文件的範圍。
配置中還有一個屬性需要討論:aspectjWeaving 屬性(如果使用 XML,則是 aspectj-weaving)。此屬性控制 LTW 是否啟用。它接受三個可能值之一,如果屬性不存在,則預設值為 autodetect。下表總結了三個可能的值:
| 註解值 | XML 值 | 解釋 |
|---|---|---|
|
|
AspectJ 織入已開啟,切面在載入時根據需要進行織入。 |
|
|
LTW 已關閉。沒有切面在載入時被織入。 |
|
|
如果 Spring LTW 基礎設施能夠找到至少一個 |
特定環境配置
最後一部分包含在應用程式伺服器和 Web 容器等環境中使用 Spring 的 LTW 支援時所需的任何附加設定和配置。
Tomcat, JBoss, WildFly
Tomcat 和 JBoss/WildFly 提供了一個通用的應用程式 ClassLoader,能夠進行本地 instrumentation。Spring 的原生 LTW 可以利用這些 ClassLoader 實現來提供 AspectJ 織入。你只需啟用載入時織入,如前面所述。具體來說,你無需修改 JVM 啟動指令碼來新增 -javaagent:path/to/spring-instrument.jar。
請注意,在 JBoss 上,你可能需要停用應用程式伺服器掃描,以防止它在應用程式實際啟動之前載入類。一個快速的解決方法是將名為 WEB-INF/jboss-scanning.xml 的檔案新增到你的 artifact 中,內容如下:
<scanning xmlns="urn:jboss:scanning:1.0"/>
通用 Java 應用程式
當在不受特定 LoadTimeWeaver 實現支援的環境中需要類 instrumentation 時,JVM 代理是通用解決方案。對於這種情況,Spring 提供了 InstrumentationLoadTimeWeaver,它需要一個 Spring 特定的(但非常通用)JVM 代理 spring-instrument.jar,該代理由常見的 @EnableLoadTimeWeaving 和 <context:load-time-weaver/> 設定自動檢測。
要使用它,你必須透過提供以下 JVM 選項來使用 Spring 代理啟動虛擬機器:
-javaagent:/path/to/spring-instrument.jar
請注意,這需要修改 JVM 啟動指令碼,這可能會阻止你在應用程式伺服器環境中使用此功能(具體取決於你的伺服器和操作策略)。話雖如此,對於每個 JVM 一個應用程式的部署(例如獨立的 Spring Boot 應用程式),無論如何你通常都可以控制整個 JVM 設定。