6.8. 在Spring应用中使用AspectJ

到目前为止本章讨论的一直是纯Spring AOP。在这一节里面我们将介绍如何使用AspectJ compiler/weaver 来代替Spring AOP或者作为它的补充,因为有些时候Spring AOP单独提供的功能也许并不能满足你的需要。

Spring提供了一个小巧的AspectJ aspect library,你可以在程序发行版本中单独使用 spring-aspects.jar文件,并将其加入到classpath下以使用其中的切面。 第 6.8.1 节 “在Spring中使用AspectJ进行domain object的依赖注入”第 6.8.2 节 “Spring中其他的AspectJ切面” 讨论了该库以及如何使用该库。 第 6.8.3 节 “使用Spring IoC来配置AspectJ的切面”讨论了如何对通过AspectJ compiler织入的AspectJ切面进行依赖注入。 最后第 6.8.4 节 “在Spring应用中使用AspectJ加载时织入(LTW)”介绍了使用AspectJ的Spring应用程序如何进行加载期织入(load-time weaving)。

6.8.1. 在Spring中使用AspectJ进行domain object的依赖注入

Spring容器对application context中定义的bean进行实例化和配置。同样也可以通过bean factory 来为一个已经存在且已经定义为spring bean的对象应用所包含的配置信息。 spring-aspects.jar中包含了一个annotation-driven的切面, 提供了能为任何对象进行依赖注入的能力。这样的支持旨在为 脱离容器管理而创建的对象进行依赖注入。领域对象经常处于这样的情形: 它们可能是通过new操作符创建的对象,也可能是由ORM工具查询数据库所返回的结果。

@Configurable注解标记了一个类可以通过Spring-driven方式来配置。 在最简单的情况下,我们只把它当作标记注解:

package com.xyz.myapp.domain;
                
                import org.springframework.beans.factory.annotation.Configurable;
                
                @Configurable
                public class Account {
                // ...
            }

当只是简单地作为一个标记接口来使用的时候,Spring将采用和该已注解的类型 (比如Account类)全名(com.xyz.myapp.domain.Account) 一致的bean原型定义来配置一个新实例。由于一个bean默认的名字就是它的全名, 所以一个比较方便的办法就是省略定义中的id属性:

<bean class="com.xyz.myapp.domain.Account" scope="prototype">
                <property name="fundsTransferService" ref="fundsTransferService"/>
            </bean>

如果你希望明确的指定bean原型定义的名字,你可以在注解中直接定义:

package com.xyz.myapp.domain;
                
                import org.springframework.beans.factory.annotation.Configurable;
                
                @Configurable("account")
                public class Account {
                // ...
            }

Spring会查找名字为"account"的bean定义,并使用它作定义来配置一个新的 Account实例。

