Using AspectJ with Spring Applications
到目前为止,我们在本章中涵盖的所有内容都是纯粹的 Spring AOP。在本部分中,我们将研究如果您需要超出 Spring AOP 单独提供的功能,可以使用 AspectJ 编译器或织入器代替或补充 Spring AOP。
Everything we have covered so far in this chapter is pure Spring AOP. In this section, we look at how you can use the AspectJ compiler or weaver instead of or in addition to Spring AOP if your needs go beyond the facilities offered by Spring AOP alone.
Spring 随一小型的 AspectJ 方面库一起提供,您可在发行版中以独立方式获取该库,作为 spring-aspects.jar
。您需要将此库添加到类路径中,以便使用其中的方面。Using AspectJ to Dependency Inject Domain Objects with Spring 和 Other Spring aspects for AspectJ 讨论了此库的内容以及如何使用该库。Configuring AspectJ Aspects by Using Spring IoC 讨论如何使用 AspectJ 编译器编织的依赖注入 AspectJ 方面。最后, Load-time Weaving with AspectJ in the Spring Framework 提供了有关使用 AspectJ 的 Spring 应用程序负载时编织的简介。
Spring ships with a small AspectJ aspect library, which is available stand-alone in your
distribution as spring-aspects.jar
. You need to add this to your classpath in order
to use the aspects in it. Using AspectJ to Dependency Inject Domain Objects with Spring and Other Spring aspects for AspectJ discuss the
content of this library and how you can use it. Configuring AspectJ Aspects by Using Spring IoC discusses how to
dependency inject AspectJ aspects that are woven using the AspectJ compiler. Finally,
Load-time Weaving with AspectJ in the Spring Framework provides an introduction to load-time weaving for Spring applications
that use AspectJ.
Using AspectJ to Dependency Inject Domain Objects with Spring
Spring 容器实例化并配置在您的应用程序上下文中定义的 bean。还可以要求 Bean 工厂配置一个已经存在的对象,给定一个包含要应用的配置的 bean 定义的名称。spring-aspects.jar包含一个注解驱动的方面,它利用此功能允许依赖注入任何对象。该支持旨在用于在任何容器的控制之外创建的对象。域对象通常属于此类别,因为它们通常使用new运算符以编程方式创建,或在数据库查询的结果中使用 ORM 工具创建。
The Spring container instantiates and configures beans defined in your application
context. It is also possible to ask a bean factory to configure a pre-existing
object, given the name of a bean definition that contains the configuration to be applied.
spring-aspects.jar
contains an annotation-driven aspect that exploits this
capability to allow dependency injection of any object. The support is intended to
be used for objects created outside of the control of any container. Domain objects
often fall into this category because they are often created programmatically with the
new
operator or by an ORM tool as a result of a database query.
@Configurable注解将一个类标记为符合 Spring 驱动的配置。在最简单的情况下,您可以像以下示例所示那样纯粹将其用作标记注解:
The @Configurable
annotation marks a class as being eligible for Spring-driven
configuration. In the simplest case, you can use purely it as a marker annotation, as the
following example shows:
-
Java
-
Kotlin
import org.springframework.beans.factory.annotation.Configurable;
@Configurable
public class Account {
// ...
}
import org.springframework.beans.factory.annotation.Configurable
@Configurable
class Account {
// ...
}
以这种方式用作标记接口时,Spring 使用与类型全限定名称(在本例中为com.xyz.domain.Account)同名的 Bean 定义(通常是原型作用域的)来配置注解类型(Account)的新实例。由于通过 XML 定义的 Bean 的默认名称是其类型的全限定名称,因此声明原型定义的便捷方法是省略id属性,如下例所示:
When used as a marker interface in this way, Spring configures new instances of the
annotated type (Account
, in this case) by using a bean definition (typically
prototype-scoped) with the same name as the fully-qualified type name
(com.xyz.domain.Account
). Since the default name for a bean defined via XML is the
fully-qualified name of its type, a convenient way to declare the prototype definition
is to omit the id
attribute, as the following example shows:
<bean class="com.xyz.domain.Account" scope="prototype">
<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>
如果您想明确指定要使用的原型 Bean 定义的名称,可以在注解中直接这样做,如下例所示:
If you want to explicitly specify the name of the prototype bean definition to use, you can do so directly in the annotation, as the following example shows:
-
Java
-
Kotlin
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("account")
public class Account {
// ...
}
import org.springframework.beans.factory.annotation.Configurable
@Configurable("account")
class Account {
// ...
}
现在 Spring 查找一个名为account的 Bean 定义,并将其用作配置新Account实例的定义。
Spring now looks for a bean definition named account
and uses that as the
definition to configure new Account
instances.
您还可以使用自动装配来避免必须指定任何专用 Bean 定义。要让 Spring 应用自动装配,请使用 @Configurable
注解的 autowire
属性。您可以指定 @Configurable(autowire=Autowire.BY_TYPE)
或 @Configurable(autowire=Autowire.BY_NAME)
分别通过类型或名称进行自动装配。作为一种选择,最好通过 @Autowired
或 @Inject
在字段或方法级别为 @Configurable
Beans 指定显式注解驱动的依赖项注入(有关更多详细信息,请参见 Annotation-based Container Configuration)。
You can also use autowiring to avoid having to specify a dedicated bean definition at
all. To have Spring apply autowiring, use the autowire
property of the @Configurable
annotation. You can specify either @Configurable(autowire=Autowire.BY_TYPE)
or
@Configurable(autowire=Autowire.BY_NAME)
for autowiring by type or by name,
respectively. As an alternative, it is preferable to specify explicit, annotation-driven
dependency injection for your @Configurable
beans through @Autowired
or @Inject
at the field or method level (see Annotation-based Container Configuration for further details).
最后,您可以使用dependencyCheck属性(例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true))为新创建和配置的对象中的对象引用启用 Spring 依赖项检查。如果将此属性设置为true,则 Spring 在配置后验证所有属性(不是基本类型或集合)是否已设置。
Finally, you can enable Spring dependency checking for the object references in the newly
created and configured object by using the dependencyCheck
attribute (for example,
@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)
). If this attribute is
set to true
, Spring validates after configuration that all properties (which
are not primitives or collections) have been set.
请注意,单独使用注解不起作用。spring-aspects.jar
中的 AnnotationBeanConfigurerAspect
是对注解存在形式执行操作的。从本质上讲,此方面表示,“从使用 @Configurable
进行注解的新类型对象的初始化返回后,使用 Spring 根据注解的属性配置新建对象”。在此上下文中,“初始化”是指新实例化的对象(例如,使用 new
运算符实例化的对象),以及正在反序列化的 Serializable
对象(例如,通过 readResolve())。
Note that using the annotation on its own does nothing. It is the
AnnotationBeanConfigurerAspect
in spring-aspects.jar
that acts on the presence of
the annotation. In essence, the aspect says, "after returning from the initialization of
a new object of a type annotated with @Configurable
, configure the newly created object
using Spring in accordance with the properties of the annotation". In this context,
"initialization" refers to newly instantiated objects (for example, objects instantiated
with the new
operator) as well as to Serializable
objects that are undergoing
deserialization (for example, through
readResolve()).
上面一段中的关键短语之一是“从本质上讲”。对于大多数情况,确切的语义“在对新对象的初始化返回后”都很好。在此上下文中,“初始化后”意味着在对象构建后注入依赖项。这意味着构造函数主体内无法使用依赖项。如果您希望在构造函数主体运行之前注入依赖项,从而可以在构造函数主体中使用它们,则需要在@Configurable声明中定义此内容,如下所示: One of the key phrases in the above paragraph is "in essence". For most cases, the
exact semantics of "after returning from the initialization of a new object" are
fine. In this context, "after initialization" means that the dependencies are
injected after the object has been constructed. This means that the dependencies
are not available for use in the constructor bodies of the class. If you want the
dependencies to be injected before the constructor bodies run and thus be
available for use in the body of the constructors, you need to define this on the
|
- Java
-
@Configurable(preConstruction = true)
- Kotlin
-
@Configurable(preConstruction = true)
您可以在 此附录 中的https://www.eclipse.org/aspectj/doc/released/progguide/index.html[AspectJ 编程指南] 中找到有关各种 AspectJ 切入点类型语言语义的更多信息。
You can find more information about the language semantics of the various pointcut types in AspectJ in this appendix of the AspectJ Programming Guide.
为此,注释类型必须使用 AspectJ weaver 编织。您可以使用构建时 Ant 或 Maven 任务执行此操作(例如,请参阅 AspectJ 开发环境指南),或使用负载时编织(请参阅 Load-time Weaving with AspectJ in the Spring Framework)。AnnotationBeanConfigurerAspect
本身需要由 Spring 配置(为了获得要用来配置新对象的 Bean 工厂的引用)。您可以按如下方式定义相关配置:
For this to work, the annotated types must be woven with the AspectJ weaver. You can
either use a build-time Ant or Maven task to do this (see, for example, the
AspectJ Development
Environment Guide) or load-time weaving (see Load-time Weaving with AspectJ in the Spring Framework). The
AnnotationBeanConfigurerAspect
itself needs to be configured by Spring (in order to obtain
a reference to the bean factory that is to be used to configure new objects). You can define
the related configuration as follows:
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableSpringConfigured
public class ApplicationConfiguration {
}
@Configuration
@EnableSpringConfigured
class ApplicationConfiguration
<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:spring-configured />
</beans>
在配置方面之前创建的@Configurable对象实例将导致一条消息输出到调试日志中,并且不会配置对象。一个示例可能是 Spring 配置中的一个 bean,该 bean 在被 Spring 初始化时创建域对象。在这种情况下,您可以使用depends-on bean 属性手动指定 bean 依赖于配置方面。以下示例说明如何使用depends-on属性:
Instances of @Configurable
objects created before the aspect has been configured
result in a message being issued to the debug log and no configuration of the
object taking place. An example might be a bean in the Spring configuration that creates
domain objects when it is initialized by Spring. In this case, you can use the
depends-on
bean attribute to manually specify that the bean depends on the
configuration aspect. The following example shows how to use the depends-on
attribute:
<bean id="myService"
class="com.xyz.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
除非你真的想在运行时依赖它的语义,否则不要通过 bean 配置器方面激活 |
Do not activate |
Unit Testing @Configurable
Objects
@Configurable
中给出的支持的一个目标在于避免进行硬编码的查找,从而支持对领域目标进行独立的单元测试。如果 AspectJ 没有编织 @Configurable
类型,该对象无法在单元测试中产生任何影响。你可以在测试目标中设置模拟或存根的属性引用,并且像往常一样进行操作。如果 @Configurable
类型已经由 AspectJ 编织,你仍然可以在容器之外像往常一样执行单元测试,但每次构建一个 @Configurable
目标时你都将看到一条警告信息,指明该目标尚未由 Spring 配置。
One of the goals of the @Configurable
support is to enable independent unit testing
of domain objects without the difficulties associated with hard-coded lookups.
If @Configurable
types have not been woven by AspectJ, the annotation has no affect
during unit testing. You can set mock or stub property references in the object under
test and proceed as normal. If @Configurable
types have been woven by AspectJ,
you can still unit test outside of the container as normal, but you see a warning
message each time that you construct a @Configurable
object indicating that it has
not been configured by Spring.
Working with Multiple Application Contexts
用于实现 @Configurable
支持的 AnnotationBeanConfigurerAspect
是一个 AspectJ 单例切面。单例切面的作用域与 static
成员的作用域相同:对于定义类型的每个 ClassLoader
,只有一个切面实例。这意味着,如果你在同一个 ClassLoader
层次结构中定义了多个应用程序上下文,你需要考虑在其中定义 @EnableSpringConfigured
bean,以及将 spring-aspects.jar
放在类路径的哪里。
The AnnotationBeanConfigurerAspect
that is used to implement the @Configurable
support
is an AspectJ singleton aspect. The scope of a singleton aspect is the same as the scope
of static
members: There is one aspect instance per ClassLoader
that defines the type.
This means that, if you define multiple application contexts within the same ClassLoader
hierarchy, you need to consider where to define the @EnableSpringConfigured
bean and
where to place spring-aspects.jar
on the classpath.
考虑一个典型的 Spring Web 应用程序配置,其中有一个共享的父 ApplicationContext,它定义了公共业务服务、支持这些服务所需的一切,以及一个针对每个 Servlet(其中包含对该 servlet 特有的定义)的子 ApplicationContext。所有这些上下文在相同的 ClassLoader 层次结构中并存,因此 AnnotationBeanConfigurerAspect 只能引用其中的一个。在这种情况下,我们建议在共享(父级)应用程序上下文中定义 @EnableSpringConfigured Bean。这定义了你可能想要注入到领域对象中的服务。这样会产生一个后果,即你使用 @Configurable 机制无法通过引用在子(特定 servlet)上下文中定义的 Bean 来配置领域对象(这可能并不是你要做的)。
Consider a typical Spring web application configuration that has a shared parent application
context that defines common business services, everything needed to support those services,
and one child application context for each servlet (which contains definitions particular
to that servlet). All of these contexts co-exist within the same ClassLoader
hierarchy,
and so the AnnotationBeanConfigurerAspect
can hold a reference to only one of them.
In this case, we recommend defining the @EnableSpringConfigured
bean in the shared
(parent) application context. This defines the services that you are likely to want to
inject into domain objects. A consequence is that you cannot configure domain objects
with references to beans defined in the child (servlet-specific) contexts by using the
@Configurable mechanism (which is probably not something you want to do anyway).
在同一个容器中部署多个 Web 应用程序时,请确保每个 Web 应用程序使用自己的 ClassLoader
来加载 spring-aspects.jar
中的类型(例如,将 spring-aspects.jar
放置在 WEB-INF/lib
中)。如果仅将 spring-aspects.jar
添加到全容器类路径中(因此由共享的父 ClassLoader
加载),所有 Web 应用程序将共享相同的面向实例(这可能不是您想要的)。
When deploying multiple web applications within the same container, ensure that each
web application loads the types in spring-aspects.jar
by using its own ClassLoader
(for example, by placing spring-aspects.jar
in WEB-INF/lib
). If spring-aspects.jar
is added only to the container-wide classpath (and hence loaded by the shared parent
ClassLoader
), all web applications share the same aspect instance (which is probably
not what you want).
Other Spring aspects for AspectJ
除 @Configurable
切面外,spring-aspects.jar
还包含一个 AspectJaspect,你可以使用该切面来驱动 Spring 事务管理,类型和方法上带有 @Transactional
注解。这主要适用于希望在 Spring 容器外部使用 Spring Framework 事务支持的用户。
In addition to the @Configurable
aspect, spring-aspects.jar
contains an AspectJ
aspect that you can use to drive Spring’s transaction management for types and methods
annotated with the @Transactional
annotation. This is primarily intended for users who
want to use the Spring Framework’s transaction support outside of the Spring container.
解释 @Transactional
注释的方面是 AnnotationTransactionAspect
。使用此方面时,必须对实现类(或该类中的方法或二者)添加注释, وليس 实现了类的接口(如果存在)。AspectJ 遵循 Java 的规则,即接口上的注释不会被继承。
The aspect that interprets @Transactional
annotations is the
AnnotationTransactionAspect
. When you use this aspect, you must annotate the
implementation class (or methods within that class or both), not the interface (if
any) that the class implements. AspectJ follows Java’s rule that annotations on
interfaces are not inherited.
类上的 @Transactional
注解指定任何公开操作的执行的默认事务语义。
A @Transactional
annotation on a class specifies the default transaction semantics for
the execution of any public operation in the class.
类中的某个方法上的 @Transactional
注释会覆盖类注释(如果存在)给出的默认事务语义。任何可见性的方法都可以进行注释,包括私有方法。直接注释非公共方法是唯一的方法来为这些方法的执行获取事务分界。
A @Transactional
annotation on a method within the class overrides the default
transaction semantics given by the class annotation (if present). Methods of any
visibility may be annotated, including private methods. Annotating non-public methods
directly is the only way to get transaction demarcation for the execution of such methods.
从 Spring Framework 4.2 开始, |
Since Spring Framework 4.2, |
对于希望使用 Spring 配置和事务管理支持但不想(或不能)使用注释的 AspectJ 程序员来说,“spring-aspects.jar”也包含你可以扩展以提供自己的切点定义的“抽象”方面。有关更多信息,请参阅“AbstractBeanConfigurerAspect”和“AbstractTransactionAspect”方面的来源。例如,以下摘录展示了如何编写一个方面来配置域模型中定义的所有对象实例,方法是使用与完全限定类名称匹配的原型 Bean 定义:
For AspectJ programmers who want to use the Spring configuration and transaction
management support but do not want to (or cannot) use annotations, spring-aspects.jar
also contains abstract
aspects you can extend to provide your own pointcut
definitions. See the sources for the AbstractBeanConfigurerAspect
and
AbstractTransactionAspect
aspects for more information. As an example, the following
excerpt shows how you could write an aspect to configure all instances of objects
defined in the domain model by using prototype bean definitions that match the
fully qualified class names:
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(..)) &&
CommonPointcuts.inDomainModel() &&
this(beanInstance);
}
Configuring AspectJ Aspects by Using Spring IoC
在 Spring 应用程序中使用 AspectJ 方方面面时,自然会希望并且期望能够使用 Spring 配置这些方方面面。AspectJ 运行时本身负责创建方面,通过 Spring 配置 AspectJ 创建的方面的方法取决于方面使用的 AspectJ 实例化模型(per-xxx
子句)。
When you use AspectJ aspects with Spring applications, it is natural to both want and
expect to be able to configure such aspects with Spring. The AspectJ runtime itself is
responsible for aspect creation, and the means of configuring the AspectJ-created
aspects through Spring depends on the AspectJ instantiation model (the per-xxx
clause)
used by the aspect.
大多数 AspectJ 方面是单例方面。这些方面的配置很简单。您可以创建一个引用方面类型的 Bean 定义,与普通方面一样,并包含 factory-method="aspectOf"
Bean 属性。这可确保 Spring 通过询问 AspectJ 来获取方面实例,而不是尝试自己创建实例。以下示例展示了如何使用 factory-method="aspectOf"
属性:
The majority of AspectJ aspects are singleton aspects. Configuration of these
aspects is easy. You can create a bean definition that references the aspect type as
normal and include the factory-method="aspectOf"
bean attribute. This ensures that
Spring obtains the aspect instance by asking AspectJ for it rather than trying to create
an instance itself. The following example shows how to use the factory-method="aspectOf"
attribute:
<bean id="profiler" class="com.xyz.profiler.Profiler"
factory-method="aspectOf"> 1
<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
1 | Note the factory-method="aspectOf" attribute |
非单例方面更难配置。然而,可以通过创建原型 bean 定义并且使用 spring-aspects.jar
中的 @Configurable
支持来配置方面实例,一旦它们被 AspectJ 运行时创建,即可完成。
Non-singleton aspects are harder to configure. However, it is possible to do so by
creating prototype bean definitions and using the @Configurable
support from
spring-aspects.jar
to configure the aspect instances once they have bean created by
the AspectJ runtime.
如果您有一些希望使用 AspectJ 编织的 @AspectJ 方面(例如,为领域模型类型使用加载时间编织),还有一些其他希望与 Spring AOP 一起使用的 @AspectJ 方面,并且这些方面全部都在 Spring 中进行配置,您需要告诉 Spring AOP、@AspectJ 自动代理支持,应为自动代理使用配置中定义的哪一个 @AspectJ 方面的子集。您可以通过在 <aop:aspectj-autoproxy/>
声明中使用一个或多个 <include/>
元素来完成此操作。每个 <include/>
元素指定一个名称模式,并且仅将与至少一个模式匹配的名称 Bean 用于 Spring AOP 自动代理配置。以下示例展示如何使用 <include/>
元素:
If you have some @AspectJ aspects that you want to weave with AspectJ (for example,
using load-time weaving for domain model types) and other @AspectJ aspects that you want
to use with Spring AOP, and these aspects are all configured in Spring, you
need to tell the Spring AOP @AspectJ auto-proxying support which exact subset of the
@AspectJ aspects defined in the configuration should be used for auto-proxying. You can
do this by using one or more <include/>
elements inside the <aop:aspectj-autoproxy/>
declaration. Each <include/>
element specifies a name pattern, and only beans with
names matched by at least one of the patterns are used for Spring AOP auto-proxy
configuration. The following example shows how to use <include/>
elements:
<aop:aspectj-autoproxy>
<aop:include name="thisBean"/>
<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
不要被 |
Do not be misled by the name of the |
Load-time Weaving with AspectJ in the Spring Framework
加载时编织 (LTW) 指的是在将 AspectJ 方面编织到应用程序的类文件时,它们正在 Java 虚拟机 (JVM) 中加载的过程。本节的重点是在 Spring 框架的特定上下文中配置和使用 LTW。本节并不是 LTW 的一般性介绍。有关 LTW 的具体细节以及仅使用 AspectJ 配置 LTW 的完整详细信息(不涉及 Spring),请参见 AspectJ 开发环境指南的 LTW 部分。
Load-time weaving (LTW) refers to the process of weaving AspectJ aspects into an application’s class files as they are being loaded into the Java virtual machine (JVM). The focus of this section is on configuring and using LTW in the specific context of the Spring Framework. This section is not a general introduction to LTW. For full details on the specifics of LTW and configuring LTW with only AspectJ (with Spring not being involved at all), see the LTW section of the AspectJ Development Environment Guide.
Spring Framework 为 AspectJ LTW 带来的价值在于能够更细粒度地控制 Weaving 过程。“纯”AspectJ LTW 是通过使用 Java(5+)代理来实现的,该代理在启动 JVM 时通过指定 VM 参数来开启。因此,它是 JVM 级别的设置,在某些情况下可能很好,但在很多情况下都有些粗略。Spring 启用的 LTW 让你能够基于按 ClassLoader
级别开启 LTW,这更加细粒度,并且在“单 JVM-多应用程序”环境中(例如典型的应用程序服务器环境中)更有意义。
The value that the Spring Framework brings to AspectJ LTW is in enabling much
finer-grained control over the weaving process. 'Vanilla' AspectJ LTW is effected by using
a Java (5+) agent, which is switched on by specifying a VM argument when starting up a
JVM. It is, thus, a JVM-wide setting, which may be fine in some situations but is often a
little too coarse. Spring-enabled LTW lets you switch on LTW on a
per-ClassLoader
basis, which is more fine-grained and which can make more
sense in a 'single-JVM-multiple-application' environment (such as is found in a typical
application server environment).
此外,in certain environments,此支持启用了负载时编织,而无需对应用程序服务器启动脚本进行任何修改,该脚本需要添加 -javaagent:path/to/aspectjweaver.jar
或(如下文所述)-javaagent:path/to/spring-instrument.jar
。开发人员配置应用程序上下文以启用负载时编织,而不是依靠通常负责部署配置(例如启动脚本)的管理员。
Further, in certain environments, this support enables
load-time weaving without making any modifications to the application server’s launch
script that is needed to add -javaagent:path/to/aspectjweaver.jar
or (as we describe
later in this section) -javaagent:path/to/spring-instrument.jar
. Developers configure
the application context to enable load-time weaving instead of relying on administrators
who typically are in charge of the deployment configuration, such as the launch script.
现在,在产品宣传结束之后,首先让我们快速浏览一个使用 Spring 的 AspectJ LTW 的示例,然后详细了解示例中引入的元素。有关完整示例,请参见 Petclinic 样本应用程序。
Now that the sales pitch is over, let us first walk through a quick example of AspectJ LTW that uses Spring, followed by detailed specifics about elements introduced in the example. For a complete example, see the Petclinic sample application.
A First Example
假设您是一位应用程序开发者, tasked 处置诊断系统中某些性能问题的原因。但不是拿出分析工具,我们将开启一个简单的分析环节,让我们快速获取一些性能指标。然后,我们马上就可以对那个特定区域应用一个精细的分析工具。
Assume that you are an application developer who has been tasked with diagnosing the cause of some performance problems in a system. Rather than break out a profiling tool, we are going to switch on a simple profiling aspect that lets us quickly get some performance metrics. We can then apply a finer-grained profiling tool to that specific area immediately afterwards.
此处提供的示例使用 XML 配置。你还可以用 Java configuration 来配置和使用 @AspectJ 。具体来说,你可以使用 |
The example presented here uses XML configuration. You can also configure and
use @AspectJ with Java configuration. Specifically, you can use the
|
以下示例展示了剖析方面,该方面很简单。它是一个基于时间的剖析器,它使用AspectJ 形式的方面声明:
The following example shows the profiling aspect, which is not fancy. It is a time-based profiler that uses the @AspectJ-style of aspect declaration:
-
Java
-
Kotlin
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 * com.xyz..*.*(..))")
public void methodsToBeProfiled(){}
}
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
class ProfilingAspect {
@Around("methodsToBeProfiled()")
fun profile(pjp: ProceedingJoinPoint): Any? {
val sw = StopWatch(javaClass.simpleName)
try {
sw.start(pjp.getSignature().getName())
return pjp.proceed()
} finally {
sw.stop()
println(sw.prettyPrint())
}
}
@Pointcut("execution(public * com.xyz..*.*(..))")
fun methodsToBeProfiled() {
}
}
我们还需要创建一个 META-INF/aop.xml
文件,以告知 AspectJ weaver,我们希望将 ProfilingAspect
编织到我们的类中。此文件约定,即在称为 META-INF/aop.xml
的 Java classpath 中存在一个文件(或多个文件),是标准的 AspectJ。以下示例显示 aop.xml
文件:
We also need to create an META-INF/aop.xml
file, to inform the AspectJ weaver that
we want to weave our ProfilingAspect
into our classes. This file convention, namely
the presence of a file (or files) on the Java classpath called META-INF/aop.xml
is
standard AspectJ. The following example shows the aop.xml
file:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages and sub-packages -->
<include within="com.xyz..*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="com.xyz.ProfilingAspect"/>
</aspects>
</aspectj>
建议仅处理特定类(通常是应用程序包中的类,如上面 |
It is recommended to only weave specific classes (typically those in the
application packages, as shown in the |
现在我们可以继续进行配置中特定于 Spring 的部分了。我们需要配置一个 LoadTimeWeaver
(稍后解释)。此装载时织入程序是负责将一个或多个 META-INF/aop.xml
文件中的方面配置织入应用程序中的类的关键组件。好处是它不需要很多配置(你可以指定更多选项,但稍后会详细说明),如下例所示:
Now we can move on to the Spring-specific portion of the configuration. We need
to configure a LoadTimeWeaver
(explained later). This load-time weaver is the
essential component responsible for weaving the aspect configuration in one or
more META-INF/aop.xml
files into the classes in your application. The good
thing is that it does not require a lot of configuration (there are some more
options that you can specify, but these are detailed later), as can be seen in
the following example:
<?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="com.xyz.StubEntitlementCalculationService"/>
<!-- this switches on the load-time weaving -->
<context:load-time-weaver/>
</beans>
现在,所有必需的产品(方面、 META-INF/aop.xml`文件和 Spring 配置)都已到位,我们可以创建一个带有 `main(..)
方法的以下驱动程序类来演示 LTW 的作用:
Now that all the required artifacts (the aspect, the META-INF/aop.xml
file, and the Spring configuration) are in place, we can create the following
driver class with a main(..)
method to demonstrate the LTW in action:
-
Java
-
Kotlin
// imports
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
EntitlementCalculationService service =
ctx.getBean(EntitlementCalculationService.class);
// the profiling aspect is 'woven' around this method execution
service.calculateEntitlement();
}
}
// imports
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
val service = ctx.getBean(EntitlementCalculationService.class)
// the profiling aspect is 'woven' around this method execution
service.calculateEntitlement()
}
我们还有一件事要做。本节的引言确实表示,可以基于每个ClassLoader
的基础通过 Spring 选择性地启动 LTW,这是正确的。但是,对于此示例,我们使用 Java 代理(随 Spring 提供)来启动 LTW。我们使用以下命令运行前面显示的Main
类:
We have one last thing to do. The introduction to this section did say that one could
switch on LTW selectively on a per-ClassLoader
basis with Spring, and this is true.
However, for this example, we use a Java agent (supplied with Spring) to switch on LTW.
We use the following command to run the Main
class shown earlier:
java -javaagent:C:/projects/xyz/lib/spring-instrument.jar com.xyz.Main
-javaagent
是指定和启用 用于对在 JVM 上运行的程序进行仪器的 agent 的标志。Spring 框架附带这样一个 agent,即 InstrumentationSavingAgent
,它包装在 @{26} 中,该包作为上述示例中 -javaagent
参数的值提供。
The -javaagent
is a flag for specifying and enabling
agents
to instrument programs that run on the JVM. The Spring Framework ships with such an
agent, the InstrumentationSavingAgent
, which is packaged in the
spring-instrument.jar
that was supplied as the value of the -javaagent
argument in
the preceding example.
Main
程序执行的输出类似于下一个示例。(我在 calculateEntitlement()
实现中引入了一个 Thread.sleep(..)
语句,以便性能分析器实际上捕捉到除了 0 毫秒之外的内容(01234 毫秒不是 AOP 引入的开销)。以下清單顯示我們運行性能分析器時取得的輸出:
The output from the execution of the Main
program looks something like the next example.
(I have introduced a Thread.sleep(..)
statement into the calculateEntitlement()
implementation so that the profiler actually captures something other than 0
milliseconds (the 01234
milliseconds is not an overhead introduced by the AOP).
The following listing shows the output we got when we ran our profiler:
Calculating entitlement
StopWatch 'ProfilingAspect': running time (millis) = 1234 ------ ----- ---------------------------- ms % Task name ------ ----- ---------------------------- 01234 100% calculateEntitlement
由于该 LTW 使用全功能的 AspectJ,我们不仅限于通知 Spring bean。以下 Main 程序的轻微变化产生相同结果:
Since this LTW is effected by using full-blown AspectJ, we are not limited only to advising
Spring beans. The following slight variation on the Main
program yields the same
result:
-
Java
-
Kotlin
// imports
public class Main {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("beans.xml");
EntitlementCalculationService service =
new StubEntitlementCalculationService();
// the profiling aspect will be 'woven' around this method execution
service.calculateEntitlement();
}
}
// imports
fun main(args: Array<String>) {
ClassPathXmlApplicationContext("beans.xml")
val service = StubEntitlementCalculationService()
// the profiling aspect will be 'woven' around this method execution
service.calculateEntitlement()
}
注意,在前面的程序中,我们引导 Spring 容器,然后在 Spring 上下文外创建一个 StubEntitlementCalculationService
新实例。概要文件建议仍会编织进来。
Notice how, in the preceding program, we bootstrap the Spring container and
then create a new instance of the StubEntitlementCalculationService
totally outside
the context of Spring. The profiling advice still gets woven in.
不可否认的是,这个例子很简单。但是,Spring 中的 LTW 支持基础知识已在前面的示例中全面介绍,本节其余部分将详细说明配置和使用的每一部分背后的“原因”。
Admittedly, the example is simplistic. However, the basics of the LTW support in Spring have all been introduced in the earlier example, and the rest of this section explains the "why" behind each bit of configuration and usage in detail.
本示例中使用的 |
The |
Aspects
您在 LTW 中使用的方面必须是 AspectJ 方面。您可以使用 AspectJ 语言编写它们,也可以使用 @AspectJ 样式编写它们。然后,您的方面同时是有效的 AspectJ 和 Spring AOP 方面。此外,编译过的方面类需要在类路径上可用。
The aspects that you use in LTW have to be AspectJ aspects. You can write them in either the AspectJ language itself, or you can write your aspects in the @AspectJ-style. Your aspects are then both valid AspectJ and Spring AOP aspects. Furthermore, the compiled aspect classes need to be available on the classpath.
META-INF/aop.xml
AspectJ LTW 基础设施使用一个或多个 META-INF/aop.xml
文件进行配置,这些文件位于 Java 类路径上(直接或通常在 jar 文件中)。例如:
The AspectJ LTW infrastructure is configured by using one or more META-INF/aop.xml
files that are on the Java classpath (either directly or, more typically, in jar files).
For example:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages and sub-packages -->
<include within="com.xyz..*"/>
</weaver>
</aspectj>
建议仅处理特定类(通常是应用程序包中的类,如上面 |
It is recommended to only weave specific classes (typically those in the
application packages, as shown in the |
此文件的结构和内容在 AspectJ 参考文档 的 LTW 部分中进行了详细说明。由于 aop.xml
文件是 100% AspectJ,因此我们在此不再对其进行进一步说明。
The structure and contents of this file is detailed in the LTW part of the
AspectJ reference
documentation. Because the aop.xml
file is 100% AspectJ, we do not describe it further here.
Required libraries (JARS)
至少,您需要以下库才能使用 Spring Framework 对 AspectJ LTW 的支持:
At minimum, you need the following libraries to use the Spring Framework’s support for AspectJ LTW:
-
spring-aop.jar
-
aspectjweaver.jar
如果您使用 Spring-provided agent to enable instrumentation,您还需要:
If you use the Spring-provided agent to enable instrumentation , you also need:
-
spring-instrument.jar
Spring Configuration
Spring 的 LTW 支持中的关键组件是 LoadTimeWeaver
接口(位于 org.springframework.instrument.classloading
包中),以及随 Spring 发行版一起提供的众多实现。LoadTimeWeaver
负责在运行时向 ClassLoader
添加一个或多个 java.lang.instrument.ClassFileTransformers
,从而为各种有趣的应用程序打开了大门,其中之一就是方面的 LTW。
The key component in Spring’s LTW support is the LoadTimeWeaver
interface (in the
org.springframework.instrument.classloading
package), and the numerous implementations
of it that ship with the Spring distribution. A LoadTimeWeaver
is responsible for
adding one or more java.lang.instrument.ClassFileTransformers
to a ClassLoader
at
runtime, which opens the door to all manner of interesting applications, one of which
happens to be the LTW of aspects.
如果您不熟悉运行时类文件转换的概念,请在继续阅读之前查看 |
If you are unfamiliar with the idea of runtime class file transformation, see the
javadoc API documentation for the |
为特定 ApplicationContext
配置 LoadTimeWeaver
可能就像添加一行一样简单。(请注意,您几乎肯定需要使用 ApplicationContext
作为您的 Spring 容器——通常,BeanFactory
是不够的,因为 LTW 支持使用 BeanFactoryPostProcessors
。)
Configuring a LoadTimeWeaver
for a particular ApplicationContext
can be as easy as
adding one line. (Note that you almost certainly need to use an
ApplicationContext
as your Spring container — typically, a BeanFactory
is not
enough because the LTW support uses BeanFactoryPostProcessors
.)
要启用 Spring Framework 的 LTW 支持,您需要按如下方式配置 LoadTimeWeaver
:
To enable the Spring Framework’s LTW support, you need to configure a LoadTimeWeaver
as follows:
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableLoadTimeWeaving
public class ApplicationConfiguration {
}
@Configuration
@EnableLoadTimeWeaving
class ApplicationConfiguration
<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
实现:
The preceding configuration automatically defines and registers a number of LTW-specific
infrastructure beans, such as a LoadTimeWeaver
and an AspectJWeavingEnabler
, for you.
The default LoadTimeWeaver
is the DefaultContextLoadTimeWeaver
class, which attempts
to decorate an automatically detected LoadTimeWeaver
. The exact type of LoadTimeWeaver
that is "automatically detected" is dependent upon your runtime environment.
The following table summarizes various LoadTimeWeaver
implementations:
Runtime Environment | LoadTimeWeaver implementation |
---|---|
Running in Apache Tomcat |
|
Running in GlassFish (limited to EAR deployments) |
|
|
|
JVM started with Spring |
|
Fallback, expecting the underlying ClassLoader to follow common conventions
(namely |
|
请注意,当您使用 DefaultContextLoadTimeWeaver
时,该表只列出了自动检测到的 LoadTimeWeavers
。您可以确切地指定要使用的 LoadTimeWeaver
实现。
Note that the table lists only the LoadTimeWeavers
that are autodetected when you
use the DefaultContextLoadTimeWeaver
. You can specify exactly which LoadTimeWeaver
implementation to use.
要配置特定的 LoadTimeWeaver
,实现 LoadTimeWeavingConfigurer
接口并覆盖 getLoadTimeWeaver()
方法(或使用 XML 等效项)。以下示例指定了 ReflectiveLoadTimeWeaver
:
To configure a specific LoadTimeWeaver
, implement the
LoadTimeWeavingConfigurer
interface and override the getLoadTimeWeaver()
method
(or use the XML equivalent).
The following example specifies a ReflectiveLoadTimeWeaver
:
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableLoadTimeWeaving
public class CustomWeaverConfiguration implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
}
@Configuration
@EnableLoadTimeWeaving
class CustomWeaverConfiguration : LoadTimeWeavingConfigurer {
override fun getLoadTimeWeaver(): LoadTimeWeaver {
return ReflectiveLoadTimeWeaver()
}
}
<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 基础设施用于添加一个或多个 ClassFileTransformers
的机制而存在。执行 LTW 的实际 ClassFileTransformer
是 ClassPreProcessorAgentAdapter
(来自 org.aspectj.weaver.loadtime
包)类。请参见 ClassPreProcessorAgentAdapter
类的类级 javadoc 以获取更多详细信息,因为实际如何进行编织的具体细节超出了本文档的范围。
The LoadTimeWeaver
that is defined and registered by the configuration can be later
retrieved from the Spring container by using the well known name, loadTimeWeaver
.
Remember that the LoadTimeWeaver
exists only as a mechanism for Spring’s LTW
infrastructure to add one or more ClassFileTransformers
. The actual
ClassFileTransformer
that does the LTW is the ClassPreProcessorAgentAdapter
(from
the org.aspectj.weaver.loadtime
package) class. See the class-level javadoc of the
ClassPreProcessorAgentAdapter
class for further details, because the specifics of how
the weaving is actually effected is beyond the scope of this document.
配置的最后一个属性还有待讨论:aspectjWeaving
属性(如果您使用 XML,则为 aspectj-weaving
)。此属性控制是否启用 LTW。它接受三个可能的值之一,如果属性不存在,则默认值为 autodetect
。下表总结了三个可能的值:
There is one final attribute of the configuration left to discuss: the aspectjWeaving
attribute (or aspectj-weaving
if you use XML). This attribute controls whether LTW
is enabled or not. It accepts one of three possible values, with the default value being
autodetect
if the attribute is not present. The following table summarizes the three
possible values:
Annotation Value | XML Value | Explanation |
---|---|---|
|
|
AspectJ weaving is on, and aspects are woven at load-time as appropriate. |
|
|
LTW is off. No aspect is woven at load-time. |
|
|
If the Spring LTW infrastructure can find at least one |
Environment-specific Configuration
最后一部分包含在诸如应用程序服务器和 Web 容器之类的环境中使用 Spring 的 LTW 支持时所需的任何其他设置和配置。
This last section contains any additional settings and configuration that you need when you use Spring’s LTW support in environments such as application servers and web containers.
Tomcat, JBoss, WildFly
Tomcat 和 JBoss/WildFly 提供了一个通用应用程序 ClassLoader
,它能够执行本地仪表化。Spring 的原生 LTW 可以利用那些 ClassLoader 实现来提供 AspectJ 编织。您可以简单地启用加载时编织,如 described earlier。具体来说,您不需要修改 JVM 启动脚本来添加 -javaagent:path/to/spring-instrument.jar
。
Tomcat and JBoss/WildFly provide a general app ClassLoader
that is capable of local
instrumentation. Spring’s native LTW may leverage those ClassLoader implementations
to provide AspectJ weaving.
You can simply enable load-time weaving, as described earlier.
Specifically, you do not need to modify the JVM launch script to add
-javaagent:path/to/spring-instrument.jar
.
请注意,在 JBoss 上,您可能需要禁用应用程序服务器扫描以防止它在应用程序实际启动之前加载类。一个快速的解决方法是将名为 WEB-INF/jboss-scanning.xml
的文件添加到您的制品中,内容如下:
Note that on JBoss, you may need to disable the app server scanning to prevent it from
loading the classes before the application actually starts. A quick workaround is to add
to your artifact a file named WEB-INF/jboss-scanning.xml
with the following content:
<scanning xmlns="urn:jboss:scanning:1.0"/>
Generic Java Applications
当在不受特定 LoadTimeWeaver
实现支持的环境中需要类检测时,JVM 代理是通用的解决方案。对于这样的情况,Spring 提供 InstrumentationLoadTimeWeaver
,它需要 Spring 特定的(但非常通用的)JVM 代理 spring-instrument.jar
,这些代理可通过常见的 @EnableLoadTimeWeaving
和 <context:load-time-weaver/>
设置自动检测。
When class instrumentation is required in environments that are not supported by
specific LoadTimeWeaver
implementations, a JVM agent is the general solution.
For such cases, Spring provides InstrumentationLoadTimeWeaver
which requires a
Spring-specific (but very general) JVM agent, spring-instrument.jar
, autodetected
by common @EnableLoadTimeWeaving
and <context:load-time-weaver/>
setups.
要使用它,您需要向 Spring 代理提供以下 JVM 选项来启动虚拟机:
To use it, you must start the virtual machine with the Spring agent by supplying the following JVM options:
-javaagent:/path/to/spring-instrument.jar
请注意,这需要修改 JVM 启动脚本,这可能会妨碍你在应用程序服务器环境中使用此功能(具体视你的服务器和你的操作策略而定)。也就是说,对于诸如独立 Spring Boot 应用程序的一应用每 JVM 部署,你通常在任何情况下都控制整个 JVM 设置。
Note that this requires modification of the JVM launch script, which may prevent you from using this in application server environments (depending on your server and your operation policies). That said, for one-app-per-JVM deployments such as standalone Spring Boot applications, you typically control the entire JVM setup in any case.