Spring5.1.x官方参考指南中文版

 主页   资讯   文章   代码   电子书 

5.10在Spring应用程序中使用AspectJ

到目前为止,我们在这一章中介绍的所有内容都是纯Spring AOP。在本节中,如果你要使用Spring AOP之外的功能,我们将介绍如何在Spring中使用AspectJ编译器或weaver。

Spring附带了一个小的AspectJ方面库,它在你的发行版中作为spring-aspects.jar单独提供。为了使用类路径中的方面,需要将其添加到类路径中。“使用aspectj来依赖注入带有Spring的域对象”,以及“aspectj的其他Spring方面”,讨论了这个库的内容以及如何使用它。“通过使用SpringIOC来配置AspectJ方面”讨论了如何使用AspectJ编译器对AspectJ方面进行依赖注入。最后,“在Spring框架中使用AspectJ进行加载时编织”,为使用AspectJ的Spring应用程序提供了加载时编织的介绍。

5.10.1 使用AspectJ来依赖注入Spring的域对象

Spring容器实例化并配置在应用程序上下文中定义的bean。如果给定包含要应用的配置的bean定义的名称,也可以要求bean工厂配置预先存在的对象。spring-aspects.jar包含一个注解驱动的方面,它利用这个能力允许任何对象的依赖注入。该支持用于在任何容器的控制范围之外创建的对象。域对象通常属于此类,因为它们通常是通过new运算符或ORM工具作为数据库查询的结果以编程方式创建的。

@Configurable注解将类标记为符合Spring驱动配置。在最简单的情况下,可以将它纯粹用作标记注解,如下示例所示:

package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
    // ...
}

以这种方式用作标记接口时,Spring通过使用与完全限定类型名(com.xyz.myapp.domain.account)同名的bean定义(通常是原型范围)来配置注解类型(本例中为account)的新实例。由于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实例。

你还可以使用自动装载来避免必须指定一个专用的bean定义。要让Spring应用自动装载,请使用@Configurable注解的autowire属性。你可以分别指定@Configurable(autowire=Autowire.BY_TYPE)或@Configurable(autowire=Autowire.BY_NAME),用于按类型或按名称自动注解。作为替代方案,最好在字段或方法级别通过@Autowired或@Inject为@Configurable bean指定显式的、由注解驱动的依赖项注入(有关详细信息,请参阅基于注解的容器配置)。

最后,可以使用dependencyCheck属性(例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true))对新创建和配置的对象中的对象引用启用Spring依赖性检查。如果将此属性设置为true,那么配置之后,Spring将验证是否已设置了所有属性(不是基元或集合)。

请注意,单独使用注解没有任何作用。Spring-Aspects.jar中的AnnotationBeanConfigurerAspect,才是让注解起作用的根本。本质上,aspect是,“从用@Configurable注解的类型的新对象的初始化返回后,根据注解的属性使用spring配置新创建的对象”。在此上下文中,“初始化”是指新实例化的对象(例如,用new运算符实例化的对象)以及正在进行反序列化的可序列化对象(例如,通过 readResolve())。

以上段落中的一个关键短语是“本质上”。在大多数情况下,“从新对象的初始化返回后”的确切语义是正确的。在此上下文中,“初始化后”意味着在构造对象之后注入依赖项。这意味着依赖项在类的构造函数体中不可用。如果希望在构造函数主体执行之前注入依赖项,从而在构造函数主体中可用,则需要在@Configurable声明中定义此依赖项,如下所示:

@Configurable(preConstruction = true)

你可以在《AspectJ编程指南》的本附录中找到关于AspectJ中各种切点类型的语言语义的更多信息。

要使这项工作,注解类型必须与AspectJ Weaver一起编织。你可以使用构建时Ant或Maven任务来执行此操作(例如,请参阅《AspectJ开发环境指南》)或加载时编织(请参阅Spring框架中的加载时编织与AspectJ)。AnnotationBeanConfigureAspect本身需要由Spring进行配置(以便获得用于配置新对象的bean工厂的引用)。如果使用基于Java的配置,可以将@EnableSpringConfigureds\添加到任何“@Configuration”类,如下:

@Configuration
@EnableSpringConfigured
public class AppConfig {
}