你也可以使用自动装配来避免手工指定原型定义的名字。只要设置@Configurable 注解中的autowire属性就可以让Spring进行自动装配: 指定@Configurable(autowire=Autowire.BY_TYPE)或者 @Configurable(autowire=Autowire.BY_NAME可以让自动装配分别按照类型或名字进行。 作为另外一种选择,在Spring2.5中最好是在域或方法级使用@Autowired@Resource为你的@Configurable beans指定 明确的、注解驱动的依赖注入。(详情请参看第 3.11 节 “基于注解(Annotation-based)的配置”

最后,你可以通过使用dependencyCheck 属性,让Spring对新创建和配置的对象的对象引用进行 依赖检查(例如:@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true))。 如果这个属性设置为true,Spring会在配置结束后校验(除了primitives和collections类型) 所有的属性是否都被设置。

仅仅使用注解并没有做任何事情。但是spring-aspects.jar 中的AnnotationBeanConfigurerAspect会在注解存在时起作用。实质上切面指明: “在初始化一个由@Configurable 注解的新对象时, Spring按照注解中的属性来配置这个新创建的对象”。这种情况下,initialization 指新初始化的(比如用new初始化)的对象以及能进行反序列化的 Serializable对象(例如通过 readResolve()方法)。

注意

在上一段中一个关键的阶段就是“inessence”。多数情况下,“ 当从一个新对象初始化返回之后”的精确语义很不错...这种语境下, “初始化之后”的意思是依赖将在对象被构造之后注入 - 这意味着在类的构造器块中依赖将不可用。如果你希望它能在构造器代码块执行 之前被注入,并从而在构造器中使用它, 那么你需要在@Configurable接口声明上做类似的定义:

@Configurable(preConstruction=true)

你可以在 AspectJ Programming Guide一书的附录中 找到更多有关在AspectJ中各种切面类型的语义信息。

要实现上述的操作,已注解的类型必须由AspectJ weaver来织入 - 你可以使用一个构建时的ant/maven任务来完成 (参见AspectJ Development Environment Guide)或者使用加载时织入(参见 第 6.8.4 节 “在Spring应用中使用AspectJ加载时织入(LTW)”)。 类AnnotationBeanConfigurerAspect本身也需要Spring来配置(获得bean factory的引用,使用bean factory配置新的对象)。为此Spring的 context命名空间 定义了一个非常方便的标签。只要简单的在application context配置中包含下面的内容。

<context:spring-configured/>

如果你使用DTD代替Schema,对应的定义如下:

<bean 
                class="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"
            factory-method="aspectOf"/>

在切面配置完成之前创建的@Configurable 对象实例会导致在log中留下一个warning,并且任何对于该对象的配置都不会生效。 举一个例子,一个Spring管理配置的bean在被Spring初始化的时候创建了一个domain object。 对于这样的情况,你需要定义bean属性中的"depends-on"属性来手动指定该bean依赖于configuration切面。

<bean id="myService"
                class="com.xzy.myapp.service.MyService"
                depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
                
                <!-- ... -->
                
            </bean>

6.8.1.1. @Configurable对象的单元测试

提供@Configurable支持的一个目的就是使得domain object的单元测试 可以独立进行,不需要通过硬编码查找各种倚赖关系。如果@Configurable 类型没有通过AspectJ织入,则在单元测试过程中注解不会起到任何作用, 测试中你可以简单的为对象的mock或者stub属性赋值,并且和正常情况一样去使用该对象。 如果@Configurable类型通过AspectJ织入, 我们依然可以脱离容器进行单元测试,不过每次创建一个新的@Configurable 对象时都会看到一个warning,标示该对象没有被Spring配置。

6.8.1.2. Working with multiple application contexts

6.8.1.2. Working with multiple application contexts

AnnotationBeanConfigurerAspect通过一个AspectJ singleton切面来实现对 @Configurable的支持。一个singleton切面的作用域和一个 静态变量的作用域是一样的,那就是说,对于每一个classloader有一个切面来定义类型。 这就意味着如果你在一个classloader层次结构中定义了多个application context的时候就需要考虑 在哪里定义<aop:spring-configured/> bean和在哪个classpath下 放置spring-aspects.jar

考虑一下典型的Spring web项目,一般都是由一个父application context定义大部分business service和 所需要的其他资源,然后每一个servlet拥有一个子application context定义。所有这些context共存于 同一个classloader体系下,因此AnnotationBeanConfigurerAspect仅保持 一个对context的引用。在这样的情况下,我们推荐在父application context中定义 <aop:spring-configured/> bean:这里所定义的service可能是 你希望注入domain object的。这样做的结果是你不能为子application context中 使用@Configurable的domain object配置bean引用(可能你也根本就不希望那么做!)。

当在一个容器中部署多个web-app的时候,请确保每一个web-application使用自己的classloader 来加载spring-aspects.jar中的类(例如将spring-aspects.jar放在WEB-INF/lib目录下)。 如果spring-aspects.jar被放在了容器的classpath下(因此也被父classloader加载),则所有的 web application将共享一个aspect实例,这可能并不是你所想要的。

6.8.2. Spring中其他的AspectJ切面

除了@Configurable切面, spring-aspects.jar包含了一个AspectJ切面可以用来为 那些使用了@Transactional注解的类型和方法驱动Spring事务管理。 提供这个的主要目的是有些用户希望脱离Spring容器使用Spring的事务管理。

解析@Transactional注解的切面是 AnnotationTransactionAspect。当使用这个切面时, 你必须注解这个实现类(和/或这个类中的方法),而不是 这个类实现的接口(如果有)。AspectJ允许在接口上注解的Java规则 不被继承

类之上的一个@Transactional注解为该类中任何 public操作的执行指定了默认的事务语义。

类内部方法上的一个@Transactional注解会覆盖类注解(如果存在) 所给定的默认的事务语义。具有public、protected和default修饰符的方法都可以被注解。 直接注解protected和default方法是让这个操作的执行获得事务划分的唯一途径。

对于AspectJ程序员,希望使用Spring管理配置和事务管理支持,不过他们不想(或者不能)使用注解, spring-aspects.jar也包含了一些抽象 切面供你继承来提供你自己的切入点定义。参见AbstractBeanConfigurerAspectAbstractTransactionAspect的Javadoc获取更多信息。 作为一个例子,下面的代码片断展示了如何编写一个切面,然后通过和类全名匹配的bean原型定义来 配置domian object中定义的所有实例:

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {
                
                public DomainObjectConfiguration() {
                setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
                }
                
                // the creation of a new bean (any object in the domain model)
                protected pointcut beanCreation(Object beanInstance) :
                initialization(new(..)) &&
                SystemArchitecture.inDomainModel() && 
                this(beanInstance);
                
            }

6.8.3. 使用Spring IoC来配置AspectJ的切面

当在Spring application中使用AspectJ的时候,很自然的会想到用Spring来管理这些切面。 AspectJ runtime自身负责切面的创建,这意味着通过Spring来管理AspectJ 创建切面依赖于切面所使用的AspectJ instantiation model(per-clause)。

大多数AspectJ切面都是singleton切面。管理这些切面非常容易, 和通常一样创建一个bean定义引用该切面类型就可以了,并且在bean定义中包含 'factory-method="aspectOf"'这个属性。 这确保Spring从AspectJ获取切面实例而不是尝试自己去创建该实例。示例如下:

<bean id="profiler" class="com.xyz.profiler.Profiler"
                factory-method="aspectOf">
                <property name="profilingStrategy" ref="jamonProfilingStrategy"/>
            </bean>

non-singleton切面的配置稍难一点,然而它可以通过定义一个bean原型定义并且使用 spring-aspects.jar中的@Configurable支持, 当切面实例由AspectJ runtime创建后进行配置。

如果你希望一些@AspectJ切面使用AspectJ来织入(例如使用load-time织入domain object) 而另一些@AspectJ切面使用Spring AOP,并且这些切面都由Spring来管理,那你就需要告诉Spring AOP @AspectJ自动代理支持那些切面需要被自动代理。你可以通过在 <aop:aspectj-autoproxy>声明中使用一个或多个 <include/>元素。每个元素指定了一种命名格式, 只有bean命名至少符合其中一种情况下才会使用Spring AOP自动代理配置:

<aop:aspectj-autoproxy>
                <aop:include name="thisBean"/>
                <aop:include name="thatBean"/>
            </aop:aspectj-autoproxy>

注意

不要被<aop:aspectj-autoproxy/>元素的名字所误导: 用它会导致Spring AOP 代理的创建。在这中只是使用@AspectJ 类型的切面声明,但并不会涉及AspectJ运行时。

6.8.4. 在Spring应用中使用AspectJ加载时织入(LTW)

加载时织入(Load-time weaving(LTW))指的是在虚拟机载入字节码文件时动态织入AspectJ切面。 本 节关注于在Spring Framework中特的定context下配置和使用LTW:并没有LTW的介绍。 关于LTW和仅使用AspectJ配置LTW的详细信息(根本不涉及Spring),请查看 LTW section of the AspectJ Development Environment Guide

Spring框架的值添加为AspectJ LTW在动态织入过程中提供了更细粒度的控制。使用Java(5+)的代理 能使用一个叫‘Vanilla’的AspectJ LTW,这需要在启动JVM的时候将某个VM参数设置为开。 这种JVM范围的设置在一些情况下或许不错,但通常情况下显得有些粗颗粒。而用Spring的LTW能让你在 per-ClassLoader的基础上打开LTW, 这显然更加细粒度并且对“单JVM多应用”的环境更具意义(例如在一个典型应用服务器环境中一样)。

另外,在某些环境下,这能让你使用LTW而 不对应用服务器的启动脚本做任何改动,不然则需要添加 -javaagent:path/to/aspectjweaver.jar或者(以下将会提及的)-javaagent:path/to/spring-agent.jar。 开发人员只需简单修改应用上下文的一个或几个文件就能使用LTW,而不需依靠那些管理着部署配置 比如启动脚本的系统管理员。

经过以上讲解之后,先让我们来过一遍一个使用Spring的AspectJ LTW的快速示例,接着是一个 有对元素详细讲解的示例。如果想要一个完整的示例,请参看Petclinic(宠物诊所)的应用实例。

6.8.4.1. 第一个例子

假设你是一个应用开人员,被指派诊断一个系统的若干性能问题。与其拿出性能分析工具, 我们不如开启一个简单的分析切面,使我们能很快地得到一些性能指标,这样我们就能马上 针对特定区域使用一些较细粒度的分析工具。

这就是一个分析切面。没什么特别的,只是一个快餐式的基于时间的模拟分析器, 使用类@AspectJ风格的切面声明。

package foo;
                    
                    import org.aspectj.lang.ProceedingJoinPoint;
                    import org.aspectj.lang.annotation.Aspect;
                    import org.aspectj.lang.annotation.Around;
                    import org.aspectj.lang.annotation.Pointcut;
                    import org.springframework.util.StopWatch;
                    import org.springframework.core.annotation.Order;
                    
                    @Aspect
                    public class ProfilingAspect {
                    
                    @Around("methodsToBeProfiled()")
                    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 * foo..*.*(..))")
                    public void methodsToBeProfiled(){}
                    }
                

