javascript
Spring之LoadTimeWeaver——一个需求引发的思考---转
原文地址:http://www.myexception.cn/software-architecture-design/602651.html
Spring之LoadTimeWeaver——一個(gè)需求引發(fā)的思考
最近有個(gè)需求——記錄應(yīng)用中某些接口被調(diào)用的軌跡,說(shuō)白了,記錄下入?yún)ⅰ⒊鰠⒌燃纯伞?/p>
?
我選用ApsectJ解決這個(gè)問(wèn)題,前期討論說(shuō)在接口層埋點(diǎn),但這樣有個(gè)問(wèn)題,代碼侵入比較嚴(yán)重,需要修改每個(gè)需要關(guān)注的接口實(shí)現(xiàn)類。經(jīng)過(guò)一番討論,決定使用AOP攔截所有這樣的接口。
?
后面又有個(gè)新的要求——沙箱環(huán)境攔截,生產(chǎn)環(huán)境不予攔截。
?
這樣就有個(gè)眼前的問(wèn)題需要我們解決,就是同一份應(yīng)用包如何區(qū)分沙箱環(huán)境和生產(chǎn)環(huán)境并執(zhí)行不同的行為。同事提醒我可以考慮Spring的LTW,即Load Time Weaving。
?
在Java?語(yǔ)言中,從織入切面的方式上來(lái)看,存在三種織入方式:編譯期織入、類加載期織入和運(yùn)行期織入。編譯期織入是指在Java編譯期,采用特殊的編譯器,將切面織入到Java類中;而類加載期織入則指通過(guò)特殊的類加載器,在類字節(jié)碼加載到JVM時(shí),織入切面;運(yùn)行期織入則是采用CGLib工具或JDK動(dòng)態(tài)代理進(jìn)行切面的織入。?
AspectJ采用編譯期織入和類加載期織入的方式織入切面,是語(yǔ)言級(jí)的AOP實(shí)現(xiàn),提供了完備的AOP支持。它用AspectJ語(yǔ)言定義切面,在編譯期或類加載期將切面織入到Java類中。?
?
AspectJ提供了兩種切面織入方式,第一種通過(guò)特殊編譯器,在編譯期,將AspectJ語(yǔ)言編寫的切面類織入到Java類中,可以通過(guò)一個(gè)Ant或Maven任務(wù)來(lái)完成這個(gè)操作;第二種方式是類加載期織入,也簡(jiǎn)稱為L(zhǎng)TW(Load Time Weaving)。?
?
如何使用Load Time Weaving?首先,需要通過(guò)JVM的-javaagent參數(shù)設(shè)置LTW的織入器類包,以代理JVM默認(rèn)的類加載器;第二,LTW織入器需要一個(gè)?aop.xml文件,在該文件中指定切面類和需要進(jìn)行切面織入的目標(biāo)類。?
?
下面我將通過(guò)一個(gè)簡(jiǎn)單的例子在描述如何使用LTW。
?
例子所作的是記錄被調(diào)用方法的執(zhí)行時(shí)間和CPU使用率。其實(shí)這在實(shí)際生產(chǎn)中很有用,與其拉一堆性能測(cè)試工具,不如動(dòng)手做個(gè)簡(jiǎn)單的分析切面,使我們能很快得到一些性能指標(biāo)。我指的是沒(méi)有硬性的性能測(cè)試需求下。
?
首先我們編寫一個(gè)被織入的受體類,也就是被攔截的對(duì)象。
?
?
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() {} }?
前文提到,我們還需要一個(gè)aop.xml。這個(gè)文件要求放在META-INF/aop.xml路徑下,以告知AspectJ Weaver我們需要把ProfilingAspect織入到應(yīng)用的哪些類中。
?
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"> <aspectj><weaver><include within="com.shansun..*" /></weaver><aspects><!-- weave in just this aspect --><aspect name="com.shansun.multidemo.spring.ltw.ProfilingAspect" /></aspects> </aspectj>?
目前為止,本次切面的“攻”和“受”都準(zhǔn)備好了,我們還需要一個(gè)中間媒介——LoadTimeWeaver。我們將Spring的配置文件添加紅色標(biāo)識(shí)內(nèi)容。
?
<?xml version="1.0" encoding="GBK"?><beans xmlns="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="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"><context:load-time-weaver aspectj-weaving="autodetect" /><context:component-scan base-package="com.shansun.multidemo"></context:component-scan> </beans>?
通過(guò)?<context:load-time-weaver??aspectj-weaving="on" />?使?spring?開(kāi)啟?loadtimeweaver,?注意aspectj-weaving?有三個(gè)選項(xiàng)?: on, off, auto-detect,?如果設(shè)置為?auto-detect, spring?將會(huì)在?classpath?中查找?aspejct?需要的?META-INF/aop.xml,?如果找到則開(kāi)啟?aspectj weaving,?這個(gè)邏輯在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)建一個(gè)main方法來(lái)掩飾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();} }??
?
因?yàn)檫@個(gè)LTW使用成熟的AspectJ,我們并不局限于通知Spring beans的方法。所以上述代碼中從ApplicationContext中獲取Bean和直接實(shí)例化一個(gè)Bean的效果是一樣的。
????????
注意,這里以使用Eclipse演示上述代碼為例,需要在運(yùn)行參數(shù)中稍作設(shè)置,即添加前文提到的-javaagent,來(lái)取代默認(rèn)的類加載器。
?
?
?
輸出結(jié)果如下:
Run
StopWatch 'ProfilingAspect': running time (millis) = 0
-----------------------------------------
ms?????%?????Task name
-----------------------------------------
?? ? ?0001?100%??run
?
至此,LTW可以正常使用了,但是麻煩的是我需要在VM參數(shù)里加上-javaagent這么個(gè)東東,如果不加會(huì)如何呢?試試看。
?
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.<init>(ClassPathXmlApplicationContext.java:139)at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)at com.shansun.multidemo.spring.Main.main(Main.java:25)必需使用java agent么?仔細(xì)觀察異常的出處InstrumentationLoadTimeWeaver。再深入這個(gè)類的內(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)它在校驗(yàn)instrumentation是否為空的時(shí)候拋出的異常。有辦法啦,重寫InstrumentationLoadTimeWeaver的addTransformer方法,隱匿異常即可。
?
?
public class ExtInstrumentationLoadTimeWeaver extendsInstrumentationLoadTimeWeaver {@Overridepublic void addTransformer(ClassFileTransformer transformer) {try {super.addTransformer(transformer);} catch (Exception e) {}} }?
???
這時(shí),我們還需要做一件事,將Spring配置文件中的load-time-weaver入口設(shè)置為我們剛自定義的ExtInstrumentationLoadTimeWeaver即可。
?
?
<?xml version="1.0" encoding="GBK"?><beans xmlns="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="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"><context:load-time-weaver weaver-class="com.shansun.multidemo.spring.ExtInstrumentationLoadTimeWeaver" aspectj-weaving="autodetect" /><context:component-scan base-package="com.shansun.multidemo"></context:component-scan> </beans>?
?
再次運(yùn)行我們的main方法,發(fā)現(xiàn)只輸出了如下結(jié)果,切面沒(méi)有起作用。
Run
?
看到了么,同一份代碼、同一份配置,只需要在VM啟動(dòng)參數(shù)中稍加變化,即可實(shí)現(xiàn)同一個(gè)應(yīng)用包在不同環(huán)境下可以自由選擇使用使用AOP功能。文章開(kāi)頭提到的那個(gè)需求也就迎刃而解了。
?
這只是一種解決途徑,相信大家會(huì)有更好的方案。如果有,請(qǐng)您告訴我。J
?
參考文檔:
1、使用AspectJ LTW(Load Time Weaving)
2、Spring LoadTimeWeaver?的那些事兒
3、在Spring應(yīng)用中使用AspectJ
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/5633609.html
總結(jié)
以上是生活随笔為你收集整理的Spring之LoadTimeWeaver——一个需求引发的思考---转的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: spring 源码分析之BeanPost
- 下一篇: annotation-config vs