如果你更喜欢基于XML的配置,那么Spring上下文名称空间定义了一个方便的context:spring-configured的元素,你可以使用它,如下所示:

<context:spring-configured/>

配置方面之前创建的@Configurable objects实例会导致向调试日志发出消息,并且不会对对象进行任何配置。一个例子可能是Spring配置中的bean,它在由Spring初始化时创建域对象。在这种情况下,可以使用depends-on bean属性手动指定bean依赖于配置方面。以下示例显示如何使用“depends-on”属性:

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

    <!-- ... -->

</bean>

不要通过bean配置器方面激活@Configurable,除非你真正打算在运行时依赖它的语义。特别是,确保在注册为容器中常规SpringBean的bean类上不使用@Configurable。这样做会导致双重初始化,一次通过容器,一次通过方面。

单元测试@Configurable对象

@Configurable支持的目标之一是能够对域对象进行独立的单元测试,而无需硬编码查找。如果@Configurable types不是由aspectj编织的,那么在单元测试期间注解没有影响。你可以在被测对象中设置mock或stub属性引用,并正常进行。如果@Configurable types已经被aspectj编织,你仍然可以像正常一样在容器外部进行单元测试,但是每次构造@Configurable对象时都会看到一条警告消息,表明它没有被spring配置。

和多个Application Contexts一起工作

用于实现@Configurable Support的AnnotationBeanConfigurerAspect是一个AspectJ单例方面。单个方面的范围与静态成员的范围相同:每个类加载器都有一个方面实例来定义类型。这意味着,如果在同一个类加载器层次结构中定义多个应用程序上下文,则需要考虑在何处定义@EnableSpringConfigured bean以及在何处将spring-aspects.jar放在类路径上。

考虑一个典型的SpringWeb应用程序配置,它具有一个定义公共业务服务的共享父应用程序上下文、支持这些服务所需的所有内容,以及每个servlet的一个子应用程序上下文(其中包含特定于该servlet的定义)。所有这些上下文都存在于同一个类加载器层次结构中,因此AnnotationBeanConfigurerAspect只能包含对其中一个上下文的引用。在这种情况下,我们建议在共享(父)应用程序上下文中定义@EnableSpringConfigured bean。这定义了你可能希望注入到域对象中的服务。结果是,你无法使用@Configurable机制(这可能不是你想要做的事情)配置域对象,因为它引用了在子(servlet特定)上下文中定义的bean。

在同一容器中部署多个Web应用程序时,请确保每个Web应用程序都使用自己的类加载器(例如,通过将spring-aspects.jar放在“WEB-INF/lib”中)加载spring-aspects.jar中的类型。如果Spring-Aspects.jar只添加到容器范围的类路径(因此由共享父类加载器加载),那么所有Web应用程序都共享相同的Aspect实例(这可能不是你想要的)。

5.10.2 AspectJ的其他Spring aspects

除了@Configurable方面,spring-aspects.jar还包含一个aspectj方面,你可以使用它来驱动spring对用@transactional annotation注解的类型和方法的事务管理。这主要面向希望在Spring容器之外使用Spring框架的事务支持的用户。

解释@transactional注解的方面是AnnotationTransactionAspect。使用此方面时,必须注解实现类(或该类中的方法或两者都使用),而不是类实现的接口(如果有的话)。AspectJ遵循Java的规则,接口上的注解不是继承的。

类上的@transactional注解指定执行类中任何公共操作的默认事务语义。

类内方法的@transactional注解将覆盖类注解(如果存在)给出的默认事务语义。任何可见性的方法都可以进行注解,包括私有方法。直接注解非公共方法是实现此类方法的事务划分的唯一方法。

自SpringFramework4.2以来,spring-aspects提供了类似的方面,为标准javax.transaction.transactional注解提供了完全相同的特性。有关详细信息,请查看JtaAnnotationTransactionAspect。

对于那些希望使用Spring配置和事务管理支持但不希望(或不能)使用注解的AspectJ程序员,spring-aspects.jar还包含可以扩展以提供自己的切入点定义的抽象方面。有关更多信息,请参阅AbstractBeanConfigureAspect和AbstractTransactionspect方面的源代码。作为一个例子,下面的摘录展示了如何通过使用与完全限定类名匹配的原型bean定义来编写一个方面来配置域模型中定义的对象的所有实例:

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

