Spring之LoadTimeWeaver——一個需求引發的思考---轉


原文地址:http://www.myexception.cn/software-architecture-design/602651.html

Spring之LoadTimeWeaver——一個需求引發的思考

最近有個需求——記錄應用中某些接口被調用的軌跡,說白了,記錄下入參、出參等即可。

 

我選用ApsectJ解決這個問題,前期討論說在接口層埋點,但這樣有個問題,代碼侵入比較嚴重,需要修改每個需要關注的接口實現類。經過一番討論,決定使用AOP攔截所有這樣的接口。

 

后面又有個新的要求——沙箱環境攔截,生產環境不予攔截。

 

這樣就有個眼前的問題需要我們解決,就是同一份應用包如何區分沙箱環境和生產環境並執行不同的行為。同事提醒我可以考慮Spring的LTW,即Load Time Weaving。

 

在Java 語言中,從織入切面的方式上來看,存在三種織入方式:編譯期織入、類加載期織入和運行期織入。編譯期織入是指在Java編譯期,采用特殊的編譯器,將切面織入到Java類中;而類加載期織入則指通過特殊的類加載器,在類字節碼加載到JVM時,織入切面;運行期織入則是采用CGLib工具或JDK動態代理進行切面的織入。 


AspectJ采用編譯期織入和類加載期織入的方式織入切面,是語言級的AOP實現,提供了完備的AOP支持。它用AspectJ語言定義切面,在編譯期或類加載期將切面織入到Java類中。 

 

AspectJ提供了兩種切面織入方式,第一種通過特殊編譯器,在編譯期,將AspectJ語言編寫的切面類織入到Java類中,可以通過一個Ant或Maven任務來完成這個操作;第二種方式是類加載期織入,也簡稱為LTW(Load Time Weaving)。 

 

如何使用Load Time Weaving?首先,需要通過JVM的-javaagent參數設置LTW的織入器類包,以代理JVM默認的類加載器;第二,LTW織入器需要一個 aop.xml文件,在該文件中指定切面類和需要進行切面織入的目標類。 

 

下面我將通過一個簡單的例子在描述如何使用LTW。

 

例子所作的是記錄被調用方法的執行時間和CPU使用率。其實這在實際生產中很有用,與其拉一堆性能測試工具,不如動手做個簡單的分析切面,使我們能很快得到一些性能指標。我指的是沒有硬性的性能測試需求下。

 

首先我們編寫一個被織入的受體類,也就是被攔截的對象。

 

 

public class DemoBean {
    public void run() {
       System.out.println("Run");
    }
}

   

接着,我們編寫分析方法執行效率的切面。

 

@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織入到應用的哪些類中。

 

<!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>

 

目前為止,本次切面的“攻”和“受”都准備好了,我們還需要一個中間媒介——LoadTimeWeaver。我們將Spring的配置文件添加紅色標識內容。

 

<?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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
        http://www.springframework.org/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-weaver aspectj-weaving="autodetect" />
 
    <context:component-scan base-package="com.shansun.multidemo"></context:component-scan>
</beans>

 

通過 <context:load-time-weaver  aspectj-weaving="on" /> 使 spring 開啟 loadtimeweaver, 注意aspectj-weaving 有三個選項 : on, off, auto-detect, 如果設置為 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);
       }
}

 

一切都准備就緒——切面類、aop.xml、Spring的配置,我們就創建一個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演示上述代碼為例,需要在運行參數中稍作設置,即添加前文提到的-javaagent,來取代默認的類加載器。

 

 

 

輸出結果如下:

Run

StopWatch 'ProfilingAspect': running time (millis) = 0

-----------------------------------------

ms     %     Task name

-----------------------------------------

      0001 100%  run

 

至此,LTW可以正常使用了,但是麻煩的是我需要在VM參數里加上-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.<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么?仔細觀察異常的出處InstrumentationLoadTimeWeaver。再深入這個類的內容,發現異常是由下述方法拋出的。

 

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);
       }
}

 

不難發現它在校驗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入口設置為我們剛自定義的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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
        http://www.springframework.org/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-weaver weaver-class="com.shansun.multidemo.spring.ExtInstrumentationLoadTimeWeaver" aspectj-weaving="autodetect" />
 
    <context:component-scan base-package="com.shansun.multidemo"></context:component-scan>
</beans>

 

 

再次運行我們的main方法,發現只輸出了如下結果,切面沒有起作用。

Run

 

看到了么,同一份代碼、同一份配置,只需要在VM啟動參數中稍加變化,即可實現同一個應用包在不同環境下可以自由選擇使用使用AOP功能。文章開頭提到的那個需求也就迎刃而解了。

 

這只是一種解決途徑,相信大家會有更好的方案。如果有,請您告訴我。J

 

參考文檔:

1、使用AspectJ LTW(Load Time Weaving)

2、Spring LoadTimeWeaver 的那些事兒

3、在Spring應用中使用AspectJ


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM