?
This document uses PHP Chinese website manual Release
到目前為止本章討論的一直是純Spring AOP。在這一節(jié)里面我們將介紹如何使用AspectJ compiler/weaver 來代替Spring AOP或者作為它的補(bǔ)充,因?yàn)橛行r(shí)候Spring AOP單獨(dú)提供的功能也許并不能滿足你的需要。
Spring提供了一個(gè)小巧的AspectJ aspect library,你可以在程序發(fā)行版本中單獨(dú)使用
spring-aspects.jar
文件,并將其加入到classpath下以使用其中的切面。
第?6.8.1?節(jié) “在Spring中使用AspectJ進(jìn)行domain object的依賴注入”和第?6.8.2?節(jié) “Spring中其他的AspectJ切面” 討論了該庫以及如何使用該庫。
第?6.8.3?節(jié) “使用Spring IoC來配置AspectJ的切面”討論了如何對(duì)通過AspectJ compiler織入的AspectJ切面進(jìn)行依賴注入。
最后第?6.8.4?節(jié) “在Spring應(yīng)用中使用AspectJ加載時(shí)織入(LTW)”介紹了使用AspectJ的Spring應(yīng)用程序如何進(jìn)行加載期織入(load-time weaving)。
Spring容器對(duì)application context中定義的bean進(jìn)行實(shí)例化和配置。同樣也可以通過bean factory
來為一個(gè)已經(jīng)存在且已經(jīng)定義為spring bean的對(duì)象應(yīng)用所包含的配置信息。
spring-aspects.jar
中包含了一個(gè)annotation-driven的切面,
提供了能為任何對(duì)象進(jìn)行依賴注入的能力。這樣的支持旨在為
脫離容器管理而創(chuàng)建的對(duì)象進(jìn)行依賴注入。領(lǐng)域?qū)ο蠼?jīng)常處于這樣的情形:
它們可能是通過new
操作符創(chuàng)建的對(duì)象,也可能是由ORM工具查詢數(shù)據(jù)庫所返回的結(jié)果。
@Configurable
注解標(biāo)記了一個(gè)類可以通過Spring-driven方式來配置。
在最簡(jiǎn)單的情況下,我們只把它當(dāng)作標(biāo)記注解:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable
public class Account {
// ...
}
當(dāng)只是簡(jiǎn)單地作為一個(gè)標(biāo)記接口來使用的時(shí)候,Spring將采用和該已注解的類型
(比如Account
類)全名(com.xyz.myapp.domain.Account
)
一致的bean原型定義來配置一個(gè)新實(shí)例。由于一個(gè)bean默認(rèn)的名字就是它的全名,
所以一個(gè)比較方便的辦法就是省略定義中的id
屬性:
<bean class="com.xyz.myapp.domain.Account" scope="prototype"> <property name="fundsTransferService" ref="fundsTransferService"/> </bean>
如果你希望明確的指定bean原型定義的名字,你可以在注解中直接定義:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("account")
public class Account {
// ...
}
Spring會(huì)查找名字為"account"的bean定義,并使用它作定義來配置一個(gè)新的
Account
實(shí)例。
你也可以使用自動(dòng)裝配來避免手工指定原型定義的名字。只要設(shè)置@Configurable
注解中的autowire
屬性就可以讓Spring進(jìn)行自動(dòng)裝配:
指定@Configurable(autowire=Autowire.BY_TYPE)
或者
@Configurable(autowire=Autowire.BY_NAME
可以讓自動(dòng)裝配分別按照類型或名字進(jìn)行。
作為另外一種選擇,在Spring2.5中最好是在域或方法級(jí)使用@Autowired
和
@Resource
為你的@Configurable
beans指定
明確的、注解驅(qū)動(dòng)的依賴注入。(詳情請(qǐng)參看第?3.11?節(jié) “基于注解(Annotation-based)的配置”)
最后,你可以通過使用dependencyCheck
屬性,讓Spring對(duì)新創(chuàng)建和配置的對(duì)象的對(duì)象引用進(jìn)行
依賴檢查(例如:@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)
)。
如果這個(gè)屬性設(shè)置為true,Spring會(huì)在配置結(jié)束后校驗(yàn)(除了primitives和collections類型)
所有的屬性是否都被設(shè)置。
僅僅使用注解并沒有做任何事情。但是spring-aspects.jar
中的AnnotationBeanConfigurerAspect
會(huì)在注解存在時(shí)起作用。實(shí)質(zhì)上切面指明:
“在初始化一個(gè)由@Configurable
注解的新對(duì)象時(shí),
Spring按照注解中的屬性來配置這個(gè)新創(chuàng)建的對(duì)象”。這種情況下,initialization
指新初始化的(比如用new
初始化)的對(duì)象以及能進(jìn)行反序列化的
Serializable
對(duì)象(例如通過
readResolve()方法)。
在上一段中一個(gè)關(guān)鍵的階段就是“inessence”。多數(shù)情況下,“
當(dāng)從一個(gè)新對(duì)象初始化返回之后”的精確語義很不錯(cuò)...這種語境下,
“初始化之后”的意思是依賴將在對(duì)象被構(gòu)造之后注入 -
這意味著在類的構(gòu)造器塊中依賴將不可用。如果你希望它能在構(gòu)造器代碼塊執(zhí)行
之前被注入,并從而在構(gòu)造器中使用它,
那么你需要在@Configurable
接口聲明上做類似的定義:
@Configurable(preConstruction=true)
你可以在 AspectJ Programming Guide一書的附錄中 找到更多有關(guān)在AspectJ中各種切面類型的語義信息。
要實(shí)現(xiàn)上述的操作,已注解的類型必須由AspectJ weaver來織入 - 你可以使用一個(gè)構(gòu)建時(shí)的ant/maven任務(wù)來完成
(參見AspectJ Development Environment Guide)或者使用加載時(shí)織入(參見 第?6.8.4?節(jié) “在Spring應(yīng)用中使用AspectJ加載時(shí)織入(LTW)”)。
類AnnotationBeanConfigurerAspect
本身也需要Spring來配置(獲得bean factory的引用,使用bean factory配置新的對(duì)象)。為此Spring的
context
命名空間
定義了一個(gè)非常方便的標(biāo)簽。只要簡(jiǎn)單的在application context配置中包含下面的內(nèi)容。
<context:spring-configured/>
如果你使用DTD代替Schema,對(duì)應(yīng)的定義如下:
<bean class="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect" factory-method="aspectOf"/>
在切面配置完成之前創(chuàng)建的@Configurable
對(duì)象實(shí)例會(huì)導(dǎo)致在log中留下一個(gè)warning,并且任何對(duì)于該對(duì)象的配置都不會(huì)生效。
舉一個(gè)例子,一個(gè)Spring管理配置的bean在被Spring初始化的時(shí)候創(chuàng)建了一個(gè)domain object。
對(duì)于這樣的情況,你需要定義bean屬性中的"depends-on"屬性來手動(dòng)指定該bean依賴于configuration切面。
<bean id="myService"
class="com.xzy.myapp.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
提供@Configurable
支持的一個(gè)目的就是使得domain object的單元測(cè)試
可以獨(dú)立進(jìn)行,不需要通過硬編碼查找各種倚賴關(guān)系。如果@Configurable
類型沒有通過AspectJ織入,則在單元測(cè)試過程中注解不會(huì)起到任何作用,
測(cè)試中你可以簡(jiǎn)單的為對(duì)象的mock或者stub屬性賦值,并且和正常情況一樣去使用該對(duì)象。
如果@Configurable
類型通過AspectJ織入,
我們依然可以脫離容器進(jìn)行單元測(cè)試,不過每次創(chuàng)建一個(gè)新的@Configurable
對(duì)象時(shí)都會(huì)看到一個(gè)warning,標(biāo)示該對(duì)象沒有被Spring配置。
AnnotationBeanConfigurerAspect
通過一個(gè)AspectJ singleton切面來實(shí)現(xiàn)對(duì)
@Configurable
的支持。一個(gè)singleton切面的作用域和一個(gè)
靜態(tài)變量的作用域是一樣的,那就是說,對(duì)于每一個(gè)classloader有一個(gè)切面來定義類型。
這就意味著如果你在一個(gè)classloader層次結(jié)構(gòu)中定義了多個(gè)application context的時(shí)候就需要考慮
在哪里定義<aop:spring-configured/>
bean和在哪個(gè)classpath下
放置spring-aspects.jar
。
考慮一下典型的Spring web項(xiàng)目,一般都是由一個(gè)父application context定義大部分business service和
所需要的其他資源,然后每一個(gè)servlet擁有一個(gè)子application context定義。所有這些context共存于
同一個(gè)classloader體系下,因此AnnotationBeanConfigurerAspect
僅保持
一個(gè)對(duì)context的引用。在這樣的情況下,我們推薦在父application context中定義
<aop:spring-configured/>
bean:這里所定義的service可能是
你希望注入domain object的。這樣做的結(jié)果是你不能為子application context中
使用@Configurable的domain object配置bean引用(可能你也根本就不希望那么做!)。
當(dāng)在一個(gè)容器中部署多個(gè)web-app的時(shí)候,請(qǐng)確保每一個(gè)web-application使用自己的classloader 來加載spring-aspects.jar中的類(例如將spring-aspects.jar放在WEB-INF/lib目錄下)。 如果spring-aspects.jar被放在了容器的classpath下(因此也被父classloader加載),則所有的 web application將共享一個(gè)aspect實(shí)例,這可能并不是你所想要的。
除了@Configurable
切面,
spring-aspects.jar
包含了一個(gè)AspectJ切面可以用來為
那些使用了@Transactional
注解的類型和方法驅(qū)動(dòng)Spring事務(wù)管理。
提供這個(gè)的主要目的是有些用戶希望脫離Spring容器使用Spring的事務(wù)管理。
解析@Transactional
注解的切面是
AnnotationTransactionAspect
。當(dāng)使用這個(gè)切面時(shí),
你必須注解這個(gè)實(shí)現(xiàn)類(和/或這個(gè)類中的方法),而不是
這個(gè)類實(shí)現(xiàn)的接口(如果有)。AspectJ允許在接口上注解的Java規(guī)則 不被繼承。
類之上的一個(gè)@Transactional
注解為該類中任何
public操作的執(zhí)行指定了默認(rèn)的事務(wù)語義。
類內(nèi)部方法上的一個(gè)@Transactional
注解會(huì)覆蓋類注解(如果存在)
所給定的默認(rèn)的事務(wù)語義。具有public、protected和default修飾符的方法都可以被注解。
直接注解protected和default方法是讓這個(gè)操作的執(zhí)行獲得事務(wù)劃分的唯一途徑。
對(duì)于AspectJ程序員,希望使用Spring管理配置和事務(wù)管理支持,不過他們不想(或者不能)使用注解,
spring-aspects.jar
也包含了一些抽象
切面供你繼承來提供你自己的切入點(diǎn)定義。參見AbstractBeanConfigurerAspect
和AbstractTransactionAspect
的Javadoc獲取更多信息。
作為一個(gè)例子,下面的代碼片斷展示了如何編寫一個(gè)切面,然后通過和類全名匹配的bean原型定義來
配置domian object中定義的所有實(shí)例:
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(..)) &&
SystemArchitecture.inDomainModel() &&
this(beanInstance);
}
當(dāng)在Spring application中使用AspectJ的時(shí)候,很自然的會(huì)想到用Spring來管理這些切面。 AspectJ runtime自身負(fù)責(zé)切面的創(chuàng)建,這意味著通過Spring來管理AspectJ 創(chuàng)建切面依賴于切面所使用的AspectJ instantiation model(per-clause)。
大多數(shù)AspectJ切面都是singleton切面。管理這些切面非常容易,
和通常一樣創(chuàng)建一個(gè)bean定義引用該切面類型就可以了,并且在bean定義中包含
'factory-method="aspectOf"'
這個(gè)屬性。
這確保Spring從AspectJ獲取切面實(shí)例而不是嘗試自己去創(chuàng)建該實(shí)例。示例如下:
<bean id="profiler" class="com.xyz.profiler.Profiler"
factory-method="aspectOf">
<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
non-singleton切面的配置稍難一點(diǎn),然而它可以通過定義一個(gè)bean原型定義并且使用
spring-aspects.jar
中的@Configurable支持,
當(dāng)切面實(shí)例由AspectJ runtime創(chuàng)建后進(jìn)行配置。
如果你希望一些@AspectJ切面使用AspectJ來織入(例如使用load-time織入domain object)
而另一些@AspectJ切面使用Spring AOP,并且這些切面都由Spring來管理,那你就需要告訴Spring AOP
@AspectJ自動(dòng)代理支持那些切面需要被自動(dòng)代理。你可以通過在
<aop:aspectj-autoproxy>
聲明中使用一個(gè)或多個(gè)
<include/>
元素。每個(gè)元素指定了一種命名格式,
只有bean命名至少符合其中一種情況下才會(huì)使用Spring AOP自動(dòng)代理配置:
<aop:aspectj-autoproxy> <aop:include name="thisBean"/> <aop:include name="thatBean"/> </aop:aspectj-autoproxy>
不要被<aop:aspectj-autoproxy/>
元素的名字所誤導(dǎo):
用它會(huì)導(dǎo)致Spring AOP 代理的創(chuàng)建。在這中只是使用@AspectJ
類型的切面聲明,但并不會(huì)涉及AspectJ運(yùn)行時(shí)。
加載時(shí)織入(Load-time weaving(LTW))指的是在虛擬機(jī)載入字節(jié)碼文件時(shí)動(dòng)態(tài)織入AspectJ切面。 本 節(jié)關(guān)注于在Spring Framework中特的定context下配置和使用LTW:并沒有LTW的介紹。 關(guān)于LTW和僅使用AspectJ配置LTW的詳細(xì)信息(根本不涉及Spring),請(qǐng)查看 LTW section of the AspectJ Development Environment Guide。
Spring框架的值添加為AspectJ LTW在動(dòng)態(tài)織入過程中提供了更細(xì)粒度的控制。使用Java(5+)的代理
能使用一個(gè)叫‘Vanilla’的AspectJ LTW,這需要在啟動(dòng)JVM的時(shí)候?qū)⒛硞€(gè)VM參數(shù)設(shè)置為開。
這種JVM范圍的設(shè)置在一些情況下或許不錯(cuò),但通常情況下顯得有些粗顆粒。而用Spring的LTW能讓你在
per-ClassLoader
的基礎(chǔ)上打開LTW,
這顯然更加細(xì)粒度并且對(duì)“單JVM多應(yīng)用”的環(huán)境更具意義(例如在一個(gè)典型應(yīng)用服務(wù)器環(huán)境中一樣)。
另外,在某些環(huán)境下,這能讓你使用LTW而 不對(duì)應(yīng)用服務(wù)器的啟動(dòng)腳本做任何改動(dòng),不然則需要添加 -javaagent:path/to/aspectjweaver.jar或者(以下將會(huì)提及的)-javaagent:path/to/spring-agent.jar。 開發(fā)人員只需簡(jiǎn)單修改應(yīng)用上下文的一個(gè)或幾個(gè)文件就能使用LTW,而不需依靠那些管理著部署配置 比如啟動(dòng)腳本的系統(tǒng)管理員。
經(jīng)過以上講解之后,先讓我們來過一遍一個(gè)使用Spring的AspectJ LTW的快速示例,接著是一個(gè) 有對(duì)元素詳細(xì)講解的示例。如果想要一個(gè)完整的示例,請(qǐng)參看Petclinic(寵物診所)的應(yīng)用實(shí)例。
假設(shè)你是一個(gè)應(yīng)用開人員,被指派診斷一個(gè)系統(tǒng)的若干性能問題。與其拿出性能分析工具, 我們不如開啟一個(gè)簡(jiǎn)單的分析切面,使我們能很快地得到一些性能指標(biāo),這樣我們就能馬上 針對(duì)特定區(qū)域使用一些較細(xì)粒度的分析工具。
這就是一個(gè)分析切面。沒什么特別的,只是一個(gè)快餐式的基于時(shí)間的模擬分析器, 使用類@AspectJ風(fēng)格的切面聲明。
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(){} }
我們還需要?jiǎng)?chuàng)建一個(gè)“META-INF/aop.xml
”文件,以告知AspectJ weaver
我們要把ProfilingAspect
織入到類中。這個(gè)文件慣例,即在Java classpath中
出現(xiàn)一個(gè)文件稱作“META-INF/aop.xml
”是標(biāo)準(zhǔn)的AspectJ。
<!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>
現(xiàn)在來看Spring特定的配置部分。我們需要配置一個(gè)LoadTimeWeaver
(稍后會(huì)有解釋,暫時(shí)不多深究)。當(dāng)將一個(gè)或多個(gè)“META-INF/aop.xml
”文件中的切面
配置織入你的應(yīng)用程序的類中時(shí),這個(gè)加載時(shí)織入器是必須的。這樣的好處是不需要很多的配置,
正如下面你看到的一樣(還有另外一些參數(shù)供你指定,我們將在后面詳細(xì)介紹)。
<?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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <!-- a service object; we will be profiling it's methods --> <bean id="entitlementCalculationService" class="foo.StubEntitlementCalculationService"/> <!-- this switches on the load-time weaving --> <context:load-time-weaver/> </beans>
現(xiàn)在萬事俱備 - 切面,META-INF/aop.xml
文件,以及Spring的配置 -
讓我們創(chuàng)建一個(gè)帶有main(..)
方法的簡(jiǎn)單驅(qū)動(dòng)類來演示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();
}
}
最后還有一件事要做。此節(jié)之前的介紹說過可以有選擇性的基于Spring的
per-ClassLoader
來啟動(dòng)LTW,而且的確如此。不過,對(duì)此例來說,
我們將使用Java代理(由Spring提供)來啟動(dòng)LTW。這個(gè)就是用以運(yùn)行上面Main
類的命令行語句:
java -javaagent:C:/projects/foo/lib/global/spring-agent.jar foo.Main
-javaagent
是一個(gè)Java 5+標(biāo)記,用來指定和激活
使JVM上的程序運(yùn)行的代理。Spring框架裝載了一個(gè)InstrumentationSavingAgent
代理,在上面的例子中被作為了-javaagent
參數(shù)的值打包在
spring-agent.jar
中。
Main
程序運(yùn)行的輸出如下所示。(我已經(jīng)在
calculateEntitlement()
的實(shí)現(xiàn)中插入了Thread.sleep(..)
語句,以免讓模擬分析器獲取0毫秒 - 這里的01234
毫秒并非是AOP引入的系統(tǒng)開銷。)
Calculating entitlement StopWatch 'ProfilingAspect': running time (millis) = 1234 ------ ----- ---------------------------- ms % Task name ------ ----- ---------------------------- 01234 100% calculateEntitlement
因?yàn)檫@個(gè)LTW使用成熟的AspectJ,我們并不局限于通知Spring beans的方法;接下來這個(gè)稍有變化的
Main
程序?qū)⑸赏瑯拥慕Y(jié)果。
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();
}
}
注意以上程序我們只是引導(dǎo)了Spring容器,然后完全在Spring上下文之外創(chuàng)建了一個(gè)
StubEntitlementCalculationService
的實(shí)例...分析通知仍然得到織入。
上面的例子雖然簡(jiǎn)單了些,但Spring中基本的LTW支持都已介紹完了, 此節(jié)余下內(nèi)容將對(duì)使用這些配置和用法背后的理由作詳細(xì)解釋。
類ProfilingAspect
在此例中雖然基本但是頗為有用。這是一個(gè)很好的開發(fā)時(shí)切面的例子,開發(fā)者可以在開發(fā)過程中使用它(廢話),
然后也能從已部署到UAT或者生產(chǎn)環(huán)境的應(yīng)用中輕易的脫離。
你在LTW中使用的切面必須是AspectJ切面。你可以使用AspectJ語言或者類@AspectJ風(fēng)格來編寫你的切面。 后一種方式當(dāng)然只能在Java 5+中使用,但它說明了你的切面可以同時(shí)對(duì)AspectJ和Spring AOP切面有效。 此外,編譯后的切面類需要被注冊(cè)到classpath下。
AspectJ LTW的基礎(chǔ)設(shè)施是用一個(gè)或多個(gè)位于Java classpath上的(可以是直接的文件形式,
也可以是更典型的jar包形式)META-INF/aop.xml
文件配置起來的。
有關(guān)文件的結(jié)構(gòu)和內(nèi)容都在AspectJ的參考文檔中有詳細(xì)介紹,有興趣的讀者
請(qǐng)參考這些資源。(很慶幸這一節(jié)比較簡(jiǎn)短,但aop.xml
文件
是100% AspectJ的 - 沒有任何使用Spring特定的信息或語義,因此我也沒有什么可貢獻(xiàn)的。
與其重寫這些已由AspectJ開發(fā)者提供的令人滿意的章節(jié),我不如領(lǐng)你到這里。)
你至少需要以下類庫來讓Spring框架支持AspectJ LTW:
spring.jar
(2.5或更高版本)
aspectjrt.jar
(1.5或更高版本)
aspectjweaver.jar
(1.5或更高版本)
如果你正在使用 由Spring提供的代理來激活檢測(cè)(instrumentation)功能,你會(huì)需要:
spring-agent.jar
Spring LTW功能的關(guān)鍵組件是LoadTimeWeaver
接口
(在org.springframework.instrument.classloading
包中),
以及Spring分發(fā)包中大量的實(shí)現(xiàn)。LoadTimeWeaver
的實(shí)現(xiàn)負(fù)責(zé)
在運(yùn)行時(shí)把一個(gè)或多個(gè)java.lang.instrument.ClassFileTransformers
類添加到
ClassLoader
中,這能產(chǎn)生各種各樣有趣的應(yīng)用,LTW切面恰好便是其中之一。
如果你對(duì)運(yùn)行時(shí)類文件變換的思想還不熟悉,推薦你在繼續(xù)之前閱讀
java.lang.instrument
包的Javadoc API文檔。
這其實(shí)并不難-反而有些惱人-因?yàn)橛杏玫奈募⒉欢?..關(guān)鍵的接口和類都將會(huì)在此節(jié)呈現(xiàn)給你。
用XML為ApplicationContext
配置一個(gè)
LoadTimeWeaver
簡(jiǎn)單得只需要添加一行。
(請(qǐng)注意幾乎肯定你需要使用ApplicationContext
作為你的
Spring容器 - 一般來說只有BeanFactory
是不夠的,
因?yàn)長(zhǎng)TW功能需要用到BeanFactoryPostProcessors
。)
當(dāng)要使用Spring框架的LTW功能時(shí),你需要配置一個(gè)LoadTimeWeaver
,
一般可以用<context:load-time-weaver/>
元素來完成。
下面為一個(gè)有效的使用默認(rèn)設(shè)置的<context:load-time-weaver/>
定義。
<?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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:load-time-weaver/> </beans>
上面<context:load-time-weaver/>
bean的定義會(huì)自動(dòng)為你定義和注冊(cè)若干
特定LTW的基礎(chǔ)設(shè)施beans,比如一個(gè)LoadTimeWeaver
和一個(gè)AspectJWeavingEnabler
。請(qǐng)注意
<context:load-time-weaver/>
是怎樣在context
命名空間下被定義的;還要注意被引用的XML Schema文件只在Spring 2.5或更高版本中才可用。
上面的配置為你定義并注冊(cè)了一個(gè)默認(rèn)的LoadTimeWeaver
bean。
默認(rèn)的LoadTimeWeaver
是一個(gè)
DefaultContextLoadTimeWeaver
類,它更傾向于去裝飾一個(gè)能自動(dòng)檢測(cè)的LoadTimeWeaver
類:LoadTimeWeaver
的確切類型會(huì)根據(jù)你的運(yùn)行時(shí)環(huán)境“自動(dòng)檢測(cè)”出來(概述如下表)。
表?6.1.?DefaultContextLoadTimeWeaver
LoadTimeWeavers
DefaultContextLoadTimeWeaver
類和LoadTimeWeavers
接口
運(yùn)行時(shí)環(huán)境 |
LoadTimeWeaver 的接口實(shí)現(xiàn) |
---|---|
BEA's Weblogic 10環(huán)境下 |
|
Oracle's OC4J環(huán)境下 |
|
GlassFish環(huán)境下 |
|
以Spring
|
|
不過,我們更希望這些類加載器能遵循共同的規(guī)范
(例如適用 |
|
請(qǐng)注意當(dāng)使用DefaultContextLoadTimeWeaver
時(shí)只有
LoadTimeWeavers
實(shí)現(xiàn)類能進(jìn)行自動(dòng)檢測(cè):
當(dāng)然,你也可以通過指定將類的完全限定名作為<context:load-time-weaver/>
元素中weaver-class
屬性的值
來指定究竟想使用哪個(gè)LoadTimeWeaver
的實(shí)現(xiàn)。如下例:
<?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-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:load-time-weaver
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</beans>
在<context:load-time-weaver/>
元素上定義和注冊(cè)的
LoadTimeWeaver
接口可以在Spring容器中以
loadTimeWeaver
名字找到。
記住LoadTimeWeaver
接口只是作為Spring LTW基礎(chǔ)設(shè)施的一個(gè)機(jī)制
用來添加一個(gè)或多個(gè)ClassFileTransformers
的。
ClassFileTransformer
類實(shí)際是利用
ClassPreProcessorAgentAdapter
類(包含在
org.aspectj.weaver.loadtime
中)來進(jìn)行LTW的。
有關(guān)ClassPreProcessorAgentAdapter
的細(xì)節(jié)請(qǐng)參見
類級(jí)別的javadoc,織入實(shí)際怎樣生效的具體內(nèi)容已經(jīng)超出本節(jié)討論范圍。
讓我們來討論<context:load-time-weaver/>
的最后一個(gè)屬性:
aspectj-weaving
。 這是一個(gè)簡(jiǎn)單的LTW開關(guān),就這么簡(jiǎn)單。
它可以接受如下所述的三種值,如果不顯示設(shè)置此屬性則其默認(rèn)值為autodetect
表?6.2.?aspectj-weaving
屬性值
屬性值 | 注釋 |
---|---|
|
AspectJ織入功能開啟,切面將會(huì)在加載時(shí)適當(dāng)時(shí)機(jī)被織入。 |
|
LTW功能關(guān)閉...不會(huì)在加載時(shí)織入切面。 |
|
如果Spring LTW基礎(chǔ)設(shè)施能找到至少一個(gè) |
這最后一節(jié)包括所有你在諸如應(yīng)用服務(wù)器和web容器中使用Spring的LTW功能時(shí)需要的額外設(shè)置和配置。
你可能在各種Java應(yīng)用中通過使用由Spring提供的檢測(cè)代理啟用Spring的LTW功能
(獨(dú)立應(yīng)用或者基于應(yīng)用服務(wù)器的應(yīng)用)。這樣的話,可以通過指定
-javaagent:path/to/spring-agent.jar
選項(xiàng)來啟動(dòng)虛擬機(jī)。
請(qǐng)注意這需要修改虛擬機(jī)的啟動(dòng)腳本,但在某些應(yīng)用服務(wù)器環(huán)境下是禁止這么做的
(這取決于你的操作策略)。
對(duì)于部署在Apache Tomcat 5.0或更高版本上的web應(yīng)用,Spring將一個(gè)
TomcatInstrumentableClassLoader
注冊(cè)成為web應(yīng)用的類加載器。
必須的Tomcat設(shè)置如下所示,你可以把它放在Tomcat WAR包根目錄下的核心文件
server.xml
中或放到應(yīng)用特定的META-INF/context.xml
文件中。
Spring的spring-tomcat-weaver.jar
需要被包含到Tomcat
的common lib路徑下以確保設(shè)置生效。
<Context path="/myWebApp" docBase="/my/webApp/location"> <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader" useSystemClassLoaderAsParent="false"/> </Context>
注意:當(dāng)使用LTW時(shí),我們一般推薦使用Tomcat 5.5.20或更高版本。
先前的版本對(duì)定制的ClassLoader
設(shè)置會(huì)產(chǎn)生問題。
另外,請(qǐng)考慮使用在Tomcat啟動(dòng)腳本中(見上面)指定由Spring提供的通用虛擬機(jī)代理。 這樣才能使檢測(cè)功能在所有已部署的web應(yīng)用中可用,無論其上運(yùn)行的是哪種類加載器。
有關(guān)更多基于Tomcat織入設(shè)置的詳細(xì)討論,請(qǐng)參考討論各種不同Tomcat版本內(nèi)容的 第?12.6.1.3.1?節(jié) “Tomcat(5.0以上)加載時(shí)的織入配置”一節(jié)。雖然本節(jié)主要關(guān)注于 JPA persistence提供者的設(shè)置,但也談到了Tomcat各種特定設(shè)置適用于一般加載時(shí)織入的情況。
BEA WebLogic(版本10或更高),Oracle的JavaEE容器(OC4J 10.1.3.1或更高)以及
Resin(版本3.1或更高)提供具有本地檢測(cè)能力的類加載器。
Srping的原生LTW利用這些類加載器來激活A(yù)spectJ織入。你可以通過簡(jiǎn)單地激活之前提到的
context:load-time-weaver
來啟動(dòng)LTW功能。具體來說,即你
不需要通過修改啟動(dòng)腳本來添加
-javaagent:path/to/spring-agent.jar
。
GlassFish同樣也提供了檢測(cè)能力的類加載器,不過只能在它的EAR環(huán)境下使用。 對(duì)于GlassFish的web應(yīng)用,可以使用跟上面tomcat相同的設(shè)置。