最近有個需求——記錄應(yīng)用中某些接口被調(diào)用的軌跡萝衩,說白了,記錄下入?yún)⒅壕巍⒊鰠⒌燃纯伞?/p>
我選用ApsectJ解決這個問題缨历,前期討論說在接口層埋點,但這樣有個問題糙麦,代碼侵入比較嚴(yán)重辛孵,需要修改每個需要關(guān)注的接口實現(xiàn)類。經(jīng)過一番討論赡磅,決定使用AOP攔截所有這樣的接口魄缚。
后面又有個新的要求——沙箱環(huán)境攔截,生產(chǎn)環(huán)境不予攔截仆邓。
這樣就有個眼前的問題需要我們解決鲜滩,就是同一份應(yīng)用包如何區(qū)分沙箱環(huán)境和生產(chǎn)環(huán)境并執(zhí)行不同的行為。同事提醒我可以考慮Spring的LTW节值,即Load Time Weaving徙硅。
在Java 語言中,從織入切面的方式上來看搞疗,存在三種織入方式:編譯期織入嗓蘑、類加載期織入和運(yùn)行期織入须肆。編譯期織入是指在Java編譯期,采用特殊的編譯器桩皿,將切面織入到Java類中豌汇;而類加載期織入則指通過特殊的類加載器,在類字節(jié)碼加載到JVM時泄隔,織入切面拒贱;運(yùn)行期織入則是采用CGLib工具或JDK動態(tài)代理進(jìn)行切面的織入。
AspectJ采用編譯期織入和類加載期織入的方式織入切面佛嬉,是語言級的AOP實現(xiàn)逻澳,提供了完備的AOP支持。它用AspectJ語言定義切面暖呕,在編譯期或類加載期將切面織入到Java類中斜做。
AspectJ提供了兩種切面織入方式,第一種通過特殊編譯器湾揽,在編譯期瓤逼,將AspectJ語言編寫的切面類織入到Java類中,可以通過一個Ant或Maven任務(wù)來完成這個操作库物;第二種方式是類加載期織入霸旗,也簡稱為LTW(Load Time Weaving)。
如何使用Load Time Weaving艳狐?首先定硝,需要通過JVM的-javaagent參數(shù)設(shè)置LTW的織入器類包,以代理JVM默認(rèn)的類加載器毫目;第二蔬啡,LTW織入器需要一個 aop.xml文件,在該文件中指定切面類和需要進(jìn)行切面織入的目標(biāo)類镀虐。
下面我將通過一個簡單的例子在描述如何使用LTW箱蟆。
例子所作的是記錄被調(diào)用方法的執(zhí)行時間和CPU使用率。其實這在實際生產(chǎn)中很有用刮便,與其拉一堆性能測試工具空猜,不如動手做個簡單的分析切面,使我們能很快得到一些性能指標(biāo)恨旱。我指的是沒有硬性的性能測試需求下辈毯。
首先我們編寫一個被織入的受體類,也就是被攔截的對象搜贤。
public class DemoBean {
public void run() {
System.out.println("Run");
}
}
接著谆沃,我們編寫分析方法執(zhí)行效率的切面。
@Aspect
public class ProfilingAspect {
@Around("profileMethod()")
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.shansun..*(..))")
public void profileMethod() {}
}
前文提到仪芒,我們還需要一個aop.xml唁影。這個文件要求放在META-INF/aop.xml路徑下耕陷,以告知AspectJ Weaver我們需要把ProfilingAspect織入到應(yīng)用的哪些類中。
http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
目前為止据沈,本次切面的“攻”和“受”都準(zhǔn)備好了哟沫,我們還需要一個中間媒介——LoadTimeWeaver。我們將Spring的配置文件添加紅色標(biāo)識內(nèi)容锌介。
http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="Index of /schema/context" xmlns:tx="Index of /schema/tx"
xsi:schemaLocation="
Index of /schema/beans?http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
Index of /schema/aop?http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
Index of /schema/context?http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx?http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
通過<context:load-time-weaveraspectj-weaving="on" />使spring開啟loadtimeweaver,注意aspectj-weaving有三個選項: on, off, auto-detect, 如果設(shè)置為auto-detect, spring將會在classpath中查找aspejct需要的META-INF/aop.xml嗜诀,如果找到則開啟aspectj weaving,這個邏輯在LoadTimeWeaverBeanDefinitionParser#isAspectJWeavingEnabled方法中:
protected boolean isAspectJWeavingEnabled(String value, ParserContext parserContext) {
if ("on".equals(value)) {
return true;
}
else if ("off".equals(value)) {
return false;
}
else {
// Determine default...
ClassLoader cl = parserContext.getReaderContext().getResourceLoader().getClassLoader();
return (cl.getResource(ASPECTJ_AOP_XML_RESOURCE) != null);
}
}
一切都準(zhǔn)備就緒——切面類、aop.xml掏湾、Spring的配置裹虫,我們就創(chuàng)建一個main方法來掩飾LTW的功效吧肿嘲。
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// DemoBean bean = (DemoBean) ctx.getBean("demoBean");
DemoBean bean = new DemoBean();
bean.run();
}
}
因為這個LTW使用成熟的AspectJ融击,我們并不局限于通知Spring beans的方法。所以上述代碼中從ApplicationContext中獲取Bean和直接實例化一個Bean的效果是一樣的雳窟。
注意尊浪,這里以使用Eclipse演示上述代碼為例,需要在運(yùn)行參數(shù)中稍作設(shè)置封救,即添加前文提到的-javaagent拇涤,來取代默認(rèn)的類加載器。
輸出結(jié)果如下:
Run
StopWatch 'ProfilingAspect': running time (millis) = 0
-----------------------------------------
ms?%?Task name
-----------------------------------------
0001?100%?run
至此誉结,LTW可以正常使用了鹅士,但是麻煩的是我需要在VM參數(shù)里加上-javaagent這么個東東,如果不加會如何呢惩坑?試試看掉盅。
Exception in thread "main" java.lang.IllegalStateException: Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.
at org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver.addTransformer(InstrumentationLoadTimeWeaver.java:88)
at org.springframework.context.weaving.AspectJWeavingEnabler.postProcessBeanFactory(AspectJWeavingEnabler.java:69)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:553)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:536)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:362)
at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:83)
at com.shansun.multidemo.spring.Main.main(Main.java:25)
必需使用java agent么?仔細(xì)觀察異常的出處InstrumentationLoadTimeWeaver以舒。再深入這個類的內(nèi)容趾痘,發(fā)現(xiàn)異常是由下述方法拋出的。
public void addTransformer(ClassFileTransformer transformer) {
Assert.notNull(transformer, "Transformer must not be null");
FilteringClassFileTransformer actualTransformer =
new FilteringClassFileTransformer(transformer, this.classLoader);
synchronized (this.transformers) {
if (this.instrumentation == null) {
throw new IllegalStateException(
"Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.");
}
this.instrumentation.addTransformer(actualTransformer);
this.transformers.add(actualTransformer);
}
}
不難發(fā)現(xiàn)它在校驗instrumentation是否為空的時候拋出的異常蔓钟。有辦法啦永票,重寫InstrumentationLoadTimeWeaver的addTransformer方法,隱匿異常即可滥沫。
public class ExtInstrumentationLoadTimeWeaver extends
InstrumentationLoadTimeWeaver {
@Override
public void addTransformer(ClassFileTransformer transformer) {
try {
super.addTransformer(transformer);
} catch (Exception e) {}
}
}
這時侣集,我們還需要做一件事,將Spring配置文件中的load-time-weaver入口設(shè)置為我們剛自定義的ExtInstrumentationLoadTimeWeaver即可兰绣。
http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="Index of /schema/aop"
xmlns:context="Index of /schema/context" xmlns:tx="Index of /schema/tx"
xsi:schemaLocation="
Index of /schema/beans?http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
Index of /schema/aop?http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
Index of /schema/context?http://www.springframework.org/schema/context/spring-context-2.5.xsd
Index of /schema/tx?http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
再次運(yùn)行我們的main方法世分,發(fā)現(xiàn)只輸出了如下結(jié)果,切面沒有起作用狭魂。
Run
看到了么罚攀,同一份代碼党觅、同一份配置,只需要在VM啟動參數(shù)中稍加變化斋泄,即可實現(xiàn)同一個應(yīng)用包在不同環(huán)境下可以自由選擇使用使用AOP功能杯瞻。文章開頭提到的那個需求也就迎刃而解了。
這只是一種解決途徑炫掐,相信大家會有更好的方案魁莉。如果有,請您告訴我募胃。J