我们还需要创建一个“META-INF/aop.xml”文件,以告知AspectJ weaver 我们要把ProfilingAspect织入到类中。这个文件惯例,即在Java classpath中 出现一个文件称作“META-INF/aop.xml”是标准的AspectJ。

<!DOCTYPE aspectj PUBLIC
                    "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
                    <aspectj>
                    
                    <weaver>
                    
                    <!-- only weave classes in our application-specific packages -->
                    <include within="foo.*"/>
                    
                    </weaver>
                    
                    <aspects>
                    
                    <!-- weave in just this aspect -->        
                    <aspect name="foo.ProfilingAspect"/>
                    
                    </aspects>
                    
                </aspectj>

现在来看Spring特定的配置部分。我们需要配置一个LoadTimeWeaver (稍后会有解释,暂时不多深究)。当将一个或多个“META-INF/aop.xml”文件中的切面 配置织入你的应用程序的类中时,这个加载时织入器是必须的。这样的好处是不需要很多的配置, 正如下面你看到的一样(还有另外一些参数供你指定,我们将在后面详细介绍)。

<?xml version="1.0" encoding="UTF-8"?>
                    <beans xmlns="http://www.springframework.org/schema/beans"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns:context="http://www.springframework.org/schema/context"
                    xsi:schemaLocation="
                    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
                    
                    <!-- a service object; we will be profiling it's methods -->
                    <bean id="entitlementCalculationService"
                    class="foo.StubEntitlementCalculationService"/>
                    
                    <!-- this switches on the load-time weaving -->
                    <context:load-time-weaver/>
                    
                </beans>