5.10.3使用SpringIoC来配置AspectJ Aspects

当你将AspectJ方面与Spring应用程序一起使用时,很自然地希望并期望能够使用Spring配置这些方面。AspectJ运行时本身负责方面的创建,通过Spring配置AspectJ创建的方面的方法取决于方面使用的AspectJ实例化模型(per-xxx子句)。

AspectJ方面的大多数是单体方面。这些方面的配置很容易。你可以创建一个bean定义,该定义将方面类型作为普通类型引用,并包含factory-method="aspectOf" bean属性。这样可以确保Spring通过向AspectJ请求来获得AspectJ实例,而不是试图创建实例本身。以下示例显示如何使用factory-method="aspectOf"属性:

<bean id="profiler" class="com.xyz.profiler.Profiler"
        factory-method="aspectOf"> 

    <property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>

非单例方面更难配置。但是,可以通过创建原型bean定义和使用spring-aspects.jar中的@Configurable支持来配置Aspectj运行时创建的bean之后的方面实例来实现这一点。

如果你有一些@Aspectj方面要用aspectj编织(例如,对域模型类型使用加载时编织)和其他@Aspectj方面要用spring aop,并且这些方面都在spring中配置,那么你需要告诉spring aop @Aspectj auto-proxing支持在配置中定义的@Aspectj方面的哪个确切子集应用于自动代理。可以通过在<aop:aspectj-autoproxy/>声明中使用一个或多个<include/>元素来完成此操作。每个<include/>元素都指定一个名称模式,只有名称与至少一个模式匹配的bean才用于SpringAOP自动代理配置。以下示例显示如何使用<include/>元素:

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

不要被<aop:aspectj-autoproxy/>元素的名称误导。使用它可以创建SpringAOP代理。这里使用的是Aspect声明的@AspectJ样式,但不涉及AspectJ运行时。

5.10.4 Spring框架中AspectJ的Load-time Weaving

加载时间编织(LTW)指的是在将AspectJ方面加载到Java虚拟机(JVM)中的过程中,将AspectJ方面编织成应用程序的类文件。本节的重点是在Spring框架的特定上下文中配置和使用LTW。本节不是LTW的一般介绍。有关LTW的详细信息和仅使用AspectJ配置LTW(根本不涉及Spring),请参阅AspectJ开发环境指南的LTW部分。

Spring框架为AspectJ LTW带来的价值在于能够对编织过程进行更细粒度的控制。“Vanilla 的AspectJ LTW是通过使用Java(5 +)代理来实现的,它在启动JVM时通过指定VM参数来打开。因此,它是一个JVM范围的设置,在某些情况下可能很好,但通常有点过于粗糙。启用了Spring的LTW允许你在每个类加载器的基础上打开LTW,它更细粒度,并且在“单个JVM多应用程序”环境中(例如在典型的应用程序服务器环境中)更有意义。

此外,在某些环境中,加载时编织,不需要对应用程序服务器的启动脚本进行任何修改,这些脚本需要添加-javaagent:path/to/aspectjweaver.jar或(如本节后面所述)javaagent:path/to/spring-instrument.jar。开发人员配置应用程序上下文以启用加载时编织,而不是依赖通常负责部署配置(如启动脚本)的管理员。

既然销售宣传已经结束,那么让我们先浏览一下使用Spring的AspectJ LTW的一个快速示例,然后详细介绍示例中介绍的元素。有关完整的示例,请参阅PetClinic示例应用程序。

一个例子

假设你是一个应用程序开发人员,负责诊断系统中某些性能问题的原因。我们将打开一个简单的分析方面,让我们快速获得一些性能指标,而不是开发一个分析工具。然后我们可以在之后立即对特定区域应用更细粒度的分析工具。

这里的示例使用XML配置。你还可以配置和使用Java配置的AspectJ。具体来说,你可以使用@EnableLoadTimeWeaving注解作为<context:load-time-weaver/>的替代方法(有关详细信息,请参阅下面)。

