Spring之LoadTimeWeaver——一個需求引發(fā)的思考

最近有個需求——記錄應(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旗唁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子痹束,更是在濱河造成了極大的恐慌检疫,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祷嘶,死亡現(xiàn)場離奇詭異屎媳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)论巍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門烛谊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嘉汰,你說我怎么就攤上這事丹禀。” “怎么了鞋怀?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵双泪,是天一觀的道長。 經(jīng)常有香客問我接箫,道長攒读,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任辛友,我火速辦了婚禮薄扁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘废累。我一直安慰自己邓梅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布邑滨。 她就那樣靜靜地躺著日缨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掖看。 梳的紋絲不亂的頭發(fā)上匣距,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天面哥,我揣著相機(jī)與錄音,去河邊找鬼毅待。 笑死尚卫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尸红。 我是一名探鬼主播吱涉,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼外里!你這毒婦竟也來了怎爵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤盅蝗,失蹤者是張志新(化名)和其女友劉穎鳖链,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體风科,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡撒轮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贼穆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡兰粉,死狀恐怖故痊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情玖姑,我是刑警寧澤愕秫,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站焰络,受9級特大地震影響戴甩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜闪彼,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一甜孤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧畏腕,春花似錦缴川、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铭污,卻和暖如春恋日,著一層夾襖步出監(jiān)牢的瞬間膀篮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工岂膳, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留各拷,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓闷营,卻偏偏與公主長得像烤黍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子傻盟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容