现在万事俱备 - 切面,META-INF/aop.xml文件,以及Spring的配置 - 让我们创建一个带有main(..)方法的简单驱动类来演示LTW的作用吧。

package foo;
                    
                    import org.springframework.context.support.ClassPathXmlApplicationContext;
                    
                    public final class Main {
                    
                    public static void main(String[] args) {
                    
                    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);
                    
                    EntitlementCalculationService entitlementCalculationService
                    = (EntitlementCalculationService) ctx.getBean("entitlementCalculationService");
                    
                    // the profiling aspect is 'woven' around this method execution
                    entitlementCalculationService.calculateEntitlement();
                    }
                }

最后还有一件事要做。此节之前的介绍说过可以有选择性的基于Spring的 per-ClassLoader来启动LTW,而且的确如此。不过,对此例来说, 我们将使用Java代理(由Spring提供)来启动LTW。这个就是用以运行上面Main 类的命令行语句:

java -javaagent:C:/projects/foo/lib/global/spring-agent.jar foo.Main

-javaagent是一个Java 5+标记,用来指定和激活 使JVM上的程序运行的代理。Spring框架装载了一个InstrumentationSavingAgent 代理,在上面的例子中被作为了-javaagent参数的值打包在 spring-agent.jar中。

Main程序运行的输出如下所示。(我已经在 calculateEntitlement()的实现中插入了Thread.sleep(..) 语句,以免让模拟分析器获取0毫秒 - 这里的01234毫秒并非是AOP引入的系统开销。)