下面的示例显示了分析方面,这是不理想的。它是一个基于时间的探查器,使用@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类路径上的文件(或文件)的存在,称为META-IMF/aop.xml是标准AspectJ。下面的示例显示aop.xml文件:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://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(稍后解释)。这个加载时weaver是负责将一个或多个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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- a service object; we will be profiling its 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在每个类加载器的基础上选择性地打开LTW,这是正确的。但是,对于这个例子,我们使用Java代理(用Spring提供)来切换LTW。我们使用以下命令来运行前面显示的主类:

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

-javaagent是一个标志,用于指定和使代理能够检测在JVM上运行的程序。Spring框架附带这样一个代理,即InstrumentationSavingAgent,它打包在Spring-instrument.jar中,在前面的示例中作为-javaagent参数的值提供。

主程序执行时的输出类似于下一个示例。(我在calculateEntitlement()实现中引入了一个Thread.sleep(..)语句,以便探查器实际捕获0毫秒以外的内容(01234毫秒不是AOP引入的开销)。下面的列表显示了运行分析器时得到的输出:

Calculating entitlement

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

由于此LTW是通过使用全AspectJ来实现的,因此我们不仅限于advising SpringBeans。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的新实例。分析advice仍然被编织在一起。

诚然,这个例子是简单的。但是,在前面的示例中已经介绍了在Spring中LTW支持的基础知识,本节的其余部分详细解释了每一位配置和使用背后的“为什么”。

本例中使用的ProfilingAspect可能是基本的,但非常有用。这是一个很好的开发时间方面的例子,开发人员可以在开发期间使用它,然后很容易地将其从部署到UAT或生产中的应用程序的构建中排除。

Aspects

你在LTW中使用的方面必须是AspectJ方面。你可以用aspectj语言本身编写它们,也可以用@Aspectj样式编写方面。你的方面是有效的AspectJ和SpringAOP方面。此外,编译的方面类需要在类路径上可用。

'META-INF/aop.xml'

AspectJ LTW基础结构通过使用Java类路径上的一个或多个META-INF/aop.xml 文件来配置(直接或更典型地,在JAR文件中)。

此文件的结构和内容在AspectJ参考文档的LTW部分中有详细说明。因为aop.xml文件是100%aspectj,所以我们在这里不再详细描述它。

Required libraries (JARS)

至少,你需要以下库来使用Spring框架对AspectJ LTW的支持:

  • spring-aop.jar
  • aspectjweaver.jar

如果使用Spring提供的代理来启用检测,还需要:

  • spring-instrument.jar

Spring配置

Spring LTW支持中的关键组件是loadTimeweaver接口(在org.springframework.instrument.classloading包中),以及与Spring发行版一起提供的许多实现。loadtimeweaver负责在运行时向类加载器添加一个或多个java.lang.instrument.ClassFileTransformers,这为各种有趣的应用程序打开了大门,其中一个应用程序恰好是方面的LTW。

如果你不熟悉运行时类文件转换的概念,请参阅java.lang.instrument包的javadoc API文档,然后继续。虽然该文档并不全面,但至少你可以看到关键的接口和类(供你阅读本节时参考)。

为特定的应用程序上下文配置LoadTimeWeaver与添加一行一样简单。(请注意,你几乎肯定需要使用ApplicationContext作为Spring容器-通常,BeanFactory是不够的,因为LTW支持使用BeanFactoryPostProcessors。)

要启用Spring框架的LTW支持,你需要配置一个LoadTimeWeaver,通常通过使用@EnableLoadTimeWeaving注解完成,如下所示:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

或者,如果你更喜欢基于XML的配置,请使用<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:load-time-weaver/>

</beans>

前面的配置自动为你定义和注册许多LTW特定的基础结构bean,例如LoadTimeWeaver和AspectJWeavingEnabler。默认的LoadTimeWeaver是DefaultContextLoadTimeWeaver类,它尝试修饰自动检测到的LoadTimeWeaver。“自动检测”的LoadTimeWeaver的确切类型取决于运行时环境。下表总结了各种LoadTimeWeaver实现:

运行时环境 LoadTimeWeaver实现
Running in Apache Tomcat TomcatLoadTimeWeaver
Running in GlassFish (limited to EAR deployments) GlassFishLoadTimeWeaver
Running in Red Hat’s JBoss AS or WildFly JBossLoadTimeWeaver
Running in IBM’s WebSphere WebSphereLoadTimeWeaver
Running in Oracle’s WebLogic WebLogicLoadTimeWeaver
JVM started with Spring InstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar) InstrumentationLoadTimeWeaver
回退,期望底层类加载器遵循公共约定(即addTransformer和可选的getThrowawayClassLoader方法) ReflectiveLoadTimeWeaver

