Spring之LoadTimeWeaver——一个需求引发的思考

栏目: Java · 发布时间: 7年前

内容简介:最近有个需求——记录应用中某些接口被调用的轨迹,说白了,记录下入参、出参等即可。我选用ApsectJ解决这个问题,前期讨论说在接口层埋点,但这样有个问题,代码侵入比较严重,需要修改每个需要关注的接口实现类。经过一番讨论,决定使用AOP拦截所有这样的接口。后面又有个新的要求——沙箱环境拦截,生产环境不予拦截。

最近有个需求——记录应用中某些接口被调用的轨迹,说白了,记录下入参、出参等即可。

我选用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");

}

}

接着,我们编写分析方法执行效率的切面。

<span style="font-family: 'Courier New'; color: #646464; font-size: x-small;">@Aspect

public class ProfilingAspect {

br/>@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() {}

}</span>

前文提到,我们还需要一个aop.xml。这个文件要求放在META-INF/aop.xml路径下,以告知AspectJ Weaver我们需要把ProfilingAspect织入到应用的哪些类中。

<span style="font-family: 'Courier New'; color: #008080; font-size: x-small;"><!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></span>

目前为止,本次切面的“攻”和“受”都准备好了,我们还需要一个中间媒介——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="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-weaver aspectj-weaving="autodetect" />

<context:component-scan base-package="com.shansun.multidemo"></context:component-scan>

</beans>

通过<context:load-time-weaveraspectj-weaving="on" />使spring开启loadtimeweaver,注意aspectj-weaving有三个选项: on, off, auto-detect, 如果设置为auto-detect, spring将会在classpath中查找aspejct需要的META-INF/aop.xml,如果找到则开启aspectj weaving,这个逻辑在LoadTimeWeaverBeanDefinitionParser#isAspectJWeavingEnabled方法中:

<span style="font-family: 'Courier New'; color: #7f0055; font-size: x-small;"><strong>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);

}

}</strong></span>

一切都准备就绪——切面类、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这么个东东,如果不加会如何呢?试试看。

<span style="font-family: 'Courier New'; color: #ff0000; font-size: x-small;">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)</span>

必需使用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="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"> ;

<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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Filter Bubble

The Filter Bubble

Eli Pariser / Penguin Press / 2011-5-12 / GBP 16.45

In December 2009, Google began customizing its search results for each user. Instead of giving you the most broadly popular result, Google now tries to predict what you are most likely to click on. Ac......一起来看看 《The Filter Bubble》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试