Calculating entitlement
                    
                    StopWatch 'ProfilingAspect': running time (millis) = 1234
                    ------ ----- ----------------------------
                    ms     %     Task name
                    ------ ----- ----------------------------
                01234  100%  calculateEntitlement

因为这个LTW使用成熟的AspectJ,我们并不局限于通知Spring beans的方法;接下来这个稍有变化的 Main程序将生成同样的结果。

package foo;
                    
                    import org.springframework.context.support.ClassPathXmlApplicationContext;
                    
                    public final class Main {
                    
                    public static void main(String[] args) {
                    
                    new ClassPathXmlApplicationContext("beans.xml", Main.class);
                    
                    EntitlementCalculationService entitlementCalculationService =
                    new StubEntitlementCalculationService();
                    
                    // the profiling aspect will be 'woven' around this method execution
                    entitlementCalculationService.calculateEntitlement();
                    }
                }

注意以上程序我们只是引导了Spring容器,然后完全在Spring上下文之外创建了一个 StubEntitlementCalculationService的实例...分析通知仍然得到织入。

上面的例子虽然简单了些,但Spring中基本的LTW支持都已介绍完了, 此节余下内容将对使用这些配置和用法背后的理由作详细解释。

注意

ProfilingAspect在此例中虽然基本但是颇为有用。这是一个很好的开发时切面的例子,开发者可以在开发过程中使用它(废话), 然后也能从已部署到UAT或者生产环境的应用中轻易的脱离。

6.8.4.2. 切面

你在LTW中使用的切面必须是AspectJ切面。你可以使用AspectJ语言或者类@AspectJ风格来编写你的切面。 后一种方式当然只能在Java 5+中使用,但它说明了你的切面可以同时对AspectJ和Spring AOP切面有效。 此外,编译后的切面类需要被注册到classpath下。

6.8.4.3. 'META-INF/aop.xml'

AspectJ LTW的基础设施是用一个或多个位于Java classpath上的(可以是直接的文件形式, 也可以是更典型的jar包形式)META-INF/aop.xml文件配置起来的。

有关文件的结构和内容都在AspectJ的参考文档中有详细介绍,有兴趣的读者 请参考这些资源。(很庆幸这一节比较简短,但aop.xml文件 是100% AspectJ的 - 没有任何使用Spring特定的信息或语义,因此我也没有什么可贡献的。 与其重写这些已由AspectJ开发者提供的令人满意的章节,我不如领你到这里。)