请注意,该表仅列出使用DefaultContextLoadTimeWeaver时自动检测的LoadTimeWeaver。你可以准确地指定要使用的LoadTimeWeaver实现。

若要指定具有Java配置的特定LoadTimeWeaver,请执行LoadTimeWeavingConfigurer接口并重写getLoadTimeWeaver()方法。以下示例指定了ReflectiveLoadTimeWeaver:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {

    @Override
    public LoadTimeWeaver getLoadTimeWeaver() {
        return new ReflectiveLoadTimeWeaver();
    }
}

如果使用基于XML的配置,则可以将完全限定的类名指定为<context:load-time-weaver/>元素上weaver-class属性的值。同样,下面的示例指定了ReflectiveLoadTimeWeaver:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:load-time-weaver
            weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>

</beans>

配置定义和注册的LoadTimeWeaver稍后可以使用众所周知的名称LoadTimeWeaver从Spring容器中检索。记住,LoadTimeWeaver仅作为Spring的LTW基础结构添加一个或多个ClassFileTransformer的机制存在。执行LTW的实际ClassFileTransformer是ClassPreprocessorAgentAdapter(来自org.aspectj.weaver.loadtime包)类。有关进一步的详细信息,请参见ClassPreprocessorAgentAdapter类的类级JavaDoc,因为实际影响编织的细节超出了本文档的范围。

还有一个最后要讨论的配置属性:aspectjWeaving属性(如果使用XML,则为 aspectj-weaving属性)。此属性控制是否启用LTW。它接受三个可能值中的一个,如果属性不存在,则默认值为autodetect。下表总结了三个可能的值:

Annotation Value XML Value Explanation
ENABLED on Aspectj编织已启用,并且各个方面在加载时视情况进行编织。
DISABLED off LTW关闭。加载时不编织任何方面。
AUTODETECT autodetect 如果SpringLTW基础结构可以找到至少一个META-INF/aop.xml文件,那么aspectj weaving就打开了。否则,它将关闭。这是默认值。

环境特定配置

最后一部分包含在应用程序服务器和Web容器等环境中使用Spring的LTW支持时所需的任何其他设置和配置。

Tomcat, JBoss, WebSphere, WebLogic

Tomcat、JBoss/WildFly、IBM WebSphere Application Server和Oracle WebLogic Server都提供了一个通用的应用程序类加载器,能够进行本地检测。Spring的本地LTW可以利用这些类加载器实现来提供AspectJ编织。如前所述,你可以简单地启用加载时编织。具体来说,你不需要修改jvm启动脚本来添加-javaagent:path/to/spring-instrument.jar。

注意,在jboss上,你可能需要禁用应用服务器扫描,以防止它在应用程序实际启动之前加载类。一个快速的解决方法是在工件中添加一个名为WEB-INF/jboss-scanning.xml的文件,其中包含以下内容:

<scanning xmlns="urn:jboss:scanning:1.0"/>

通用Java应用程序

当特定的loadtimeweaver实现不支持的环境中需要类检测时,JVM代理是通用的解决方案。对于这种情况,Spring提供了InstrumentationLoadTimeweaver,它需要特定于Spring(但非常通用)的JVM代理spring-instrument.jar,由common @EnableLoadTimeWeaving和<context:load-time-weaver/>设置自动检测。

要使用它,必须通过提供以下JVM选项来使用Spring代理启动虚拟机:

-javaagent:/path/to/spring-instrument.jar

请注意,这需要修改JVM启动脚本,这可能会阻止你在应用程序服务器环境中使用该脚本(取决于你的服务器和操作策略)。也就是说,对于每个JVM部署一个应用程序(如独立的Spring引导应用程序),你通常在任何情况下都控制整个JVM设置。