將 AspectJ 與 Spring 應用程式搭配使用
到目前為止,本章中涵蓋的所有內容都是純粹的 Spring AOP。在本節中,我們將探討如果您的需求超出 Spring AOP 單獨提供的功能,您可以如何使用 AspectJ 編譯器或織入器來取代或補充 Spring AOP。
Spring 隨附一個小型 AspectJ 切面庫,該庫在您的發行版中以獨立的 spring-aspects.jar
形式提供。您需要將其新增至您的類別路徑,才能使用其中的切面。使用 AspectJ 與 Spring 對網域物件進行相依性注入 和 AspectJ 的其他 Spring 切面 討論了此庫的內容以及您如何使用它。使用 Spring IoC 配置 AspectJ 切面 討論了如何對使用 AspectJ 編譯器織入的 AspectJ 切面進行相依性注入。最後,在 Spring Framework 中使用 AspectJ 進行載入時織入 簡介了針對使用 AspectJ 的 Spring 應用程式進行載入時織入。
使用 AspectJ 與 Spring 對網域物件進行相依性注入
Spring 容器會實例化並配置在您的應用程式 Context 中定義的 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 定義 (通常是 prototype 作用域) 名稱來配置已註解類型 (在本例中為 Account
) 的新實例。由於透過 XML 定義的 Bean 的預設名稱是其類型的完整名稱,因此宣告 prototype 定義的便捷方式是省略 id
屬性,如下列範例所示
<bean class="com.xyz.domain.Account" scope="prototype">
<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>
如果您想要明確指定要使用的 prototype 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)
。或者,對於您的 @Configurable
Bean,最好透過欄位或方法層級的 @Autowired
或 @Inject
指定明確的註解驅動相依性注入 (如需更多詳細資訊,請參閱 基於註解的容器組態)。
最後,您可以使用 dependencyCheck
屬性 (例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)
) 為新建立和配置的物件啟用 Spring 相依性檢查。如果此屬性設定為 true
,則 Spring 會在配置後驗證是否已設定所有屬性 (不是基本類型或集合)。
請注意,單獨使用註解不會執行任何操作。spring-aspects.jar
中的 AnnotationBeanConfigurerAspect
會根據註解的存在而運作。本質上,切面表示:「從以 @Configurable
註解的類型的新物件初始化傳回後,依照註解的屬性使用 Spring 配置新建立的物件」。在此 Context 中,「初始化」是指新實例化的物件 (例如,使用 new
運算子實例化的物件) 以及正在進行還原序列化的 Serializable
物件 (例如,透過 readResolve())。
上述段落中的關鍵詞組之一是「本質上」。在大多數情況下,「從新物件初始化傳回後」的確切語意都很好。在此 Context 中,「初始化後」表示相依性在物件建構後注入。這表示相依性在類別的建構子主體中不可用。如果您希望在建構子主體執行之前注入相依性,並使其在建構子的主體中可用,則需要在
您可以在 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,該 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 織入,則註解在單元測試期間無效。您可以在測試中的物件中設定 Mock 或 Stub 屬性參考,並照常繼續。如果 @Configurable
類型已由 AspectJ 織入,您仍然可以在容器外部照常進行單元測試,但每次建構 @Configurable
物件時,您都會看到一則警告訊息,指出它尚未由 Spring 配置。
使用多個應用程式 Context
用於實作 @Configurable
支援的 AnnotationBeanConfigurerAspect
是 AspectJ Singleton 切面。Singleton 切面的作用域與 static
成員的作用域相同:每個定義類型的 ClassLoader
都有一個切面實例。這表示,如果您在相同的 ClassLoader
階層中定義多個應用程式 Context,則需要考慮在何處定義 @EnableSpringConfigured
Bean,以及在類別路徑上放置 spring-aspects.jar
的位置。
考慮典型的 Spring Web 應用程式組態,該組態具有共用的父應用程式 Context,該 Context 定義了常見的業務服務、支援這些服務所需的一切,以及每個 Servlet 的一個子應用程式 Context (其中包含特定於該 Servlet 的定義)。所有這些 Context 都共存於相同的 ClassLoader
階層中,因此 AnnotationBeanConfigurerAspect
只能保留對其中一個 Context 的參考。在這種情況下,我們建議在共用 (父) 應用程式 Context 中定義 @EnableSpringConfigured
Bean。這定義了您可能想要注入到網域物件中的服務。結果是您無法使用 @Configurable 機制來配置對子 (Servlet 特定) Context 中定義的 Bean 的參考 (這可能也不是您想要執行的操作)。
在相同的容器中部署多個 Web 應用程式時,請確保每個 Web 應用程式都使用自己的 ClassLoader
載入 spring-aspects.jar
中的類型 (例如,透過將 spring-aspects.jar
放置在 WEB-INF/lib
中)。如果 spring-aspects.jar
僅新增至容器範圍的類別路徑 (因此由共用的父 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
切面的來源。例如,以下摘錄顯示了您可以如何編寫一個切面,以使用符合完整類別名稱的 prototype 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 切面
當您將 AspectJ 切面與 Spring 應用程式搭配使用時,自然會希望並且預期能夠使用 Spring 配置此類切面。AspectJ 執行階段本身負責切面建立,以及透過 Spring 配置 AspectJ 建立的切面的方式取決於切面使用的 AspectJ 實例化模型 (per-xxx
子句)。
大多數 AspectJ 切面都是 Singleton 切面。這些切面的組態很容易。您可以建立一個 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" 屬性 |
非 Singleton 切面更難以配置。但是,可以透過建立 prototype 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) 是指在將 AspectJ 切面載入 Java 虛擬機器 (JVM) 時,將其織入應用程式的類別檔案的過程。本節重點介紹在 Spring Framework 的特定 Context 中配置和使用 LTW。本節並非 LTW 的一般簡介。如需有關 LTW 的詳細資訊以及僅使用 AspectJ (完全不涉及 Spring) 配置 LTW 的詳細資訊,請參閱 AspectJ 開發環境指南的 LTW 章節。
Spring Framework 為 AspectJ LTW 帶來的價值在於能夠更精細地控制織入 (weaving) 過程。 「原生 (Vanilla)」 AspectJ LTW 是透過使用 Java (5+) 代理程式 (agent) 來實現的,該代理程式在啟動 JVM 時透過指定 VM 引數來開啟。 因此,這是一個 JVM 全域設定,在某些情況下可能還可以,但通常有點過於粗略。 Spring 啟用的 LTW 可讓您在每個 ClassLoader
的基礎上開啟 LTW,這更加精細,並且在「單一 JVM 多應用程式」環境(例如在典型的應用程式伺服器環境中)中更有意義。
此外,在某些環境中,此支援能夠在不修改應用程式伺服器的啟動腳本的情況下實現載入時織入 (load-time weaving),而修改啟動腳本是新增 -javaagent:path/to/aspectjweaver.jar
或 (如我們在本節稍後描述) -javaagent:path/to/spring-instrument.jar
所需的。 開發人員配置應用程式上下文 (application context) 以啟用載入時織入,而不是依賴通常負責部署配置(例如啟動腳本)的管理員。
現在銷售說詞結束了,讓我們先快速瀏覽一下使用 Spring 的 AspectJ LTW 範例,然後詳細說明範例中引入的元素。 如需完整範例,請參閱基於 Spring Framework 的 Petclinic 範例應用程式。
第一個範例
假設您是一位應用程式開發人員,您的任務是診斷系統中某些效能問題的原因。 我們不打算使用效能分析工具,而是要開啟一個簡單的效能分析切面 (aspect),讓我們可以快速獲得一些效能指標。 然後,我們可以立即將更精細的效能分析工具應用於該特定區域。
此處提供的範例使用 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 織入器 (weaver) 我們想要將 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>
建議僅織入特定的類別(通常是應用程式套件 (package) 中的類別,如上面的 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>
現在所有需要的工件 (artifact)(切面、META-INF/aop.xml
檔案和 Spring 配置)都已就緒,我們可以建立以下驅動程式類別 (driver class),其中包含 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,這是真的。 然而,對於這個範例,我們使用 Java 代理程式(Spring 提供)來開啟 LTW。 我們使用以下命令來執行先前顯示的 Main
類別
java -javaagent:C:/projects/xyz/lib/spring-instrument.jar com.xyz.Main
-javaagent
是一個標誌 (flag),用於指定和啟用 代理程式來檢測在 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 來實現的,因此我們不僅限於建議 (advise) 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()
}
請注意,在先前的程式中,我們引導 (bootstrap) Spring 容器,然後完全在 Spring 上下文之外建立 StubEntitlementCalculationService
的新實例。 效能分析建議仍然會被織入。
誠然,這個範例很簡單。 然而,Spring 中 LTW 支援的基本知識已在先前的範例中介紹完畢,本節的其餘部分將詳細解釋每個配置和用法背後的「原因」。
本範例中使用的 ProfilingAspect 可能很基本,但它非常有用。 這是一個很好的開發時切面範例,開發人員可以在開發期間使用它,然後輕鬆地從部署到 UAT 或生產環境的應用程式組建中排除它。 |
切面
您在 LTW 中使用的切面必須是 AspectJ 切面。 您可以使用 AspectJ 語言本身編寫它們,也可以使用 @AspectJ 風格編寫您的切面。 然後,您的切面既是有效的 AspectJ 切面,也是 Spring AOP 切面。 此外,編譯後的切面類別需要可在類別路徑上取得。
META-INF/aop.xml
AspectJ LTW 基礎架構是透過使用一個或多個位於 Java 類別路徑上的 META-INF/aop.xml
檔案(直接或更常見的是在 jar 檔案中)來配置的。 例如
<!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>
建議僅織入特定的類別(通常是應用程式套件 (package) 中的類別,如上面的 aop.xml 範例所示),以避免副作用,例如 AspectJ 傾印 (dump) 檔案和警告。 從效率的角度來看,這也是最佳實務。 |
此檔案的結構和內容在 AspectJ 參考文件的 LTW 部分中有詳細說明。 由於 aop.xml
檔案是 100% AspectJ,因此我們在此不再進一步描述它。
必要的函式庫 (JAR)
若要使用 Spring Framework 對 AspectJ LTW 的支援,您至少需要以下函式庫
-
spring-aop.jar
-
aspectjweaver.jar
如果您使用Spring 提供的代理程式來啟用檢測 (instrumentation),您還需要
-
spring-instrument.jar
Spring 配置
Spring LTW 支援中的關鍵元件是 LoadTimeWeaver
介面(在 org.springframework.instrument.classloading
套件中),以及 Spring 發行版隨附的眾多實作。 LoadTimeWeaver
負責在執行時將一個或多個 java.lang.instrument.ClassFileTransformers
新增到 ClassLoader
,這為各種有趣的應用程式打開了大門,其中一個恰好是切面的 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 部署) |
|
|
|
使用 Spring |
|
回退 (Fallback),預期底層 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
稍後可以使用眾所周知的名稱 loadTimeWeaver
從 Spring 容器中檢索。 請記住,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
,它能夠進行本機檢測。 Spring 的原生 LTW 可以利用這些 ClassLoader 實作來提供 AspectJ 織入。 您可以簡單地啟用載入時織入,如先前所述。 具體來說,您不需要修改 JVM 啟動腳本來新增 -javaagent:path/to/spring-instrument.jar
。
請注意,在 JBoss 上,您可能需要停用應用程式伺服器掃描,以防止它在應用程式實際啟動之前載入類別。 一個快速的解決方法是在您的工件中新增一個名為 WEB-INF/jboss-scanning.xml
的檔案,其內容如下
<scanning xmlns="urn:jboss:scanning:1.0"/>
通用 Java 應用程式
當在特定 LoadTimeWeaver
實作不支援的環境中需要類別檢測時,JVM 代理程式是通用的解決方案。 對於這種情況,Spring 提供了 InstrumentationLoadTimeWeaver
,它需要一個 Spring 特有的(但非常通用的)JVM 代理程式 spring-instrument.jar
,由通用的 @EnableLoadTimeWeaving
和 <context:load-time-weaver/>
設定自動偵測。
若要使用它,您必須使用 Spring 代理程式啟動虛擬機器,方法是提供以下 JVM 選項
-javaagent:/path/to/spring-instrument.jar
請注意,這需要修改 JVM 啟動腳本,這可能會阻止您在應用程式伺服器環境中使用它(取決於您的伺服器和您的操作策略)。 也就是說,對於每個 JVM 一個應用程式的部署(例如獨立的 Spring Boot 應用程式),您通常在任何情況下都控制整個 JVM 設定。