6.8.4.4. 相关类库(JARS)

你至少需要以下类库来让Spring框架支持AspectJ LTW:

  1. spring.jar(2.5或更高版本)

  2. aspectjrt.jar (1.5或更高版本)

  3. aspectjweaver.jar (1.5或更高版本)

如果你正在使用 由Spring提供的代理来激活检测(instrumentation)功能,你会需要:

  1. spring-agent.jar

6.8.4.5. Spring配置

Spring LTW功能的关键组件是LoadTimeWeaver接口 (在org.springframework.instrument.classloading包中), 以及Spring分发包中大量的实现。LoadTimeWeaver的实现负责 在运行时把一个或多个java.lang.instrument.ClassFileTransformers类添加到 ClassLoader中,这能产生各种各样有趣的应用,LTW切面恰好便是其中之一。

提示

如果你对运行时类文件变换的思想还不熟悉,推荐你在继续之前阅读 java.lang.instrument包的Javadoc API文档。 这其实并不难-反而有些恼人-因为有用的文件并不多...关键的接口和类都将会在此节呈现给你。

用XML为ApplicationContext配置一个 LoadTimeWeaver简单得只需要添加一行。 (请注意几乎肯定你需要使用ApplicationContext作为你的 Spring容器 - 一般来说只有BeanFactory是不够的, 因为LTW功能需要用到BeanFactoryPostProcessors。)

当要使用Spring框架的LTW功能时,你需要配置一个LoadTimeWeaver, 一般可以用<context:load-time-weaver/>元素来完成。 下面为一个有效的使用默认设置的<context:load-time-weaver/>定义。

<?xml version="1.0" encoding="UTF-8"?>
                    <beans xmlns="http://www.springframework.org/schema/beans"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns:context="http://www.springframework.org/schema/context"
                    xsi:schemaLocation="
                    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
                    
                    <context:load-time-weaver/>
                    
                </beans>

上面<context:load-time-weaver/> bean的定义会自动为你定义和注册若干 特定LTW的基础设施beans,比如一个LoadTimeWeaver 和一个AspectJWeavingEnabler。请注意 <context:load-time-weaver/>是怎样在context 命名空间下被定义的;还要注意被引用的XML Schema文件只在Spring 2.5或更高版本中才可用。

上面的配置为你定义并注册了一个默认的LoadTimeWeaver bean。 默认的LoadTimeWeaver是一个 DefaultContextLoadTimeWeaver类,它更倾向于去装饰一个能自动检测的LoadTimeWeaver类:LoadTimeWeaver 的确切类型会根据你的运行时环境“自动检测”出来(概述如下表)。

表 6.1. DefaultContextLoadTimeWeaver LoadTimeWeaversDefaultContextLoadTimeWeaver类和LoadTimeWeavers接口

运行时环境 LoadTimeWeaver的接口实现

BEA's Weblogic 10环境下

WebLogicLoadTimeWeaver

Oracle's OC4J环境下

OC4JLoadTimeWeaver

GlassFish环境下

GlassFishLoadTimeWeaver

以SpringInstrumentationSavingAgent

启动的JVM中

(java -javaagent:path/to/spring-agent.jar)

InstrumentationLoadTimeWeaver

不过,我们更希望这些类加载器能遵循共同的规范 (例如适用TomcatInstrumentableClassLoader和Resin)

ReflectiveLoadTimeWeaver


请注意当使用DefaultContextLoadTimeWeaver时只有 LoadTimeWeavers实现类能进行自动检测: 当然,你也可以通过指定将类的完全限定名作为<context:load-time-weaver/> 元素中weaver-class属性的值 来指定究竟想使用哪个LoadTimeWeaver的实现。如下例:

<?xml version="1.0" encoding="UTF-8"?>
                    <beans xmlns="http://www.springframework.org/schema/beans"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns:context="http://www.springframework.org/schema/context"
                    xsi:schemaLocation="
                    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
                    
                    <context:load-time-weaver
                    weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
                    
                </beans>

<context:load-time-weaver/>元素上定义和注册的 LoadTimeWeaver接口可以在Spring容器中以 loadTimeWeaver名字找到。 记住LoadTimeWeaver接口只是作为Spring LTW基础设施的一个机制 用来添加一个或多个ClassFileTransformers的。 ClassFileTransformer类实际是利用 ClassPreProcessorAgentAdapter类(包含在 org.aspectj.weaver.loadtime中)来进行LTW的。 有关ClassPreProcessorAgentAdapter的细节请参见 类级别的javadoc,织入实际怎样生效的具体内容已经超出本节讨论范围。

让我们来讨论<context:load-time-weaver/>的最后一个属性: aspectj-weaving。 这是一个简单的LTW开关,就这么简单。 它可以接受如下所述的三种值,如果不显示设置此属性则其默认值为autodetect

表 6.2. aspectj-weaving属性值

属性值 注释

on

AspectJ织入功能开启,切面将会在加载时适当时机被织入。

off

LTW功能关闭...不会在加载时织入切面。

autodetect

如果Spring LTW基础设施能找到至少一个META-INF/aop.xml 文件,那么AspectJ织入将会开启,否则关闭。此为默认值。


6.8.4.6. 特定环境的配置

这最后一节包括所有你在诸如应用服务器和web容器中使用Spring的LTW功能时需要的额外设置和配置。

6.8.4.6.1. 通用Java应用

你可能在各种Java应用中通过使用由Spring提供的检测代理启用Spring的LTW功能 (独立应用或者基于应用服务器的应用)。这样的话,可以通过指定 -javaagent:path/to/spring-agent.jar选项来启动虚拟机。 请注意这需要修改虚拟机的启动脚本,但在某些应用服务器环境下是禁止这么做的 (这取决于你的操作策略)。

6.8.4.6.2. Tomcat

对于部署在Apache Tomcat 5.0或更高版本上的web应用,Spring将一个 TomcatInstrumentableClassLoader注册成为web应用的类加载器。 必须的Tomcat设置如下所示,你可以把它放在Tomcat WAR包根目录下的核心文件 server.xml中或放到应用特定的META-INF/context.xml文件中。 Spring的spring-tomcat-weaver.jar需要被包含到Tomcat 的common lib路径下以确保设置生效。

<Context path="/myWebApp" docBase="/my/webApp/location">
                        <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"
                        useSystemClassLoaderAsParent="false"/>
                        </Context>
                    

注意:当使用LTW时,我们一般推荐使用Tomcat 5.5.20或更高版本。 先前的版本对定制的ClassLoader设置会产生问题。

另外,请考虑使用在Tomcat启动脚本中(见上面)指定由Spring提供的通用虚拟机代理。 这样才能使检测功能在所有已部署的web应用中可用,无论其上运行的是哪种类加载器。

有关更多基于Tomcat织入设置的详细讨论,请参考讨论各种不同Tomcat版本内容的 第 12.6.1.3.1 节 “Tomcat(5.0以上)加载时的织入配置”一节。虽然本节主要关注于 JPA persistence提供者的设置,但也谈到了Tomcat各种特定设置适用于一般加载时织入的情况。

6.8.4.6.3. WebLogic, OC4J, Resin, GlassFish

BEA WebLogic(版本10或更高),Oracle的JavaEE容器(OC4J 10.1.3.1或更高)以及 Resin(版本3.1或更高)提供具有本地检测能力的类加载器。 Srping的原生LTW利用这些类加载器来激活AspectJ织入。你可以通过简单地激活之前提到的 context:load-time-weaver来启动LTW功能。具体来说,即你 需要通过修改启动脚本来添加 -javaagent:path/to/spring-agent.jar

GlassFish同样也提供了检测能力的类加载器,不过只能在它的EAR环境下使用。 对于GlassFish的web应用,可以使用跟上面tomcat相同的设置。