5. 基于 Spring 的面向方面编程
Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking
about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the
aspect.
面向方面编程(AOP)通过提供另一种思考程序结构的方式,补充了面向对象编程(OOP)。在 OOP 中,模块化的关键单元是类,而在 AOP
中,模块化的单元是方面。
Aspects enable the modularization of concerns (such as transaction management) that cut across multiple types and
objects. (Such concerns are often termed “crosscutting” concerns in AOP literature.)
方面使跨多个类型和对象的关注点(如事务管理)模块化。(这些关注点在 AOP 文献中通常被称为“横切”关注点。)
One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP (meaning
you do not need to use AOP if you don’t want to), AOP complements Spring IoC to provide a very capable middleware
solution.
Spring 的关键组件之一是 AOP 框架。虽然 Spring IoC 容器不依赖于 AOP(这意味着如果您不想使用 AOP,则不需要使用 AOP),但 AOP 补充
Spring IoC,提供了一种非常强大的中间件解决方案。
AOP is used in the Spring Framework to:
AOP 用于 Spring 框架中:
Provide declarative enterprise services. The most important such service
is declarative transaction management.
提供声明式企业服务。其中最重要的服务是声明式事务管理。
Let users implement custom aspects, complementing their use of OOP with AOP.
让用户实现自定义方面,以 AOP 补充他们对 OOP 的使用。
If you are interested only in generic declarative services or other pre-packaged declarative middleware services such as
pooling, you do not need to work directly with Spring AOP, and can skip most of this chapter.
如果您只对通用声明性服务或其他预包装声明性中间件服务(如连接池)感兴趣,您无需直接与 Spring AOP 工作,可以跳过本章的大部分内容。
Let us begin by defining some central AOP concepts and terminology. These terms are not Spring-specific. Unfortunately,
AOP terminology is not particularly intuitive. However, it would be even more confusing if Spring used its own
terminology.
让我们首先定义一些核心的 AOP 概念和术语。这些术语并非仅限于 Spring。不幸的是,AOP 术语并不特别直观。然而,如果 Spring
使用自己的术语,将会更加混乱。
Aspect: A modularization of a concern that cuts across multiple classes. Transaction management is a good example of a
crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented by using regular
classes (the schema-based approach) or regular classes annotated with the @Aspect annotation (
the @AspectJ style).
方面:跨越多个类的关注点的模块化。事务管理是企业 Java 应用程序中跨切面关注点的良好示例。在 Spring AOP
中,方面通过使用常规类(基于模式的方案)或使用 @Aspect 注解的常规类(@AspectJ 风格)来实现。
Join point: A point during the execution of a program, such as the execution of a method or the handling of an
exception. In Spring AOP, a join point always represents a method execution.
连接点:程序执行过程中的一个点,例如方法执行或异常处理。在 Spring AOP 中,连接点始终代表方法执行。
Advice: Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and
“after” advice.
建议:在特定连接点由方面采取的操作。不同类型的建议包括“周围”、“之前”和“之后”建议。
(Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and
maintain a chain of interceptors around the join point.
(建议类型将在后面讨论。)许多 AOP 框架,包括 Spring,将建议建模为拦截器,并在连接点周围维护一个拦截器链。
Pointcut: A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join
point matched by the pointcut (for example, the execution of a method with a certain name).
切入点:匹配连接点的谓词。建议与切入点表达式相关联,并在切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。
The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut
expression language by default.
连接点概念与切点表达式匹配是 AOP 的核心,Spring 默认使用 AspectJ 切点表达式语言。
Introduction: Declaring additional methods or fields on behalf of a type. Spring AOP lets you introduce new
interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to
make a bean implement an IsModified interface, to simplify caching. (An introduction is known as an inter-type
declaration in the AspectJ community.)
简介:代表类型声明额外的方法或字段。Spring AOP 允许您向任何被建议的对象引入新的接口(及其相应的实现)。例如,您可以使用引入来使一个
Bean 实现 IsModified 接口,以简化缓存。(在 AspectJ 社区中,引入被称为跨类型声明。)
Target object: An object being advised by one or more aspects. Also referred to as the “advised object”. Since Spring
AOP is implemented by using runtime proxies, this object is always a proxied object.
目标对象:被一个或多个方面建议的对象。也称为“建议对象”。由于 Spring AOP 是通过使用运行时代理实现的,因此此对象始终是代理对象。
AOP proxy: An object created by the AOP framework in order to implement the aspect contracts (advise method executions
and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy.
面向切面代理:由 AOP 框架创建的对象,用于实现方面契约(建议方法执行等)。在 Spring 框架中,AOP 代理是 JDK 动态代理或 CGLIB
代理。
Weaving: linking aspects with other application types or objects to create an advised object. This can be done at
compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP
frameworks, performs weaving at runtime.
编织:将方面与其他应用类型或对象链接以创建建议的对象。这可以在编译时(例如使用 AspectJ 编译器)进行,也可以在加载时或在运行时进行。Spring
AOP 与其他纯 Java AOP 框架一样,在运行时执行编织。
Spring AOP includes the following types of advice:
Spring AOP 包括以下类型的建议:
Before advice: Advice that runs before a join point but that does not have the ability to prevent execution flow
proceeding to the join point (unless it throws an exception).
在连接点之前运行的建议,但没有阻止执行流程继续到连接点的能力(除非抛出异常)。
After returning advice: Advice to be run after a join point completes normally (for example, if a method returns
without throwing an exception).
返回建议:在连接点正常完成后运行的建议(例如,如果方法返回而不抛出异常)。
After throwing advice: Advice to be run if a method exits by throwing an exception.
抛出建议后:当方法通过抛出异常退出时需要运行的建议。
After (finally) advice: Advice to be run regardless of the means by which a join point exits (normal or exceptional
return).
在(最终)建议之后:无论通过何种方式退出连接点(正常或异常返回)都要执行的建议。
Around advice: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of
advice. Around advice can perform custom behavior before and after the method invocation.
围绕建议:围绕连接点(如方法调用)的建议。这是最强大的建议类型。围绕建议可以在方法调用前后执行自定义行为。
It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution
by returning its own return value or throwing an exception.
它还负责选择是继续执行连接点,还是通过返回自己的返回值或抛出异常来跳过建议的方法执行。
Around advice is the most general kind of advice. Since Spring AOP, like AspectJ, provides a full range of advice types,
we recommend that you use the least powerful advice type that can implement the required behavior.
围绕建议是最通用的建议类型。由于 Spring AOP,如
AspectJ,提供了一整套建议类型,我们建议您使用能够实现所需行为的最低权限的建议类型。
For example, if you need only to update a cache with the return value of a method, you are better off implementing an
after returning advice than an around advice, although an around advice can accomplish the same thing.
例如,如果您只需要更新一个方法返回值缓存的缓存,您最好实现一个在返回后(after
returning)的建议,而不是一个在周围(around)的建议,尽管周围的建议也能达到同样的效果。
Using the most specific advice type provides a simpler programming model with less potential for errors. For example,
you do not need to invoke the proceed() method on the JoinPoint used for around advice, and, hence, you cannot fail
to invoke it.
使用最具体的建议类型提供了一个更简单的编程模型,错误的可能性更小。例如,您不需要在用于环绕建议的 JoinPoint 上调用
proceed() 方法,因此您不可能调用失败。
All advice parameters are statically typed so that you work with advice parameters of the appropriate type (e.g. the
type of the return value from a method execution) rather than Object arrays.
所有建议参数都是静态类型,这样您可以与适当类型的建议参数(例如方法执行返回值的类型)一起工作,而不是使用 Object 数组。
The concept of join points matched by pointcuts is the key to AOP, which distinguishes it from older technologies
offering only interception. Pointcuts enable advice to be targeted independently of the object-oriented hierarchy.
连接点与切入点匹配的概念是 AOP
的关键,这使其与仅提供拦截功能的老旧技术区分开来。切入点使建议能够独立于面向对象层次结构进行定位。
For example, you can apply an around advice providing declarative transaction management to a set of methods that span
multiple objects (such as all business operations in the service layer).
例如,您可以将提供声明式事务管理的 around 建议应用于跨越多个对象的方法集(例如服务层中的所有业务操作)。
5.2. Spring AOP 功能和目标
Spring AOP is implemented in pure Java. There is no need for a special compilation process. Spring AOP does not need to
control the class loader hierarchy and is thus suitable for use in a servlet container or application server.
Spring AOP 使用纯 Java 实现。无需特殊的编译过程。Spring AOP 不需要控制类加载器层次结构,因此适用于在 servlet 容器或应用服务器中使用。
Spring AOP currently supports only method execution join points (advising the execution of methods on Spring beans).
Field interception is not implemented, although support for field interception could be added without breaking the core
Spring AOP APIs.
Spring AOP 目前仅支持方法执行连接点(在 Spring bean 上建议方法执行)。字段拦截尚未实现,尽管可以在不破坏核心 Spring AOP API
的情况下添加对字段拦截的支持。
If you need to advise field access and update join points, consider a language such as AspectJ.
如果您需要建议字段访问和更新连接点,请考虑使用 AspectJ 这样的语言。
Spring AOP’s approach to AOP differs from that of most other AOP frameworks. The aim is not to provide the most complete
AOP implementation (although Spring AOP is quite capable).
Spring AOP 对 AOP 的处理方式与其他大多数 AOP 框架不同。目标不是提供最完整的 AOP 实现(尽管 Spring AOP 非常强大)。
Rather, the aim is to provide a close integration between AOP implementation and Spring IoC, to help solve common
problems in enterprise applications.
而不是,目标是提供 AOP 实现与 Spring IoC 的紧密集成,以帮助企业应用程序解决常见问题。
Thus, for example, the Spring Framework’s AOP functionality is normally used in conjunction with the Spring IoC
container. Aspects are configured by using normal bean definition syntax (although this allows powerful “auto-proxying”
capabilities).
因此,例如,Spring 框架的 AOP 功能通常与 Spring IoC 容器一起使用。方面是通过使用常规的 Bean
定义语法进行配置的(尽管这允许强大的“自动代理”功能)。
This is a crucial difference from other AOP implementations. You cannot do some things easily or efficiently with Spring
AOP, such as advise very fine-grained objects (typically, domain objects). AspectJ is the best choice in such cases.
这是与其他 AOP 实现的一个关键区别。在 Spring AOP 中,您无法轻松或高效地执行某些操作,例如对非常细粒度的对象(通常是域对象)进行建议。在这种情况下,AspectJ
是最好的选择。
However, our experience is that Spring AOP provides an excellent solution to most problems in enterprise Java
applications that are amenable to AOP.
然而,我们的经验是,Spring AOP 为大多数适合 AOP 的企业 Java 应用程序问题提供了一个出色的解决方案。
Spring AOP never strives to compete with AspectJ to provide a comprehensive AOP solution. We believe that both
proxy-based frameworks such as Spring AOP and full-blown frameworks such as AspectJ are valuable and that they are
complementary, rather than in competition.
Spring AOP 从不试图与 AspectJ 竞争以提供全面的 AOP 解决方案。我们相信,无论是基于代理的框架如 Spring AOP 还是完整的框架如
AspectJ,它们都是有价值的,并且它们是互补的,而不是相互竞争的。
Spring seamlessly integrates Spring AOP and IoC with AspectJ, to enable all uses of AOP within a consistent Spring-based
application architecture. This integration does not affect the Spring AOP API or the AOP Alliance API. Spring AOP
remains backward-compatible. See the following chapter for a discussion of the Spring AOP APIs.
Spring 无缝集成 Spring AOP 和 IoC 与 AspectJ,以实现在一致的基于 Spring 的应用程序架构中所有 AOP 的使用。此集成不影响
Spring AOP API 或 AOP 联盟 API。Spring AOP 保持向后兼容。请参阅以下章节,以讨论 Spring AOP API。
One of the central tenets of the Spring Framework is that of non-invasiveness. This is the idea that you should not be
forced to introduce framework-specific classes and interfaces into your business or domain model.
Spring 框架的核心原则之一是非侵入性。这是指你不应该被迫将框架特定的类和接口引入到你的业务或领域模型中。
However, in some places, the Spring Framework does give you the option to introduce Spring Framework-specific
dependencies into your codebase.
然而,在某些地方,Spring 框架确实给了你将特定于 Spring 框架的依赖项引入代码库的选项。
The rationale in giving you such options is because, in certain scenarios, it might be just plain easier to read or code
some specific piece of functionality in such a way.
提供这些选项的合理性在于,在某些情况下,以这种方式阅读或编写某些特定功能可能只是更简单。
However, the Spring Framework (almost) always offers you the choice: You have the freedom to make an informed decision
as to which option best suits your particular use case or scenario.
然而,Spring 框架(几乎)总是为你提供选择:你可以自由地做出明智的决定,选择最适合你特定用例或场景的选项。
One such choice that is relevant to this chapter is that of which AOP framework (and which AOP style) to choose. You
have the choice of AspectJ, Spring AOP, or both.
这样的选择与本章相关,即选择哪个 AOP 框架(以及哪种 AOP 风格)。您可以选择 AspectJ、Spring AOP 或两者都选。
You also have the choice of either the @AspectJ annotation-style approach or the Spring XML configuration-style
approach.
您也可以选择使用@AspectJ 注解风格的方案或 Spring XML 配置风格的方案。
The fact that this chapter chooses to introduce the @AspectJ-style approach first should not be taken as an indication
that the Spring team favors the @AspectJ annotation-style approach over the Spring XML configuration-style.
本章选择首先介绍@AspectJ 风格的途径,不应被视为 Spring 团队倾向于@AspectJ 注解风格方法而非 Spring XML 配置风格的指示。
See Choosing which AOP Declaration Style to Use for a more complete discussion of the “whys and
wherefores” of each style.
查看《选择使用哪种 AOP 声明风格》以获得对每种风格“原因和目的”的更完整讨论。
Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of
interfaces) to be proxied.
Spring AOP 默认使用标准 JDK 动态代理来创建 AOP 代理。这允许任何接口(或接口集)被代理。
Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. By default, CGLIB is
used if a business object does not implement an interface.
Spring AOP 也可以使用 CGLIB 代理。这是为了代理类而不是接口。默认情况下,如果业务对象没有实现接口,则使用 CGLIB。
As it is good practice to program to interfaces rather than classes, business classes normally implement one or more
business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where
you need to advise a method that is not declared on an interface or where you need to pass a proxied object to a method
as a concrete type.
由于将编程面向接口而不是面向类视为良好实践,业务类通常实现一个或多个业务接口。在那些(希望是罕见的)需要通知接口上未声明的方法或需要将代理对象作为具体类型传递给方法的场合,可以强制使用
CGLIB。
It is important to grasp the fact that Spring AOP is proxy-based.
See Understanding AOP Proxies for a thorough examination of exactly what this
implementation detail actually means.
重要的是要了解 Spring AOP 是基于代理的。参见《理解 AOP 代理》以详细了解这一实现细节的实际含义。
@AspectJ refers to a style of declaring aspects as regular Java classes annotated with annotations. The @AspectJ style
was introduced by the AspectJ project as part of the AspectJ 5 release. Spring
interprets the same annotations as AspectJ 5, using a library supplied by AspectJ for pointcut parsing and matching. The
AOP runtime is still pure Spring AOP, though, and there is no dependency on the AspectJ compiler or weaver.
@AspectJ 指的是一种将方面声明为带有注解的常规 Java 类的风格。@AspectJ 风格是由 AspectJ 项目在 AspectJ 5 版本中引入的。Spring
将与 AspectJ 5 相同的注解视为相同,使用 AspectJ 提供的库进行切入点解析和匹配。然而,AOP 运行时仍然是纯 Spring AOP,且不依赖于
AspectJ 编译器或织入器。
Using the AspectJ compiler and weaver enables use of the full AspectJ language and is discussed
in Using AspectJ with Spring Applications.
使用 AspectJ 编译器和织入器可以启用完整的 AspectJ 语言功能,这在《使用 Spring 应用程序中的 AspectJ》一文中有所讨论。
5.4.1. 启用@AspectJ 支持
To use @AspectJ aspects in a Spring configuration, you need to enable Spring support for configuring Spring AOP based on
@AspectJ aspects and auto-proxying beans based on whether or not they are advised by those aspects.
要在 Spring 配置中使用@AspectJ 方面,您需要启用 Spring 对基于@AspectJ 方面的 Spring AOP 配置的支持,并根据这些方面是否建议了这些
bean 来自动代理 bean。
By auto-proxying, we mean that, if Spring determines that a bean is advised by one or more aspects, it automatically
generates a proxy for that bean to intercept method invocations and ensures that advice is run as needed.
通过自动代理,我们指的是,如果 Spring 确定一个 Bean 被一个或多个方面所建议,它将自动为该 Bean 生成一个代理来拦截方法调用,并确保在需要时运行建议。
The @AspectJ support can be enabled with XML- or Java-style configuration. In either case, you also need to ensure that
AspectJ’s aspectjweaver.jar library is on the classpath of your application (version 1.8 or later). This library is
available in the lib directory of an AspectJ distribution or from the Maven Central repository.
the @AspectJ 支持可以通过 XML 或 Java 风格的配置启用。在任一情况下,您还需要确保 AspectJ 的 aspectjweaver.jar
库位于您的应用程序类路径上(版本 1.8 或更高)。此库可在 AspectJ 分发的 lib 目录中找到,或从 Maven Central 仓库获取。
启用 Java 配置中的@AspectJ 支持
To enable @AspectJ support with Java @Configuration, add the @EnableAspectJAutoProxy annotation, as the following
example shows:
要启用 Java @Configuration 的@AspectJ 支持,请添加 @EnableAspectJAutoProxy 注解,如下例所示:
启用 XML 配置的@AspectJ 支持
To enable @AspectJ support with XML-based configuration, use the aop:aspectj-autoproxy element, as the following
example shows:
要启用基于 XML 的配置的@AspectJ 支持,请使用 aop:aspectj-autoproxy 元素,如下例所示:
This assumes that you use schema support as described in XML Schema-based configuration.
See the AOP schema for how to import the tags in the aop namespace.
这假设您使用如 XML Schema-based 配置中所述的架构支持。请参阅 AOP 架构了解如何在 aop 命名空间中导入标签。
5.4.2. 声明一个方面
With @AspectJ support enabled, any bean defined in your application context with a class that is an @AspectJ aspect (has
the @Aspect annotation) is automatically detected by Spring and used to configure Spring AOP. The next two examples
show the minimal definition required for a not-very-useful aspect.
启用@AspectJ 支持后,您应用程序上下文中定义的任何具有@AspectJ 方面(具有 @Aspect 注解)的类的 bean 都将自动被 Spring
检测并用于配置 Spring AOP。以下两个示例显示了创建一个不太有用的方面所需的最小定义。
The first of the two example shows a regular bean definition in the application context that points to a bean class that
has the @Aspect annotation:
第一个示例显示了应用程序上下文中的一个常规的 bean 定义,它指向一个带有 @Aspect 注解的 bean 类:
The second of the two examples shows the NotVeryUsefulAspect class definition, which is annotated with the
org.aspectj.lang.annotation.Aspect annotation;
第二个示例展示了带有 org.aspectj.lang.annotation.Aspect 注解的 NotVeryUsefulAspect 类定义;
Aspects (classes annotated with @Aspect) can have methods and fields, the same as any other class. They can also
contain pointcut, advice, and introduction (inter-type) declarations.
方面(用 @Aspect 注解的类)可以拥有方法和字段,就像任何其他类一样。它们还可以包含切入点、建议和引入(跨类型)声明。
Autodetecting aspects through component scanning
自动通过组件扫描检测方面
You can register aspect classes as regular beans in your Spring XML configuration, via @Bean methods in
@Configuration classes, or have Spring autodetect them through classpath scanning — the same as any other
Spring-managed bean. However, note that the @Aspect annotation is not sufficient for autodetection in the classpath.
For that purpose, you need to add a separate @Component annotation (or, alternatively, a custom stereotype annotation
that qualifies, as per the rules of Spring’s component scanner).
您可以将方面类注册为 Spring XML 配置中的常规 bean,通过 @Configuration 类中的 @Bean 方法,或者让 Spring
通过类路径扫描自动检测它们——就像任何其他 Spring 管理的 bean 一样。然而,请注意, @Aspect 注解不足以在类路径中进行自动检测。为此,您需要添加一个单独的
@Component 注解(或者,作为替代,一个符合 Spring 组件扫描器规则的定制.stereotype 注解)。
Advising aspects with other aspects?
咨询与其他方面的关系?
In Spring AOP, aspects themselves cannot be the targets of advice from other aspects. The @Aspect annotation on a
class marks it as an aspect and, hence, excludes it from auto-proxying.
在 Spring AOP 中,方面本身不能成为其他方面建议的目标。类上的 @Aspect 注解将其标记为方面,因此排除了自动代理。
5.4.3. 声明切入点
Pointcuts determine join points of interest and thus enable us to control when advice runs. Spring AOP only supports
method execution join points for Spring beans, so you can think of a pointcut as matching the execution of methods on
Spring beans.
切入点确定感兴趣的连接点,从而使我们能够控制建议何时运行。Spring AOP 仅支持 Spring bean 的方法执行连接点,因此您可以将切入点视为匹配
Spring bean 上方法的执行。
A pointcut declaration has two parts: a signature comprising a name and any parameters and a pointcut expression that
determines exactly which method executions we are interested in.
切点声明包含两部分:一个包含名称和任何参数的签名,以及一个确定我们感兴趣的确切方法执行的切点表达式。
In the @AspectJ annotation-style of AOP, a pointcut signature is provided by a regular method definition, and the
pointcut expression is indicated by using the @Pointcut annotation (the method serving as the pointcut signature must
have a void return type).
在 AOP 的@AspectJ 注解风格中,切点签名由一个常规方法定义提供,切点表达式通过使用 @Pointcut 注解来指示(作为切点签名的该方法必须有一个
void 返回类型)。
An example may help make this distinction between a pointcut signature and a pointcut expression clear. The following
example defines a pointcut named anyOldTransfer that matches the execution of any method named transfer:
一个例子可以帮助明确点切签名和点切表达式的区别。以下示例定义了一个名为 anyOldTransfer 的点切,它匹配任何名为 transfer
的方法的执行:
The pointcut expression that forms the value of the @Pointcut annotation is a regular AspectJ pointcut expression. For
a full discussion of AspectJ’s pointcut language, see
the AspectJ Programming Guide (and, for extensions,
the AspectJ 5 Developer’s Notebook) or one of
the books on AspectJ (such as Eclipse AspectJ, by Colyer et al., or AspectJ in Action, by Ramnivas Laddad).
切入点表达式形成 @Pointcut 注解的值是一个标准的 AspectJ 切入点表达式。关于 AspectJ 切入点语言的全面讨论,请参阅 AspectJ
编程指南(以及扩展部分,AspectJ 5 开发者笔记本)或关于 AspectJ 的书籍(如 Colyer 等人所著的《Eclipse AspectJ》或 Ramnivas
Laddad 所著的《AspectJ in Action》)。
支持切点设计符
Spring AOP supports the following AspectJ pointcut designators (PCD) for use in pointcut expressions:
Spring AOP 支持以下 AspectJ 切入点设计符(PCD)用于切入点表达式:
execution: For matching method execution join points. This is the primary pointcut designator to use when working
with Spring AOP.
execution :用于匹配方法执行连接点。这是在处理 Spring AOP 时使用的首选切入点设计符。
within: Limits matching to join points within certain types (the execution of a method declared within a matching
type when using Spring AOP).
within :将匹配限制在特定类型的连接点内(在 Spring AOP 中使用时,匹配类型内声明的方法的执行)。
this: Limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (
Spring AOP proxy) is an instance of the given type.
this :将匹配限制在连接点(使用 Spring AOP 时方法的执行)处,其中 bean 引用(Spring AOP 代理)是给定类型的实例。
target: Limits matching to join points (the execution of methods when using Spring AOP) where the target object (
application object being proxied) is an instance of the given type.
target :将匹配限制在连接点(使用 Spring AOP 时方法的执行)处,其中目标对象(被代理的应用程序对象)是给定类型的实例。
args: Limits matching to join points (the execution of methods when using Spring AOP) where the arguments are
instances of the given types.
args : 限制匹配到连接点(使用 Spring AOP 时方法的执行)的参数是给定类型的实例。
@target: Limits matching to join points (the execution of methods when using Spring AOP) where the class of the
executing object has an annotation of the given type.
@target :将匹配限制在具有给定类型注解的执行对象类中(使用 Spring AOP 时方法的执行)的连接点()。
@args: Limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the
actual arguments passed have annotations of the given types.
@args :将匹配限制在连接点(使用 Spring AOP 时方法的执行)处,实际参数的运行时类型具有给定类型的注解。
@within: Limits matching to join points within types that have the given annotation (the execution of methods
declared in types with the given annotation when using Spring AOP).
@within : 限制匹配具有给定注解的类型内的连接点(在 Spring AOP 中使用具有给定注解的类型中声明的方法的执行)。
@annotation: Limits matching to join points where the subject of the join point (the method being run in Spring AOP)
has the given annotation.
@annotation : 限制匹配到具有给定注解的连接点(在 Spring AOP 中运行的正在运行的方法)的连接点。
Because Spring AOP limits matching to only method execution join points, the preceding discussion of the pointcut
designators gives a narrower definition than you can find in the AspectJ programming guide.
因为 Spring AOP 仅将匹配限制在方法执行连接点,所以关于切点设计器的先前讨论给出了比你在 AspectJ
编程指南中找到的更窄的定义。
In addition, AspectJ itself has type-based semantics and, at an execution join point, both this and target refer to
the same object: the object executing the method. Spring AOP is a proxy-based system and differentiates between the
proxy object itself (which is bound to this) and the target object behind the proxy (which is bound to target).
此外,AspectJ 本身具有基于类型的语义,在执行连接点时, this 和 target 都指向同一个对象:执行方法的对象。Spring AOP
是一个基于代理的系统,区分代理对象本身(绑定到 this )和代理后面的目标对象(绑定到 target )。
Due to the proxy-based nature of Spring’s AOP framework, calls within the target object are, by definition, not
intercepted. For JDK proxies, only public interface method calls on the proxy can be intercepted.
由于 Spring 的 AOP 框架基于代理的特性,目标对象内部的调用按定义不会被拦截。对于 JDK
代理,只有代理上的公共接口方法调用可以被拦截。
With CGLIB, public and protected method calls on the proxy are intercepted (and even package-visible methods, if
necessary). However, common interactions through proxies should always be designed through public signatures.
使用 CGLIB,代理上的公共和受保护方法调用将被拦截(如果需要,甚至包括包可见的方法)。然而,通过代理的常见交互应始终通过公共签名来设计。
Note that pointcut definitions are generally matched against any intercepted method. If a pointcut is strictly meant to
be public-only, even in a CGLIB proxy scenario with potential non-public interactions through proxies, it needs to be
defined accordingly.
请注意,切入点定义通常与任何拦截的方法相匹配。如果一个切入点严格限定为仅公共,即使在 CGLIB 代理场景中可能通过代理进行非公共交互,也需要相应地定义。
If your interception needs include method calls or even constructors within the target class, consider the use of
Spring-driven native AspectJ weaving instead of Spring’s proxy-based AOP framework. This constitutes a
different mode of AOP usage with different characteristics, so be sure to make yourself familiar with weaving before
making a decision.
如果您的拦截需求包括目标类中的方法调用甚至构造函数,请考虑使用由 Spring 驱动的原生 AspectJ 织入,而不是 Spring 的基于代理的
AOP 框架。这构成了 AOP 使用的一种不同模式,具有不同的特性,因此在做出决定之前,请确保您熟悉织入。
Spring AOP also supports an additional PCD named bean. This PCD lets you limit the matching of join points to a
particular named Spring bean or to a set of named Spring beans (when using wildcards). The bean PCD has the following
form:
Spring AOP 还支持一个额外的 PCD,名为 bean 。这个 PCD 允许您将连接点的匹配限制在特定的命名 Spring bean 或一组命名 Spring
bean(当使用通配符时)。 bean PCD 的形式如下:
The idOrNameOfBean token can be the name of any Spring bean. Limited wildcard support that uses the * character is
provided, so, if you establish some naming conventions for your Spring beans, you can write a bean PCD expression to
select them. As is the case with other pointcut designators, the bean PCD can be used with the && (and), || (or),
and ! (negation) operators, too.
The bean PCD is supported only in Spring AOP and not in native AspectJ weaving. It is a Spring-specific extension to
the standard PCDs that AspectJ defines and is, therefore, not available for aspects declared in the @Aspect model.
The bean PCD operates at the instance level (building on the Spring bean name concept) rather than at the type level
only (to which weaving-based AOP is limited).
PCD 在实例级别运行(基于 Spring Bean 名称概念),而不是仅在类型级别(织入式 AOP 的限制)。
Instance-based pointcut designators are a special capability of Spring’s proxy-based AOP framework and its close
integration with the Spring bean factory, where it is natural and straightforward to identify specific beans by name.
基于实例的点切设计符是 Spring 基于代理的 AOP 框架及其与 Spring Bean 工厂紧密集成的特殊功能,通过名称识别特定 Bean 既自然又直接。
结合切入点表达式
You can combine pointcut expressions by using &&, || and !. You can also refer to pointcut expressions by name.
The following example shows three pointcut expressions:
您可以通过使用 &&, || 和 ! 来组合切入点表达式。您还可以通过名称引用切入点表达式。以下示例展示了三个切入点表达式:
1
anyPublicOperation matches if a method execution join point represents the execution of any public method.
anyPublicOperation 匹配方法执行连接点表示执行 任何公共方法。
2
inTrading matches if a method execution is in the trading module.
inTrading 匹配如果方法执行在交易模块中。
3
tradingOperation matches if a method execution represents any public method in the trading module.
tradingOperation 匹配如果方法执行代表任何公共方法 交易模块
1
anyPublicOperation matches if a method execution join point represents the execution of any public method.
2
inTrading matches if a method execution is in the trading module.
3
tradingOperation matches if a method execution represents any public method in the trading module.
It is a best practice to build more complex pointcut expressions out of smaller named components, as shown earlier.
构建更复杂的切入点表达式,由较小的命名组件组成,这是一种最佳实践,如前所述。
When referring to pointcuts by name, normal Java visibility rules apply (you can see private pointcuts in the same type,
protected pointcuts in the hierarchy, public pointcuts anywhere, and so on). Visibility does not affect pointcut
matching.
当按名称引用切入点时,适用正常的 Java 可见性规则(你可以在同一类型中看到私有切入点,在层次结构中看到受保护的切入点,在任何地方看到公共切入点等)。可见性不影响切入点匹配。
分享公共切入点定义
When working with enterprise applications, developers often want to refer to modules of the application and particular
sets of operations from within several aspects. We recommend defining a CommonPointcuts aspect that captures common
pointcut expressions for this purpose. Such an aspect typically resembles the following example:
当与企业应用程序一起工作时,开发人员通常希望从多个方面引用应用程序的模块和特定的操作集合。我们建议定义一个用于此目的的
CommonPointcuts 方面,该方面捕获常见的切入点表达式。这样的方面通常类似于以下示例:
You can refer to the pointcuts defined in such an aspect anywhere you need a pointcut expression. For example, to make
the service layer transactional, you could write the following:
您可以参考在该方面定义的点切,在任何需要点切表达式的位置。例如,为了使服务层事务化,您可以编写以下内容:
The <aop:config> and <aop:advisor> elements are discussed in Schema-based AOP Support. The
transaction elements are discussed
in Transaction Management.
<aop:config> 和 <aop:advisor> 元素在基于模式的 AOP 支持中进行了讨论。事务元素在事务管理中进行了讨论。
Spring AOP users are likely to use the execution pointcut designator the most often. The format of an execution
expression follows:
Spring AOP 用户最常使用 execution 切点设计符。执行表达式的格式如下:
All parts except the returning type pattern (ret-type-pattern in the preceding snippet), the name pattern, and the
parameters pattern are optional. The returning type pattern determines what the return type of the method must be in
order for a join point to be matched. * is most frequently used as the returning type pattern. It matches any return
type. A fully-qualified type name matches only when the method returns the given type. The name pattern matches the
method name. You can use the * wildcard as all or part of a name pattern. If you specify a declaring type pattern,
include a trailing . to join it to the name pattern component. The parameters pattern is slightly more complex: ()
matches a method that takes no parameters, whereas (..) matches any number (zero or more) of parameters. The (*)
pattern matches a method that takes one parameter of any type. (*,String) matches a method that takes two parameters.
The first can be of any type, while the second must be a String. Consult
the Language Semantics section of the
AspectJ Programming Guide for more information.
所有部分(除了返回类型模式(前一个片段中的 ret-type-pattern )、名称模式和参数模式)都是可选的。返回类型模式决定了方法返回类型必须是什么,以便匹配连接点。
* 最常用作返回类型模式。它匹配任何返回类型。完全限定的类型名称仅在方法返回给定类型时匹配。名称模式匹配方法名称。您可以使用
* 通配符作为名称模式的所有或部分。如果您指定了声明类型模式,请包括一个尾随的 . 以将其连接到名称模式组件。参数模式稍微复杂一些:
() 匹配不接受参数的方法,而 (..) 匹配任何数量(零个或更多)的参数。 (*) 模式匹配接受任何类型的一个参数的方法。
(*,String) 匹配接受两个参数的方法。第一个可以是任何类型,而第二个必须是 String 。有关更多信息,请参阅 AspectJ
编程指南的语言语义部分。
The following examples show some common pointcut expressions:
以下示例展示了某些常见的切入点表达式:
The execution of any public method:
任何公共方法的执行:
The execution of any method with a name that begins with set:
任何以 set 开头的名称的方法执行
The execution of any method defined by the AccountService interface:
任何由 AccountService 接口定义的方法执行:
The execution of any method defined in the service package:
任何在 service 包中定义的方法的执行:
The execution of any method defined in the service package or one of its sub-packages:
任何在服务包或其子包中定义的方法的执行:
Any join point (method execution only in Spring AOP) within the service package:
任何服务包内的连接点(仅在 Spring AOP 中方法执行)
Any join point (method execution only in Spring AOP) within the service package or one of its sub-packages:
任何服务包或其子包内的连接点(仅在 Spring AOP 中方法执行):
Any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface:
任何代理实现 AccountService 接口的连接点(仅在 Spring AOP 中方法执行)
this is more commonly used in a binding form. See the section on Declaring Advice for how to make the
proxy object available in the advice body.
this 更常用于绑定形式。请参阅“声明建议”部分了解如何在建议体中使代理对象可用。
Any join point (method execution only in Spring AOP) where the target object implements the AccountService
interface:
任何实现 AccountService 接口的目标对象中的连接点(仅在 Spring AOP 中方法执行)
target is more commonly used in a binding form. See the Declaring Advice section for how to make the
target object available in the advice body.
target 更常用于绑定形式。请参阅“声明建议”部分了解如何在建议体中使目标对象可用。
Any join point (method execution only in Spring AOP) that takes a single parameter and where the argument passed at
runtime is Serializable:
任何只接受单个参数的连接点(仅在 Spring AOP 中执行的方法)以及运行时传递的参数为 Serializable :
args is more commonly used in a binding form. See the Declaring Advice section for how to make the
method arguments available in the advice body.
args 更常用于绑定形式。请参阅“声明建议”部分了解如何在建议体中使方法参数可用。
Note that the pointcut given in this example is different from execution(* *(java.io.Serializable)). The args
version matches if the argument passed at runtime is Serializable, and the execution version matches if the method
signature declares a single parameter of type Serializable.
请注意,本例中给出的切入点与 execution(* *(java.io.Serializable)) 不同。args 版本匹配如果运行时传递的参数是
Serializable ,而执行版本匹配如果方法签名声明了一个类型为 Serializable 的单个参数。
Any join point (method execution only in Spring AOP) where the target object has a @Transactional annotation:
任何具有 @Transactional 注解的目标对象所在的连接点(仅在 Spring AOP 中方法执行)
You can also use @target in a binding form. See the Declaring Advice section for how to make the
annotation object available in the advice body.
您也可以使用 @target 进行绑定形式。请参阅声明建议部分了解如何在建议体中使注解对象可用。
Any join point (method execution only in Spring AOP) where the declared type of the target object has an
@Transactional annotation:
任何具有 @Transactional 注解的目标对象声明的类型所在的连接点(仅在 Spring AOP 中方法执行)
You can also use @within in a binding form. See the Declaring Advice section for how to make the
annotation object available in the advice body.
您也可以使用 @within 进行绑定形式。请参阅声明建议部分了解如何在建议体中使注解对象可用。
Any join point (method execution only in Spring AOP) where the executing method has an @Transactional annotation:
任何具有 @Transactional 注解的执行方法(仅在 Spring AOP 中方法执行)的连接点:
You can also use @annotation in a binding form. See the Declaring Advice section for how to make the
annotation object available in the advice body.
您也可以使用 @annotation 进行绑定形式。请参阅声明建议部分了解如何在建议体中使注解对象可用。
Any join point (method execution only in Spring AOP) which takes a single parameter, and where the runtime type of the
argument passed has the @Classified annotation:
任何仅接受单个参数的连接点(仅在 Spring AOP 中执行的方法),并且传递的参数的运行时类型具有 @Classified 注解:
You can also use @args in a binding form. See the Declaring Advice section how to make the annotation
object(s) available in the advice body.
您也可以使用 @args 进行绑定形式。请参阅声明建议部分了解如何在建议体中使注解对象可用。
Any join point (method execution only in Spring AOP) on a Spring bean named tradeService:
任何名为 tradeService 的 Spring Bean 上的任何连接点(仅在 Spring AOP 中方法执行)
Any join point (method execution only in Spring AOP) on Spring beans having names that match the wildcard expression
*Service:
任何名称匹配通配符表达式 *Service 的 Spring bean 的连接点(仅在 Spring AOP 中执行方法)
During compilation, AspectJ processes pointcuts in order to optimize matching performance. Examining code and
determining if each join point matches (statically or dynamically) a given pointcut is a costly process.
在编译过程中,AspectJ
处理切入点以优化匹配性能。检查代码并确定每个连接点(静态或动态)是否与给定的切入点匹配是一个耗时的过程。
(A dynamic match means the match cannot be fully determined from static analysis and that a test is placed in the code
to determine if there is an actual match when the code is running).
(动态匹配意味着无法从静态分析中完全确定匹配,需要在代码中放置一个测试以确定代码运行时是否存在实际匹配)。
On first encountering a pointcut declaration, AspectJ rewrites it into an optimal form for the matching process. What
does this mean?
首次遇到切点声明时,AspectJ 将其重写为匹配过程的最佳形式。这是什么意思?
Basically, pointcuts are rewritten in DNF (Disjunctive Normal Form) and the components of the pointcut are sorted such
that those components that are cheaper to evaluate are checked first. This means you do not have to worry about
understanding the performance of various pointcut designators and may supply them in any order in a pointcut
declaration.
基本上,切入点被重写为 DNF(析取范式)形式,并且切入点组件被排序,以便首先检查那些评估成本较低的组件。这意味着您不必担心理解各种切入点设计器的性能,可以在切入点声明中以任何顺序提供它们。
However, AspectJ can work only with what it is told. For optimal performance of matching, you should think about what
they are trying to achieve and narrow the search space for matches as much as possible in the definition.
然而,AspectJ
只能按照它被告知的内容工作。为了匹配的最佳性能,你应该考虑它们试图实现的目标,并在定义中尽可能缩小匹配搜索空间。
The existing designators naturally fall into one of three groups: kinded, scoping, and contextual:
现有的标识符自然分为三类:类型、作用域和上下文:
Kinded designators select a particular kind of join point: execution, get, set, call, and handler.
Kinded 设计符选择特定的连接点: execution , get , set , call ,和 handler 。
Scoping designators select a group of join points of interest (probably of many kinds): within and withincode
作用域指定符选择一组感兴趣的连接点(可能是多种类型): within 和 withincode
Contextual designators match (and optionally bind) based on context: this, target, and @annotation
上下文标识符根据上下文匹配(并可可选绑定): this , target ,和 @annotation
A well written pointcut should include at least the first two types (kinded and scoping). You can include the contextual
designators to match based on join point context or bind that context for use in the advice.
一个编写良好的切入点应至少包括前两种类型(类型和范围)。您可以根据连接点上下文包含上下文设计符进行匹配,或将该上下文绑定以在通知中使用。
Supplying only a kinded designator or only a contextual designator works but could affect weaving performance (time and
memory used), due to extra processing and analysis.
仅提供一种类型标识符或仅提供上下文标识符可行,但可能会影响编织性能(时间和内存使用),因为需要额外的处理和分析。
Scoping designators are very fast to match, and using them means AspectJ can very quickly dismiss groups of join points
that should not be further processed. A good pointcut should always include one if possible.
范围指定符匹配非常快,使用它们意味着 AspectJ 可以非常快速地排除不应进一步处理的连接点组。一个好的切入点如果可能的话,应该始终包含一个。
Advice is associated with a pointcut expression and runs before, after, or around method executions matched by the
pointcut. The pointcut expression may be either a simple reference to a named pointcut or a pointcut expression declared
in place.
建议与一个切入点表达式相关联,在匹配切入点的目标方法执行之前、之后或周围运行。切入点表达式可以是命名切入点的一个简单引用,也可以是声明在当前位置的切入点表达式。
You can declare before advice in an aspect by using the @Before annotation:
您可以在方面中使用 @Before 注解在建议之前进行声明:
If we use an in-place pointcut expression, we could rewrite the preceding example as the following example:
如果我们使用原地切入点表达式,我们可以将前面的示例重写为以下示例:
After returning advice runs when a matched method execution returns normally. You can declare it by using the
@AfterReturning annotation:
在匹配的方法执行正常返回后运行建议。您可以通过使用 @AfterReturning 注解来声明它:
You can have multiple advice declarations (and other members as well), all inside the same aspect. We show only a single
advice declaration in these examples to focus the effect of each one.
您可以在同一个方面内拥有多个建议声明(以及其他成员),我们在这几个例子中只展示一个建议声明,以集中展示每个声明的影响。
Sometimes, you need access in the advice body to the actual value that was returned. You can use the form of
@AfterReturning that binds the return value to get that access, as the following example shows:
有时,您需要在建议体中访问实际返回的值。您可以使用 @AfterReturning 的形式将返回值绑定以获取访问,如下例所示:
The name used in the returning attribute must correspond to the name of a parameter in the advice method. When a
method execution returns, the return value is passed to the advice method as the corresponding argument value. A
returning clause also restricts matching to only those method executions that return a value of the specified type (in
this case, Object, which matches any return value).
在 returning 属性中使用的名称必须与建议方法中参数的名称相对应。当方法执行返回时,返回值作为相应的参数值传递给建议方法。
returning 子句还限制匹配仅限于返回指定类型值的方法执行(在这种情况下, Object ,匹配任何返回值)。
Please note that it is not possible to return a totally different reference when using after returning advice.
请注意,在使用返回建议后,无法返回完全不同的参考。
After throwing advice runs when a matched method execution exits by throwing an exception. You can declare it by using
the @AfterThrowing annotation, as the following example shows:
在匹配的方法执行退出时抛出异常后运行建议操作。您可以通过使用 @AfterThrowing 注解来声明它,如下例所示:
Often, you want the advice to run only when exceptions of a given type are thrown, and you also often need access to the
thrown exception in the advice body. You can use the throwing attribute to both restrict matching (if desired — use
Throwable as the exception type otherwise) and bind the thrown exception to an advice parameter. The following example
shows how to do so:
通常,您希望仅在抛出给定类型的异常时执行建议,并且您通常还需要在建议体中访问抛出的异常。您可以使用 throwing
属性来限制匹配(如果需要——否则使用 Throwable 作为异常类型)并将抛出的异常绑定到建议参数。以下示例展示了如何做到这一点:
The name used in the throwing attribute must correspond to the name of a parameter in the advice method. When a method
execution exits by throwing an exception, the exception is passed to the advice method as the corresponding argument
value. A throwing clause also restricts matching to only those method executions that throw an exception of the
specified type (DataAccessException, in this case).
在 throwing 属性中使用的名称必须与建议方法中参数的名称相对应。当方法通过抛出异常退出时,异常作为相应的参数值传递给建议方法。一个
throwing 子句也仅将匹配限制在抛出指定类型( DataAccessException ,在这种情况下)异常的方法执行上。
Note that @AfterThrowing does not indicate a general exception handling callback. Specifically, an @AfterThrowing
advice method is only supposed to receive exceptions from the join point (user-declared target method) itself but not
from an accompanying @After/@AfterReturning method.
请注意, @AfterThrowing 并不表示一个通用的异常处理回调。特别是, @AfterThrowing 建议方法仅应接收来自连接点(用户声明的目标方法)本身的异常,而不是来自伴随的
@After / @AfterReturning 方法。
After (finally) advice runs when a matched method execution exits. It is declared by using the @After annotation.
After advice must be prepared to handle both normal and exception return conditions. It is typically used for releasing
resources and similar purposes. The following example shows how to use after finally advice:
在匹配的方法执行退出后(最终)执行建议。它通过使用 @After 注解声明。在建议中必须准备好处理正常和异常返回条件。它通常用于释放资源等类似目的。以下示例展示了如何使用
after finally 建议:
Note that @After advice in AspectJ is defined as "after finally advice", analogous to a finally block in a try-catch
statement. It will be invoked for any outcome, normal return or exception thrown from the join point (user-declared
target method), in contrast to @AfterReturning which only applies to successful normal returns.
请注意,AspectJ 中的 @After 建议定义为“finally 建议”,类似于 try-catch 语句中的 finally
块。它将在任何结果下被调用,无论是正常返回还是从连接点(用户声明的目标方法)抛出的异常,与 @AfterReturning 不同,它仅适用于成功的正常返回。
The last kind of advice is around advice. Around advice runs "around" a matched method’s execution. It has the
opportunity to do work both before and after the method runs and to determine when, how, and even if the method actually
gets to run at all.
最后一种建议是关于建议。它围绕着匹配方法的执行。它有机会在方法运行前后进行工作,并决定方法何时、如何以及是否真正运行。
Around advice is often used if you need to share state before and after a method execution in a thread-safe manner – for
example, starting and stopping a timer.
在需要以线程安全的方式在方法执行前后共享状态时,通常会使用环绕通知 - 例如,启动和停止计时器。
Always use the least powerful form of advice that meets your requirements.
始终使用满足您需求的最低效力的建议形式。
For example, do not use around advice if before advice is sufficient for your needs.
例如,如果之前的建议足以满足您的需求,则无需使用围绕建议。
Around advice is declared by annotating a method with the @Around annotation. The method should declare Object as
its return type, and the first parameter of the method must be of type ProceedingJoinPoint. Within the body of the
advice method, you must invoke proceed() on the ProceedingJoinPoint in order for the underlying method to run.
Invoking proceed() without arguments will result in the caller’s original arguments being supplied to the underlying
method when it is invoked. For advanced use cases, there is an overloaded variant of the proceed() method which
accepts an array of arguments (Object). The values in the array will be used as the arguments to the underlying method
when it is invoked.
围绕建议是通过使用 @Around 注解来声明的方法。该方法应将 Object 声明为其返回类型,并且方法的第一参数必须是类型
ProceedingJoinPoint 。在建议方法的主体中,您必须对 ProceedingJoinPoint 上的 proceed() 进行调用,以便底层方法运行。不带参数调用
proceed() 将导致调用者的原始参数在调用底层方法时被提供。对于高级用例, proceed() 方法有一个接受参数数组( Object
)的重载变体。数组中的值将在调用底层方法时用作参数。
The behavior of proceed when called with an Object is a little different than the behavior of proceed for around
advice compiled by the AspectJ compiler. For around advice written using the traditional AspectJ language, the number of
arguments passed to proceed must match the number of arguments passed to the around advice (not the number of
arguments taken by the underlying join point), and the value passed to proceed in a given argument position supplants
the original value at the join point for the entity the value was bound to (do not worry if this does not make sense
right now).
当使用 Object 调用 proceed 时,其行为与 AspectJ 编译器编译的 around advice 的行为略有不同。对于使用传统 AspectJ 语言编写的
around advice,传递给 proceed 的参数数量必须与传递给 around advice
的参数数量匹配(而不是底层连接点的参数数量),并且传递给特定参数位置的值将取代绑定到该值的连接点上的原始值(如果现在还不明白,请不要担心)。
The approach taken by Spring is simpler and a better match to its proxy-based, execution-only semantics. You only need
to be aware of this difference if you compile @AspectJ aspects written for Spring and use proceed with arguments
with the AspectJ compiler and weaver. There is a way to write such aspects that is 100% compatible across both Spring
AOP and AspectJ, and this is discussed in
the following section on advice parameters.
Spring 采用的方法更简单,更符合其基于代理、仅执行语义。只有当你使用 AspectJ 编译器和织入器编译为 Spring 编写的 @AspectJ
方面,并使用 proceed 带有参数时,你才需要意识到这种差异。有一种编写这样的方面的方式,可以在 Spring AOP 和 AspectJ 之间
100%兼容,这将在以下关于建议参数的章节中讨论。
The value returned by the around advice is the return value seen by the caller of the method. For example, a simple
caching aspect could return a value from a cache if it has one or invoke proceed() (and return that value) if it does
not. Note that proceed may be invoked once, many times, or not at all within the body of the around advice. All of
these are legal.
返回 around 建议的值是方法调用者看到的返回值。例如,一个简单的缓存方面如果有一个缓存,则可以返回缓存中的值;如果没有,则可以调用
proceed() (并返回该值)。请注意, proceed 可能在 around 建议的体内被调用一次、多次或根本不调用。所有这些都是合法的。
If you declare the return type of your around advice method as void, null will always be returned to the caller,
effectively ignoring the result of any invocation of proceed(). It is therefore recommended that an around advice
method declare a return type of Object. The advice method should typically return the value returned from an
invocation of proceed(), even if the underlying method has a void return type. However, the advice may optionally
return a cached value, a wrapped value, or some other value depending on the use case.
如果您将环绕通知方法的返回类型声明为 void ,则 null 将始终返回给调用者,从而有效地忽略对 proceed()
的任何调用的结果。因此,建议环绕通知方法声明一个返回类型为 Object 。建议通知方法通常返回从 proceed()
调用返回的值,即使底层方法的返回类型为 void 。然而,根据用例,通知方法可以选择返回缓存值、包装值或其他值。
The following example shows how to use around advice:
以下示例展示了如何使用环绕建议:
Spring offers fully typed advice, meaning that you declare the parameters you need in the advice signature (as we saw
earlier for the returning and throwing examples) rather than work with Object arrays all the time. We see how to make
argument and other contextual values available to the advice body later in this section. First, we take a look at how to
write generic advice that can find out about the method the advice is currently advising.
春季提供完全类型化的建议,意味着你在建议签名中声明所需的参数(正如我们之前在返回和抛出示例中看到的),而不是始终使用
Object 数组。在本节稍后,我们将看到如何使参数和其他上下文值对建议体可用。首先,我们来看看如何编写可以找出当前建议的方法的泛型建议。
JoinPoint访问当前 JoinPoint
Any advice method may declare, as its first parameter, a parameter of type org.aspectj.lang.JoinPoint. Note that
around advice is required to declare a first parameter of type ProceedingJoinPoint, which is a subclass of
JoinPoint.
任何建议方法都可以将其第一个参数声明为类型 org.aspectj.lang.JoinPoint 。请注意,在建议周围必须声明一个类型为
ProceedingJoinPoint 的第一个参数,它是 JoinPoint 的子类。
The JoinPoint interface provides a number of useful methods:
JoinPoint 接口提供了一些有用的方法:
getArgs(): Returns the method arguments.
getArgs() : 返回方法参数。
getThis(): Returns the proxy object.
getThis() : 返回代理对象。
getTarget(): Returns the target object.
getTarget() : 返回目标对象。
getSignature(): Returns a description of the method that is being advised.
getSignature() : 返回正在建议的方法的描述。
toString(): Prints a useful description of the method being advised.
toString() : 打印出被建议的方法的有用描述。
See the javadoc for more
detail.
查看 javadoc 以获取更多详细信息。
传递参数到建议
We have already seen how to bind the returned value or exception value (using after returning and after throwing
advice). To make argument values available to the advice body, you can use the binding form of args. If you use a
parameter name in place of a type name in an args expression, the value of the corresponding argument is passed as the
parameter value when the advice is invoked. An example should make this clearer. Suppose you want to advise the
execution of DAO operations that take an Account object as the first parameter, and you need access to the account in
the advice body. You could write the following:
我们已经看到如何绑定返回值或异常值(使用返回后和抛出后通知)。为了使参数值可用于通知体,您可以使用 args 的绑定形式。如果您在
args 表达式中用参数名代替类型名,则在调用通知时,将传递相应参数的值。以下示例将使这一点更清晰。假设您想通知以 Account
对象作为第一个参数的 DAO 操作执行,并且您需要在通知体中访问账户。您可以编写以下内容:
The args(account,..) part of the pointcut expression serves two purposes. First, it restricts matching to only those
method executions where the method takes at least one parameter, and the argument passed to that parameter is an
instance of Account. Second, it makes the actual Account object available to the advice through the account
parameter.
点切表达式中的 args(account,..) 部分有两个作用。首先,它限制匹配只针对那些方法执行,其中方法至少有一个参数,并且传递给该参数的参数是一个
Account 的实例。其次,它通过 account 参数使实际的 Account 对象可用于通知。
Another way of writing this is to declare a pointcut that "provides" the Account object value when it matches a join
point, and then refer to the named pointcut from the advice. This would look as follows:
另一种写法是声明一个“提供” Account 对象值的 pointcut,当它与连接点匹配时,然后从 advice 中引用该命名的
pointcut。这看起来如下所示:
See the AspectJ programming guide for more details.
查看 AspectJ 编程指南以获取更多详细信息。
The proxy object (this), target object (target), and annotations (@within, @target, @annotation, and @args)
can all be bound in a similar fashion. The next two examples show how to match the execution of methods annotated with
an @Auditable annotation and extract the audit code:
代理对象( this )、目标对象( target )以及注解( @within 、 @target 、 @annotation 和 @args
)都可以以类似的方式绑定。接下来的两个示例展示了如何匹配带有 @Auditable 注解的方法执行并提取审计代码:
The first of the two examples shows the definition of the @Auditable annotation:
第一个示例展示了 @Auditable 注解的定义:
The second of the two examples shows the advice that matches the execution of @Auditable methods:
第二个例子展示了与 @Auditable 方法执行匹配的建议:
建议参数和泛型
Spring AOP can handle generics used in class declarations and method parameters. Suppose you have a generic type like
the following:
Spring AOP 可以处理类声明和方法参数中使用的泛型。假设你有一个如下所示的泛型类型:
You can restrict interception of method types to certain parameter types by tying the advice parameter to the parameter
type for which you want to intercept the method:
您可以通过将建议参数绑定到您想要拦截的方法的参数类型来限制对方法类型的拦截:
This approach does not work for generic collections. So you cannot define a pointcut as follows:
这种方法不适用于通用集合。因此,您不能如下定义切入点:
To make this work, we would have to inspect every element of the collection, which is not reasonable, as we also cannot
decide how to treat null values in general. To achieve something similar to this, you have to type the parameter to Collection<?> and manually check the type of the elements.
为了使这可行,我们必须检查集合中的每个元素,这并不合理,因为我们也无法决定如何一般性地处理 null 值。为了实现类似的功能,您必须输入 Collection<?>
的参数并手动检查元素类型。
确定参数名称
Parameter binding in advice invocations relies on matching the names used in pointcut expressions to the parameter names
declared in advice and pointcut method signatures.
参数绑定在通知调用中依赖于将切入点表达式中所使用的名称与通知和切入点方法签名中声明的参数名称匹配。
This section uses the terms argument and parameter interchangeably, since AspectJ APIs refer to parameter names as
argument names.
本节中,术语参数和参数可以互换使用,因为 AspectJ API 将参数名称称为参数名称。
Spring AOP uses the following ParameterNameDiscoverer implementations to determine parameter names. Each discoverer
will be given a chance to discover parameter names, and the first successful discoverer wins. If none of the registered
discoverers is capable of determining parameter names, an exception will be thrown.
Spring AOP 使用以下 ParameterNameDiscoverer 实现来确定参数名称。每个发现者都将有机会发现参数名称,第一个成功的发现者获胜。如果注册的任何发现者都无法确定参数名称,将抛出异常。
AspectJAnnotationParameterNameDiscoverer
Uses parameter names that have been explicitly specified by the user via the argNames attribute in the corresponding
advice or pointcut annotation. See Explicit Argument Names for details.
使用用户通过相应的建议或切入点注解中的 argNames 属性显式指定的参数名称。有关详细信息,请参阅显式参数名称。
KotlinReflectionParameterNameDiscoverer
Uses Kotlin reflection APIs to determine parameter names. This discoverer is only used if such APIs are present on the
classpath. Not supported in a GraalVM native image.
使用 Kotlin 反射 API 确定参数名称。只有当类路径上存在此类 API 时,才使用此发现者。不支持在 GraalVM 原生镜像中使用。
StandardReflectionParameterNameDiscoverer
Uses the standard java.lang.reflect.Parameter API to determine parameter names. Requires that code be compiled with
the -parameters flag for javac. Recommended approach on Java 8+.
使用标准 java.lang.reflect.Parameter API 确定参数名称。要求代码使用 -parameters 标志编译以支持 javac 。Java 8+ 推荐方法。
LocalVariableTableParameterNameDiscoverer
Analyzes the local variable table available in the byte code of the advice class to determine parameter names from debug
information. Requires that code be compiled with debug symbols (-g:vars at a minimum). Deprecated as of Spring
Framework 6.0 for removal in Spring Framework 6.1 in favor of compiling code with -parameters. Not supported in a
GraalVM native image unless the corresponding class files are present as resources within the image.
分析建议类字节码中可用的局部变量表,以从调试信息中确定参数名称。要求代码以至少包含调试符号( -g:vars )的方式编译。自
Spring Framework 6.0 起已弃用,并在 Spring Framework 6.1 中移除,以支持使用 -parameters 编译代码。除非在图像中作为资源存在相应的类文件,否则不支持在
GraalVM 本地图像中。
AspectJAdviceParameterNameDiscoverer
Deduces parameter names from the pointcut expression, returning, and throwing clauses. See
the javadoc
for details on the algorithm used.
从切入点表达式中的 returning 和 throwing 子句中推断参数名称。请参阅 javadoc 以了解所用算法的详细信息。
@AspectJ advice and pointcut annotations have an optional argNames attribute that you can use to specify the argument
names of the annotated method.
@AspectJ 建议和切入点注解有一个可选的 argNames 属性,您可以使用它来指定被注解方法的参数名称。
If an @AspectJ aspect has been compiled by the AspectJ compiler (ajc) even without debug information, you do not need
to add the argNames attribute, since the compiler retains the needed information.
如果使用 AspectJ 编译器( ajc )编译了 @AspectJ aspect,即使没有调试信息,您也不需要添加 argNames 属性,因为编译器保留了所需的信息。
Similarly, if an @AspectJ aspect has been compiled with javac using the -parameters flag, you do not need to add the
argNames attribute, since the compiler retains the needed information.
同样,如果一个@AspectJ 方面用 -parameters 标志编译了 javac ,你不需要添加 argNames 属性,因为编译器保留了所需信息。
The following example shows how to use the argNames attribute:
以下示例展示了如何使用 argNames 属性:
If the first parameter is of type JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart, you can omit the name
of the parameter from the value of the argNames attribute. For example, if you modify the preceding advice to receive
the join point object, the argNames attribute does not need to include it:
如果第一个参数是类型 JoinPoint 、 ProceedingJoinPoint 或 JoinPoint.StaticPart ,则可以省略参数名称,从 argNames
属性的值中。例如,如果修改前面的建议以接收连接点对象,则 argNames 属性不需要包含它:
The special treatment given to the first parameter of type JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart
is particularly convenient for advice methods that do not collect any other join point context. In such situations, you
may omit the argNames attribute. For example, the following advice does not need to declare the argNames
attribute:
对类型 JoinPoint 、 ProceedingJoinPoint 或 JoinPoint.StaticPart 的第一个参数的特殊处理,对于不收集任何其他连接点上下文的建议方法特别方便。在这种情况下,您可以省略
argNames 属性。例如,以下建议不需要声明 argNames 属性:
继续进行论证
We remarked earlier that we would describe how to write a proceed call with arguments that works consistently across
Spring AOP and AspectJ. The solution is to ensure that the advice signature binds each of the method parameters in
order. The following example shows how to do so:
我们之前提到,我们将描述如何编写一个在 Spring AOP 和 AspectJ 中都能一致工作的带有参数的 proceed
调用。解决方案是确保通知签名按顺序绑定每个方法参数。以下示例展示了如何做到这一点:
In many cases, you do this binding anyway (as in the preceding example).
在许多情况下,你仍然会这样做绑定(如前例所示)。
What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same
precedence rules as AspectJ to determine the order of advice execution.
当多条建议都想要在同一个连接点运行时会发生什么?Spring AOP 遵循与 AspectJ 相同的优先级规则来确定建议执行的顺序。
The highest precedence advice runs first "on the way in" (so, given two pieces of before advice, the one with highest
precedence runs first).
最高优先级建议首先运行“进入时”(因此,给定两条先前的建议,优先级最高的那条先运行)。
"On the way out" from a join point, the highest precedence advice runs last (so, given two pieces of after advice, the
one with the highest precedence will run second).
从连接点“出去”时,最高优先级的建议最后运行(因此,给定两条后续建议,优先级最高的那一条将最后运行)。
When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify
otherwise, the order of execution is undefined. You can control the order of execution by specifying precedence.
当两个在不同方面定义的建议都需要在同一个连接点运行时,除非您明确指定,否则执行顺序是未定义的。您可以通过指定优先级来控制执行顺序。
This is done in the normal Spring way by either implementing the org.springframework.core.Ordered interface in the
aspect class or annotating it with the @Order annotation. Given two aspects, the aspect returning the lower value from
Ordered.getOrder() (or the annotation value) has the higher precedence.
这是通过在方面类中实现 org.springframework.core.Ordered 接口或用 @Order 注解来完成的,就像正常的 Spring
方式一样。给定两个方面,返回 Ordered.getOrder() (或注解值)较低值的方面具有更高的优先级。
Each of the distinct advice types of a particular aspect is conceptually meant to apply to the join point directly. As a
consequence, an @AfterThrowing advice method is not supposed to receive an exception from an accompanying @After/
@AfterReturning method.
每个特定方面的不同建议类型在概念上旨在直接应用于连接点。因此, @AfterThrowing 建议方法不应该从伴随的 @After /
@AfterReturning 方法接收异常。
As of Spring Framework 5.2.7, advice methods defined in the same @Aspect class that need to run at the same join point
are assigned precedence based on their advice type in the following order, from highest to lowest precedence: @Around,
@Before, @After, @AfterReturning, @AfterThrowing. Note, however, that an @After advice method will effectively
be invoked after any @AfterReturning or @AfterThrowing advice methods in the same aspect, following AspectJ’s "after
finally advice" semantics for @After.
截至 Spring Framework 5.2.7,在同一 @Aspect 类中定义的需要在相同连接点运行的 advice 方法,根据以下顺序按其 advice
类型分配优先级,从最高优先级到最低优先级: @Around , @Before , @After , @AfterReturning , @AfterThrowing
。请注意,然而, @After advice 方法将有效地在同一方面中的任何 @AfterReturning 或 @AfterThrowing advice 方法之后调用,遵循
AspectJ 的“after finally advice”语义 @After 。
When two pieces of the same type of advice (for example, two @After advice methods) defined in the same @Aspect
class both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the source
code declaration order through reflection for javac-compiled classes). Consider collapsing such advice methods into one
advice method per join point in each @Aspect class or refactor the pieces of advice into separate @Aspect classes
that you can order at the aspect level via Ordered or @Order.
当同一类中定义的两个相同类型的建议(例如,两个 @After 建议方法)都需要在同一个 @Aspect 连接点运行时,顺序是未定义的(因为无法通过反射检索
javac 编译类的源代码声明顺序)。考虑将此类建议方法折叠为每个 @Aspect 类中每个连接点的单个建议方法,或者将建议部分重构为可以通過
Ordered 或 @Order 在方面级别排序的单独 @Aspect 类。
Introductions (known as inter-type declarations in AspectJ) enable an aspect to declare that advised objects implement a
given interface, and to provide an implementation of that interface on behalf of those objects.
介绍(在 AspectJ 中称为交叉类型声明)允许方面声明被建议的对象实现一个给定的接口,并代表这些对象提供该接口的实现。
You can make an introduction by using the @DeclareParents annotation. This annotation is used to declare that matching
types have a new parent (hence the name). For example, given an interface named UsageTracked and an implementation of
that interface named DefaultUsageTracked, the following aspect declares that all implementors of service interfaces
also implement the UsageTracked interface (e.g. for statistics via JMX):
您可以使用 @DeclareParents 注解来进行介绍。这个注解用于声明匹配的类型有一个新的父类(因此得名)。例如,给定一个名为
UsageTracked 的接口和一个名为 DefaultUsageTracked 的该接口的实现,以下方面声明了所有服务接口的实现者也实现了
UsageTracked 接口(例如,通过 JMX 进行统计):
The interface to be implemented is determined by the type of the annotated field. The value attribute of the
@DeclareParents annotation is an AspectJ type pattern. Any bean of a matching type implements the UsageTracked
interface. Note that, in the before advice of the preceding example, service beans can be directly used as
implementations of the UsageTracked interface. If accessing a bean programmatically, you would write the following:
要实现的接口由注解字段的类型决定。 @DeclareParents 注解的 value 属性是一个 AspectJ 类型模式。任何匹配类型的 bean 都实现了
UsageTracked 接口。注意,在前一个示例的 before advice 中,服务 bean 可以直接用作 UsageTracked 接口的实现。如果以编程方式访问
bean,你会编写以下内容:
5.4.6. 方面实例化模型
This is an advanced topic. If you are just starting out with AOP, you can safely skip it until later.
这是一个高级主题。如果你刚开始学习面向切面编程(AOP),你可以安全地跳过它,稍后再看。
By default, there is a single instance of each aspect within the application context. AspectJ calls this the singleton
instantiation model. It is possible to define aspects with alternate lifecycles. Spring supports AspectJ’s perthis and
pertarget instantiation models; percflow, percflowbelow, and pertypewithin are not currently supported.
默认情况下,在应用程序上下文中每个方面只有一个实例。AspectJ 称这种实例化为单例实例模型。可以定义具有不同生命周期的方面。Spring
支持 AspectJ 的 perthis 和 pertarget 实例化模型; percflow 、 percflowbelow 和 pertypewithin 目前不支持。
You can declare a perthis aspect by specifying a perthis clause in the @Aspect annotation. Consider the following
example:
您可以通过在注解中指定一个 perthis 子句来声明一个 perthis 方面。考虑以下示例:
In the preceding example, the effect of the perthis clause is that one aspect instance is created for each unique
service object that performs a business service (each unique object bound to this at join points matched by the
pointcut expression). The aspect instance is created the first time that a method is invoked on the service object. The
aspect goes out of scope when the service object goes out of scope.
在先前的示例中, perthis
子句的效果是,对于每个执行业务服务的唯一服务对象(每个唯一对象绑定到与切入点表达式匹配的连接点),创建一个方面实例。方面实例是在第一次在服务对象上调用方法时创建的。当服务对象超出作用域时,方面实例将超出作用域。
Before the aspect instance is created, none of the advice within it runs. As soon as the aspect instance has been
created, the advice declared within it runs at matched join points, but only when the service object is the one with
which this aspect is associated.
在方面实例创建之前,其中没有任何建议运行。一旦方面实例被创建,其中声明的建议将在匹配的连接点运行,但仅当服务对象是与该方面关联的那个对象时。
See the AspectJ Programming Guide for more information on per clauses.
查看 AspectJ 编程指南以获取有关 per 子句的更多信息。
The pertarget instantiation model works in exactly the same way as perthis, but it creates one aspect instance for
each unique target object at matched join points.
pertarget 实例化模型与 perthis 完全以相同的方式工作,但它为每个匹配的连接点上的唯一目标对象创建一个方面实例。
5.4.7. 一个面向切面的示例
Now that you have seen how all the constituent parts work, we can put them together to do something useful.
现在你已经看到了所有组成部分是如何工作的,我们可以将它们组合起来做一些有用的事情。
The execution of business services can sometimes fail due to concurrency issues (for example, a deadlock loser). If the
operation is retried, it is likely to succeed on the next try.
企业服务的执行有时会因并发问题(例如,死锁失败者)而失败。如果重试操作,下一次尝试很可能成功。
For business services where it is appropriate to retry in such conditions (idempotent operations that do not need to go
back to the user for conflict resolution), we want to transparently retry the operation to avoid the client seeing a
PessimisticLockingFailureException. This is a requirement that clearly cuts across multiple services in the service
layer and, hence, is ideal for implementing through an aspect.
对于在适当条件下可以重试的业务服务(不需要回退给用户进行冲突解决的幂等操作),我们希望透明地重试操作,以避免客户端看到
PessimisticLockingFailureException 。这是一个明显跨越多个服务层的需要,因此通过方面实现是理想的。
Because we want to retry the operation, we need to use around advice so that we can call proceed multiple times. The
following listing shows the basic aspect implementation:
因为我们要重试操作,所以需要使用环绕建议,这样我们就可以多次调用 proceed 。以下列表展示了基本方面的实现:
Note that the aspect implements the Ordered interface so that we can set the precedence of the aspect higher than the
transaction advice (we want a fresh transaction each time we retry). The maxRetries and order properties are both
configured by Spring. The main action happens in the doConcurrentOperation around advice. Notice that, for the moment,
we apply the retry logic to each businessService(). We try to proceed, and if we fail with a
PessimisticLockingFailureException, we try again, unless we have exhausted all of our retry attempts.
请注意,该方面实现了 Ordered 接口,以便我们可以将方面的优先级设置高于事务建议(我们希望在每次重试时都有一个新的事务)。
maxRetries 和 order 属性都由 Spring 配置。主要动作发生在 doConcurrentOperation 环绕建议中。注意,目前,我们将重试逻辑应用于每个
businessService() 。我们尝试进行操作,如果遇到 PessimisticLockingFailureException 失败,我们再次尝试,除非我们用尽了所有的重试尝试。
The corresponding Spring configuration follows:
相应的 Spring 配置如下:
To refine the aspect so that it retries only idempotent operations, we might define the following Idempotent
annotation:
为了使方面仅重试幂等操作,我们可能定义以下 Idempotent 注解:
We can then use the annotation to annotate the implementation of service operations. The change to the aspect to retry
only idempotent operations involves refining the pointcut expression so that only @Idempotent operations match, as
follows:
我们可以使用注解来注释服务操作的实现。将仅重试幂等操作的方面更改涉及细化切入点表达式,以便只有 @Idempotent 操作匹配,如下所示:
5.5. 基于模式的 AOP 支持
If you prefer an XML-based format, Spring also offers support for defining aspects using the aop namespace tags. The
exact same pointcut expressions and advice kinds as when using the @AspectJ style are supported. Hence, in this section
we focus on that syntax and refer the reader to the discussion in the previous
section (@AspectJ support) for an understanding of writing pointcut expressions and the binding of
advice parameters.
如果您更喜欢基于 XML 的格式,Spring 也提供了使用 aop 命名空间标签定义方面的支持。支持的切点表达式和通知类型与使用
@AspectJ 风格时完全相同。因此,在本节中,我们重点关注该语法,并将读者指引到上一节(@AspectJ 支持)的讨论,以了解如何编写切点表达式和绑定通知参数。
To use the aop namespace tags described in this section, you need to import the spring-aop schema, as described
in XML Schema-based configuration. See the AOP schema for how to import the tags in
the aop namespace.
要使用本节中描述的 aop 命名空间标签,您需要导入 spring-aop 模式,如 XML Schema-based 配置中所述。请参阅 AOP 模式了解如何在
aop 命名空间中导入标签。
Within your Spring configurations, all aspect and advisor elements must be placed within an <aop:config> element (you
can have more than one <aop:config> element in an application context configuration). An <aop:config> element can
contain pointcut, advisor, and aspect elements (note that these must be declared in that order).
在您的 Spring 配置中,所有切面和顾问元素都必须放置在 <aop:config> 元素内(在应用程序上下文配置中可以有多个
<aop:config> 元素)。 <aop:config> 元素可以包含切入点、顾问和切面元素(请注意,这些元素必须按此顺序声明)。
The <aop:config> style of configuration makes heavy use of Spring’s auto-proxying mechanism. This
can cause issues (such as advice not being woven) if you already use explicit auto-proxying through the use of
BeanNameAutoProxyCreator or something similar. The recommended usage pattern is to use either only the <aop:config>
style or only the AutoProxyCreator style and never mix them.
配置中使用 <aop:config> 风格会大量使用 Spring 的自动代理机制。如果你已经通过使用 BeanNameAutoProxyCreator
或类似的方式显式地使用了自动代理,这可能会导致问题(如建议未被编织)。建议的使用模式是只使用 <aop:config> 风格或只使用
AutoProxyCreator 风格,永远不要混合使用。
5.5.1. 声明一个方面
When you use the schema support, an aspect is a regular Java object defined as a bean in your Spring application
context. The state and behavior are captured in the fields and methods of the object, and the pointcut and advice
information are captured in the XML.
当您使用模式支持时,一个方面是在您的 Spring 应用上下文中定义为一个 bean 的常规 Java 对象。状态和行为被捕获在对象的字段和方法中,而切点和通知信息被捕获在
XML 中。
You can declare an aspect by using the <aop:aspect> element, and reference the backing bean by using the ref
attribute, as the following example shows:
您可以使用 <aop:aspect> 元素声明一个方面,并通过使用 ref 属性引用后端 Bean,如下例所示:
The bean that backs the aspect (aBean in this case) can of course be configured and dependency injected just like any
other Spring bean.
该支持方面的豆(在本例中为 aBean )当然可以像任何其他 Spring 豆一样进行配置和依赖注入。
5.5.2. 声明切入点
You can declare a named pointcut inside an <aop:config> element, letting the pointcut definition be shared across
several aspects and advisors.
您可以在 <aop:config> 元素内声明一个命名切入点,使切入点定义可以在多个方面和顾问之间共享。
A pointcut that represents the execution of any business service in the service layer can be defined as follows:
一个代表服务层中任何业务服务执行的场景点可以定义为如下:
Note that the pointcut expression itself is using the same AspectJ pointcut expression language as described
in @AspectJ support. If you use the schema based declaration style, you can refer to named pointcuts
defined in types (@Aspects) within the pointcut expression. Another way of defining the above pointcut would be as
follows:
请注意,切入点表达式本身使用的是与@AspectJ 支持中描述相同的 AspectJ
切入点表达式语言。如果您使用基于模式的声明风格,可以在切入点表达式中引用在类型(@Aspects)中定义的命名切入点。定义上述切入点的另一种方式如下:
Assume that you have a CommonPointcuts aspect as described
in Sharing Common Pointcut Definitions.
假设您有一个如《共享公共切入点定义》中所述的 CommonPointcuts 方面。
Then declaring a pointcut inside an aspect is very similar to declaring a top-level pointcut, as the following example
shows:
然后,在方面内部声明一个切入点与声明顶级切入点非常相似,如下例所示:
In much the same way as an @AspectJ aspect, pointcuts declared by using the schema based definition style can collect
join point context. For example, the following pointcut collects the this object as the join point context and passes
it to the advice:
与@AspectJ 切面类似,使用基于模式的定义风格声明的切入点可以收集连接点上下文。例如,以下切入点收集 this
对象作为连接点上下文并将其传递给通知:
The advice must be declared to receive the collected join point context by including parameters of the matching names,
as follows:
建议必须通过包含匹配名称的参数来声明接收收集到的连接点上下文,如下所示:
When combining pointcut sub-expressions, && is awkward within an XML document, so you can use the and, or,
and not keywords in place of &&, ||, and !, respectively. For example, the previous pointcut can be
better written as follows:
当组合切点子表达式时,在 XML 文档中使用 && 显得笨拙,因此可以使用 and 、 or 和 not 关键字分别代替
&& 、 || 和 ! 。例如,前面的切点可以更好地写成如下:
Note that pointcuts defined in this way are referred to by their XML id and cannot be used as named pointcuts to form
composite pointcuts. The named pointcut support in the schema-based definition style is thus more limited than that
offered by the @AspectJ style.
请注意,以这种方式定义的切入点被称为 XML id ,不能用作命名切入点来形成组合切入点。因此,基于模式的定义风格中提供的命名切入点支持比@AspectJ
风格提供的更有限。
The schema-based AOP support uses the same five kinds of advice as the @AspectJ style, and they have exactly the same
semantics.
基于模式的 AOP 支持使用与@AspectJ 风格相同的五种类型的建议,它们具有完全相同的语义。
Before advice runs before a matched method execution. It is declared inside an <aop:aspect> by using the
<aop:before> element, as the following example shows:
在匹配的方法执行之前运行建议。它通过使用 <aop:before> 元素在 <aop:aspect> 中声明,如下例所示:
Here, dataAccessOperation is the id of a pointcut defined at the top (<aop:config>) level. To define the pointcut
inline instead, replace the pointcut-ref attribute with a pointcut attribute, as follows:
这里, dataAccessOperation 是在顶部( <aop:config> )级别定义的切点( id )的 dataAccessOperation。要在线定义切点,请将
pointcut-ref 属性替换为 pointcut 属性,如下所示:
As we noted in the discussion of the @AspectJ style, using named pointcuts can significantly improve the readability of
your code.
正如我们在@AspectJ 风格讨论中提到的,使用命名切入点可以显著提高代码的可读性。
The method attribute identifies a method (doAccessCheck) that provides the body of the advice. This method must be
defined for the bean referenced by the aspect element that contains the advice. Before a data access operation is
performed (a method execution join point matched by the pointcut expression), the doAccessCheck method on the aspect
bean is invoked.
method 属性标识了一个提供建议体( doAccessCheck )的方法。此方法必须为包含建议的方面元素的引用的 bean
定义。在执行数据访问操作之前(与切入点表达式匹配的方法执行连接点),将调用方面 bean 上的 doAccessCheck 方法。
After returning advice runs when a matched method execution completes normally. It is declared inside an <aop:aspect>
in the same way as before advice. The following example shows how to declare it:
在匹配的方法执行正常完成后运行返回建议。它像之前一样在 <aop:aspect> 内声明。以下示例展示了如何声明它:
As in the @AspectJ style, you can get the return value within the advice body. To do so, use the returning attribute
to specify the name of the parameter to which the return value should be passed, as the following example shows:
与@AspectJ 风格一样,您可以在通知体内部获取返回值。为此,使用 returning 属性来指定应将返回值传递给哪个参数的名称,如下例所示:
The doAccessCheck method must declare a parameter named retVal. The type of this parameter constrains matching in
the same way as described for @AfterReturning. For example, you can declare the method signature as follows:
该方法必须声明一个名为 retVal 的参数。此参数的类型以与 @AfterReturning 所述相同的方式约束匹配。例如,您可以如下声明方法签名:
After throwing advice runs when a matched method execution exits by throwing an exception. It is declared inside an
<aop:aspect> by using the after-throwing element, as the following example shows:
在匹配的方法执行退出时抛出异常后执行建议运行。它通过使用 after-throwing 元素在 <aop:aspect> 内部声明,如下例所示:
As in the @AspectJ style, you can get the thrown exception within the advice body. To do so, use the throwing
attribute to specify the name of the parameter to which the exception should be passed as the following example shows:
与@AspectJ 风格一样,您可以在通知体中获取抛出的异常。为此,使用 throwing 属性指定应将异常传递给哪个参数,如下例所示:
The doRecoveryActions method must declare a parameter named dataAccessEx. The type of this parameter constrains
matching in the same way as described for @AfterThrowing. For example, the method signature may be declared as
follows:
该方法必须声明一个名为 dataAccessEx 的参数。此参数的类型以与 @AfterThrowing 所述相同的方式约束匹配。例如,方法签名可以声明如下:
After (finally) advice runs no matter how a matched method execution exits. You can declare it by using the after
element, as the following example shows:
在(最终)建议运行之后,无论匹配的方法执行如何退出。您可以通过使用 after 元素来声明它,如下例所示:
The last kind of advice is around advice. Around advice runs "around" a matched method’s execution. It has the
opportunity to do work both before and after the method runs and to determine when, how, and even if the method actually
gets to run at all.
最后一种建议是关于建议。它围绕着匹配方法的执行。它有机会在方法运行前后进行工作,并决定方法何时、如何以及是否真正运行。
Around advice is often used if you need to share state before and after a method execution in a thread-safe manner – for
example, starting and stopping a timer.
在需要以线程安全的方式在方法执行前后共享状态时,通常会使用环绕通知 - 例如,启动和停止计时器。
Always use the least powerful form of advice that meets your requirements.
始终使用满足您需求的最低效力的建议形式。
For example, do not use around advice if before advice is sufficient for your needs.
例如,如果之前的建议足以满足您的需求,则无需使用围绕建议。
You can declare around advice by using the aop:around element. The advice method should declare Object as its return
type, and the first parameter of the method must be of type ProceedingJoinPoint. Within the body of the advice method,
you must invoke proceed() on the ProceedingJoinPoint in order for the underlying method to run. Invoking proceed()
without arguments will result in the caller’s original arguments being supplied to the underlying method when it is
invoked. For advanced use cases, there is an overloaded variant of the proceed() method which accepts an array of
arguments (Object). The values in the array will be used as the arguments to the underlying method when it is invoked.
See Around Advice for notes on calling proceed with an Object.
您可以使用 aop:around 元素声明环绕建议。建议方法应将 Object 声明为其返回类型,并且方法的第一参数必须是类型
ProceedingJoinPoint 。在建议方法的主体中,您必须对 ProceedingJoinPoint 上的 proceed() 进行调用,以便底层方法运行。不带参数调用
proceed() 将导致调用者的原始参数在调用底层方法时被提供。对于高级用例, proceed() 方法有一个重载变体,它接受一个参数数组(
Object )。数组中的值将在调用底层方法时用作参数。有关使用 Object 调用 proceed 的说明,请参阅环绕建议。
The following example shows how to declare around advice in XML:
以下示例展示了如何在 XML 中声明环绕建议:
The implementation of the doBasicProfiling advice can be exactly the same as in the @AspectJ example (minus the
annotation, of course), as the following example shows:
实现 doBasicProfiling 建议可以与@AspectJ 示例中的完全相同(当然,不包括注解),如下例所示:
The schema-based declaration style supports fully typed advice in the same way as described for the @AspectJ support —
by matching pointcut parameters by name against advice method parameters.
See Advice Parameters for details. If you wish to explicitly specify argument names for
the advice methods (not relying on the detection strategies previously described), you can do so by using the
arg-names attribute of the advice element, which is treated in the same manner as the argNames attribute in an
advice annotation (as described in Determining Argument Names). The following
example shows how to specify an argument name in XML:
基于模式的声明风格支持与@AspectJ
支持所述相同的方式完全类型化的建议——通过按名称匹配切入点参数与建议方法参数。有关详细信息,请参阅建议参数。如果您希望显式指定建议方法的参数名称(不依赖于之前描述的检测策略),可以通过使用建议元素的
arg-names 属性来实现,该属性的处理方式与建议注解中的 argNames 属性相同(如“确定参数名称”中所述)。以下示例展示了如何在
XML 中指定参数名称:
The arg-names attribute accepts a comma-delimited list of parameter names.
arg-names 属性接受一个以逗号分隔的参数名称列表。
The following slightly more involved example of the XSD-based approach shows some around advice used in conjunction with
a number of strongly typed parameters:
以下基于 XSD 方法的稍微复杂一点的示例展示了与一些强类型参数结合使用的周围建议:
Next up is the aspect. Notice the fact that the profile(..) method accepts a number of strongly-typed parameters, the
first of which happens to be the join point used to proceed with the method call. The presence of this parameter is an
indication that the profile(..) is to be used as around advice, as the following example shows:
接下来是方面。注意 profile(..) 方法接受多个强类型参数,其中第一个恰好是用于进行方法调用的连接点。这个参数的存在表明应该将
profile(..) 用作 around 建议,如下例所示:
Finally, the following example XML configuration effects the execution of the preceding advice for a particular join
point:
最后,以下示例 XML 配置影响了特定连接点的先前建议的执行:
Consider the following driver script:
考虑以下驱动脚本:
With such a Boot class, we would get output similar to the following on standard output:
使用这样的 Boot 类,我们将在标准输出上得到类似以下的结果:
00000 ? execution(getFoo)
When multiple pieces of advice need to run at the same join point (executing method) the ordering rules are as described
in Advice Ordering. The precedence between aspects is determined via the order
attribute in the <aop:aspect> element or by either adding the @Order annotation to the bean that backs the aspect or
by having the bean implement the Ordered interface.
当多条建议需要在同一连接点(执行方法)运行时,排序规则如建议排序中所述。方面之间的优先级通过 <aop:aspect> 元素中的 order
属性确定,或者通过将 @Order 注解添加到支持方面的 bean,或者通过 bean 实现 Ordered 接口来决定。
In contrast to the precedence rules for advice methods defined in the same @Aspect class, when two pieces of advice
defined in the same <aop:aspect> element both need to run at the same join point, the precedence is determined by the
order in which the advice elements are declared within the enclosing <aop:aspect> element, from highest to lowest
precedence.
与同一 @Aspect 类中定义的建议方法优先级规则相比,当同一 <aop:aspect> 元素中定义的两条建议都需要在同一个连接点运行时,优先级由建议元素在包含的
<aop:aspect> 元素中声明的顺序决定,从最高优先级到最低优先级。
For example, given an around advice and a before advice defined in the same <aop:aspect> element that apply to the
same join point, to ensure that the around advice has higher precedence than the before advice, the <aop:around>
element must be declared before the <aop:before> element.
例如,给定在同一个 <aop:aspect> 元素中定义的 around 建议和 before 建议且应用于相同的连接点,为确保 around 建议的优先级高于
before 建议, <aop:around> 元素必须先于 <aop:before> 元素声明。
As a general rule of thumb, if you find that you have multiple pieces of advice defined in the same <aop:aspect>
element that apply to the same join point, consider collapsing such advice methods into one advice method per join point
in each <aop:aspect> element or refactor the pieces of advice into separate <aop:aspect> elements that you can order
at the aspect level.
一般来说,如果您发现您在同一个 <aop:aspect> 元素中定义了多条适用于相同连接点的建议,请考虑将这些建议方法合并为每个
<aop:aspect> 元素中的每个连接点的一个建议方法,或者将建议拆分为单独的 <aop:aspect> 元素,您可以在方面级别对这些元素进行排序。
Introductions (known as inter-type declarations in AspectJ) let an aspect declare that advised objects implement a given
interface and provide an implementation of that interface on behalf of those objects.
介绍(在 AspectJ 中称为交叉类型声明)允许方面声明被建议的对象实现一个给定的接口,并代表这些对象提供该接口的实现。
You can make an introduction by using the aop:declare-parents element inside an aop:aspect. You can use the
aop:declare-parents element to declare that matching types have a new parent (hence the name). For example, given an
interface named UsageTracked and an implementation of that interface named DefaultUsageTracked, the following aspect
declares that all implementors of service interfaces also implement the UsageTracked interface. (In order to expose
statistics through JMX for example.)
您可以通过在 aop:aspect 中使用 aop:declare-parents 元素来进行介绍。您可以使用 aop:declare-parents
元素来声明匹配的类型有一个新的父类(因此得名)。例如,给定一个名为 UsageTracked 的接口和一个名为 DefaultUsageTracked
的该接口的实现,以下方面声明了所有服务接口的实现者也实现了 UsageTracked 接口。(例如,为了通过 JMX 公开统计信息。)
The class that backs the usageTracking bean would then contain the following method:
该支持 usageTracking 实例的类将包含以下方法:
The interface to be implemented is determined by the implement-interface attribute. The value of the types-matching
attribute is an AspectJ type pattern. Any bean of a matching type implements the UsageTracked interface. Note that, in
the before advice of the preceding example, service beans can be directly used as implementations of the UsageTracked
interface. To access a bean programmatically, you could write the following:
要实现的接口由 implement-interface 属性确定。 types-matching 属性的值是一个 AspectJ 类型模式。任何匹配类型的 bean 都实现了
UsageTracked 接口。注意,在前一个示例的 before advice 中,服务 bean 可以直接用作 UsageTracked 接口的实现。要程序化访问
bean,可以编写以下代码:
5.5.5. 方面实例模型
The only supported instantiation model for schema-defined aspects is the singleton model. Other instantiation models may
be supported in future releases.
仅支持用于模式定义方面的单例模型实例化。其他实例化模型可能在未来的版本中得到支持。
The concept of “advisors” comes from the AOP support defined in Spring and does not have a direct equivalent in AspectJ.
An advisor is like a small self-contained aspect that has a single piece of advice.
“顾问”这一概念源自 Spring 中定义的 AOP 支持,在 AspectJ 中没有直接的对应物。顾问就像一个包含单一建议的小型自包含方面。
The advice itself is represented by a bean and must implement one of the advice interfaces described
in Advice Types in Spring. Advisors can take advantage of AspectJ pointcut expressions.
该建议本身由一个豆子表示,必须实现 Spring 中“建议类型”中描述的其中一个建议接口。建议者可以利用 AspectJ 切入点表达式。
Spring supports the advisor concept with the <aop:advisor> element. You most commonly see it used in conjunction with
transactional advice, which also has its own namespace support in Spring. The following example shows an advisor:
Spring 支持使用 <aop:advisor> 元素来支持顾问概念。您最常见的是它与事务性建议一起使用,Spring 也为其提供了自己的命名空间支持。以下示例展示了顾问:
As well as the pointcut-ref attribute used in the preceding example, you can also use the pointcut attribute to
define a pointcut expression inline.
除了前面示例中使用的 pointcut-ref 属性外,您还可以使用 pointcut 属性来定义内联的点切表达式。
To define the precedence of an advisor so that the advice can participate in ordering, use the order attribute to
define the Ordered value of the advisor.
为了定义顾问的优先级,以便建议可以参与排序,请使用 order 属性来定义顾问的 Ordered 值。
5.5.7. 一个 AOP 架构示例
This section shows how the concurrent locking failure retry example from An AOP Example looks
when rewritten with the schema support.
本节展示了当使用模式支持重写 An AOP Example 中的并发锁定失败重试示例时的样子。
The execution of business services can sometimes fail due to concurrency issues (for example, a deadlock loser). If the
operation is retried, it is likely to succeed on the next try.
企业服务的执行有时会因并发问题(例如,死锁失败者)而失败。如果重试操作,下一次尝试很可能成功。
For business services where it is appropriate to retry in such conditions (idempotent operations that do not need to go
back to the user for conflict resolution), we want to transparently retry the operation to avoid the client seeing a
PessimisticLockingFailureException. This is a requirement that clearly cuts across multiple services in the service
layer and, hence, is ideal for implementing through an aspect.
对于在适当条件下可以重试的业务服务(不需要回退给用户进行冲突解决的幂等操作),我们希望透明地重试操作,以避免客户端看到
PessimisticLockingFailureException 。这是一个明显跨越多个服务层的需要,因此通过方面实现是理想的。
Because we want to retry the operation, we need to use around advice so that we can call proceed multiple times. The
following listing shows the basic aspect implementation (which is a regular Java class that uses the schema support):
因为我们要重试操作,所以需要使用环绕通知,这样我们就可以多次调用 proceed 。以下列表展示了基本方面实现的示例(这是一个使用模式支持的常规
Java 类):
Note that the aspect implements the Ordered interface so that we can set the precedence of the aspect higher than the
transaction advice (we want a fresh transaction each time we retry). The maxRetries and order properties are both
configured by Spring. The main action happens in the doConcurrentOperation around advice method. We try to proceed. If
we fail with a PessimisticLockingFailureException, we try again, unless we have exhausted all of our retry attempts.
请注意,该方面实现了 Ordered 接口,以便我们可以将方面的优先级设置高于事务建议(我们希望在每次重试时都有一个新的事务)。
maxRetries 和 order 属性都由 Spring 配置。主要动作发生在 doConcurrentOperation 环绕建议方法中。我们尝试进行操作。如果我们失败于
PessimisticLockingFailureException ,我们会再次尝试,除非我们已经用尽所有重试尝试。
This class is identical to the one used in the @AspectJ example, but with the annotations removed.
这个类与@AspectJ 示例中使用的类相同,但去除了注解。
The corresponding Spring configuration is as follows:
相应的 Spring 配置如下:
Notice that, for the time being, we assume that all business services are idempotent. If this is not the case, we can
refine the aspect so that it retries only genuinely idempotent operations, by introducing an Idempotent annotation and
using the annotation to annotate the implementation of service operations, as the following example shows:
请注意,目前我们假设所有业务服务都是幂等的。如果不是这种情况,我们可以通过引入 Idempotent
注解,并使用该注解来注解服务操作的实现,从而仅重试真正幂等的操作,如下例所示:
The change to the aspect to retry only idempotent operations involves refining the pointcut expression so that only
@Idempotent operations match, as follows:
将重试仅幂等操作的角度的改变涉及细化切入点表达式,以便只有 @Idempotent 操作匹配,如下所示:
5.6. 选择使用哪种 AOP 声明风格
Once you have decided that an aspect is the best approach for implementing a given requirement, how do you decide
between using Spring AOP or AspectJ and between the Aspect language (code) style, the @AspectJ annotation style, or the
Spring XML style?
一旦你决定某个方面是实现特定需求的最佳方法,你如何决定使用 Spring AOP 还是 AspectJ,以及选择 Aspect 语言(代码)风格、@AspectJ
注解风格或 Spring XML 风格?
These decisions are influenced by a number of factors including application requirements, development tools, and team
familiarity with AOP.
这些决策受多种因素影响,包括应用需求、开发工具以及团队对面向方面编程(AOP)的熟悉程度。
5.6.1. Spring AOP 还是 Full AspectJ?
Use the simplest thing that can work. Spring AOP is simpler than using full AspectJ, as there is no requirement to
introduce the AspectJ compiler / weaver into your development and build processes.
使用最简单可行的方法。Spring AOP 比使用完整的 AspectJ 简单,因为没有必要将 AspectJ
编译器/织入器引入到您的开发和构建过程中。
If you only need to advise the execution of operations on Spring beans, Spring AOP is the right choice. If you need to
advise objects not managed by the Spring container (such as domain objects, typically), you need to use AspectJ.
如果您只需要建议对 Spring beans 的操作执行,Spring AOP 是正确的选择。如果您需要建议由 Spring 容器未管理的对象(如域对象,通常是),则需要使用
AspectJ。
You also need to use AspectJ if you wish to advise join points other than simple method executions (for example, field
get or set join points and so on).
您还需要使用 AspectJ 来建议除简单方法执行之外的其他连接点(例如,字段获取或设置连接点等)。
When you use AspectJ, you have the choice of the AspectJ language syntax (also known as the “code style”) or the
@AspectJ annotation style. Clearly, if you do not use Java 5+, the choice has been made for you: Use the code style.
当您使用 AspectJ 时,您可以选择 AspectJ 语言语法(也称为“代码风格”)或@AspectJ 注解风格。显然,如果您不使用 Java
5+,选择已经为您做出:使用代码风格。
If aspects play a large role in your design, and you are able to use
the AspectJ Development Tools (AJDT) plugin for Eclipse, the AspectJ language syntax is
the preferred option. It is cleaner and simpler because the language was purposefully designed for writing aspects.
如果某些方面在设计中的作用很大,并且您能够使用 Eclipse 的 AspectJ 开发工具(AJDT)插件,那么 AspectJ
语言语法是首选选项。因为它更简洁、更简单,因为这种语言是专门为编写方面而设计的。
If you do not use Eclipse or have only a few aspects that do not play a major role in your application, you may want to
consider using the @AspectJ style, sticking with regular Java compilation in your IDE, and adding an aspect weaving
phase to your build script.
如果您不使用 Eclipse 或者只有少数几个在您的应用程序中不起主要作用的部分,您可能想考虑使用@AspectJ 风格,坚持在您的 IDE
中使用常规 Java 编译,并在构建脚本中添加一个方面织入阶段。
5.6.2. @AspectJ 或 XML 用于 Spring AOP?
If you have chosen to use Spring AOP, you have a choice of @AspectJ or XML style. There are various tradeoffs to
consider.
如果您选择了使用 Spring AOP,您可以选择@AspectJ 或 XML 风格。需要考虑各种权衡。
The XML style may be most familiar to existing Spring users, and it is backed by genuine POJOs.
XML 风格可能对现有的 Spring 用户最为熟悉,并且它由真正的 POJOs 支持。
When using AOP as a tool to configure enterprise services, XML can be a good choice (a good test is whether you consider
the pointcut expression to be a part of your configuration that you might want to change independently).
当使用 AOP 作为配置企业服务的工具时,XML
可以是一个不错的选择(一个很好的测试是,你是否认为切入点表达式是你可能希望独立更改的配置的一部分)。
With the XML style, it is arguably clearer from your configuration which aspects are present in the system.
使用 XML 样式,从您的配置中可以明显看出系统中存在哪些方面。
The XML style has two disadvantages. First, it does not fully encapsulate the implementation of the requirement it
addresses in a single place.
XML 风格有两个缺点。首先,它没有在单个位置完全封装它所处理的要求的实现。
The DRY principle says that there should be a single, unambiguous, authoritative representation of any piece of
knowledge within a system.
DRY 原则表示,在系统中任何知识片段都应该有一个单一、明确、权威的表示。
When using the XML style, the knowledge of how a requirement is implemented is split across the declaration of the
backing bean class and the XML in the configuration file. When you use the @AspectJ style, this information is
encapsulated in a single module: the aspect.
当使用 XML 样式时,对需求实现方式的知识分布在后端 Bean 类的声明和配置文件中的 XML 之间。当使用@AspectJ
样式时,这些信息被封装在单个模块中:方面。
Secondly, the XML style is slightly more limited in what it can express than the @AspectJ style: Only the “singleton”
aspect instantiation model is supported, and it is not possible to combine named pointcuts declared in XML.
其次,XML 风格在表达上略逊于@AspectJ 风格:仅支持“singleton”切面实例化模型,且无法将 XML 中声明的命名切入点组合。
For example, in the @AspectJ style you can write something like the following:
例如,在@AspectJ 风格中,您可以编写如下内容:
In the XML style you can declare the first two pointcuts:
在 XML 风格中,您可以声明前两个切入点:
The downside of the XML approach is that you cannot define the accountPropertyAccess pointcut by combining these
definitions.
XML 方法的缺点是您不能通过组合这些定义来定义 accountPropertyAccess 切入点。
The @AspectJ style supports additional instantiation models and richer pointcut composition. It has the advantage of
keeping the aspect as a modular unit. It also has the advantage that the @AspectJ aspects can be understood (and thus
consumed) both by Spring AOP and by AspectJ.
@AspectJ 风格支持额外的实例化模型和更丰富的切入点组合。它具有将方面作为模块化单元保持的优势。此外,@AspectJ 方面既可以通过
Spring AOP 理解(因此被消费),也可以通过 AspectJ 理解。
So, if you later decide you need the capabilities of AspectJ to implement additional requirements, you can easily
migrate to a classic AspectJ setup. On balance, the Spring team prefers the @AspectJ style for custom aspects beyond
simple configuration of enterprise services.
因此,如果您以后决定需要 AspectJ 的功能来实现额外的需求,您可以轻松迁移到经典的 AspectJ 设置。总的来说,Spring
团队更倾向于使用@AspectJ 风格来处理除简单企业服务配置之外的自定义方面。
5.7. 面向类型混合
It is perfectly possible to mix @AspectJ style aspects by using the auto-proxying support, schema-defined <aop:aspect>
aspects, <aop:advisor> declared advisors, and even proxies and interceptors in other styles in the same configuration.
All of these are implemented by using the same underlying support mechanism and can co-exist without any difficulty.
完全可以通过使用自动代理支持、由模式定义的 <aop:aspect> 方面、 <aop:advisor> 声明的顾问,甚至在同一配置中使用其他风格的代理和拦截器来混合@AspectJ
风格的方面。所有这些都通过使用相同的底层支持机制实现,并且可以毫无困难地共存。
5.8. 代理机制
Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. JDK dynamic proxies
are built into the JDK, whereas CGLIB is a common open-source class definition library (repackaged into
spring-core).
Spring AOP 使用 JDK 动态代理或 CGLIB 为指定的目标对象创建代理。JDK 动态代理内置在 JDK 中,而 CGLIB 是一个常见的开源类定义库(重新打包为
spring-core )。
If the target object to be proxied implements at least one interface, a JDK dynamic proxy is used. All of the interfaces
implemented by the target type are proxied. If the target object does not implement any interfaces, a CGLIB proxy is
created.
如果目标对象要代理至少一个接口,则使用 JDK 动态代理。目标类型实现的全部接口都被代理。如果目标对象不实现任何接口,则创建一个
CGLIB 代理。
If you want to force the use of CGLIB proxying (for example, to proxy every method defined for the target object, not
only those implemented by its interfaces), you can do so. However, you should consider the following issues:
如果您想强制使用 CGLIB 代理(例如,代理目标对象定义的每个方法,而不仅仅是其实现的接口的方法),您可以这样做。但是,您应该考虑以下问题:
With CGLIB, final methods cannot be advised, as they cannot be overridden in runtime-generated subclasses.
使用 CGLIB, final 方法无法被增强,因为它们不能在运行时生成的子类中被覆盖。
As of Spring 4.0, the constructor of your proxied object is NOT called twice anymore, since the CGLIB proxy instance
is created through Objenesis.
截至 Spring 4.0,您的代理对象的构造函数不再被调用两次,因为 CGLIB 代理实例是通过 Objenesis 创建的。
Only if your JVM does not allow for constructor bypassing, you might see double invocations and corresponding debug
log entries from Spring’s AOP support.
只有当您的 JVM 不允许绕过构造函数时,您才可能会看到 Spring 的 AOP 支持中的双重调用和相应的调试日志条目。
To force the use of CGLIB proxies, set the value of the proxy-target-class attribute of the <aop:config> element to
true, as follows:
为了强制使用 CGLIB 代理,将 <aop:config> 元素的 proxy-target-class 属性的值设置为 true,如下所示:
To force CGLIB proxying when you use the @AspectJ auto-proxy support, set the proxy-target-class attribute of the
<aop:aspectj-autoproxy> element to true, as follows:
要强制使用 CGLIB 代理时启用@AspectJ 自动代理支持,请将 <aop:aspectj-autoproxy> 元素的 proxy-target-class 属性设置为
true ,如下所示:
Multiple <aop:config/> sections are collapsed into a single unified auto-proxy creator at runtime, which applies the
strongest proxy settings that any of the <aop:config/> sections (typically from different XML bean definition files)
specified. This also applies to the <tx:annotation-driven/> and <aop:aspectj-autoproxy/> elements.
多个 <aop:config/> 部分在运行时合并为一个统一的自动代理创建器,该创建器应用了任何 <aop:config/> 部分(通常来自不同的
XML bean 定义文件)指定的最强代理设置。这也适用于 <tx:annotation-driven/> 和 <aop:aspectj-autoproxy/> 元素。
To be clear, using proxy-target-class="true" on <tx:annotation-driven/>, <aop:aspectj-autoproxy/>, or
<aop:config/> elements forces the use of CGLIB proxies for all three of them.
为了明确,在 <tx:annotation-driven/> 、 <aop:aspectj-autoproxy/> 或 <aop:config/> 元素上使用
proxy-target-class="true" 强制这三个元素都使用 CGLIB 代理。
5.8.1. 理解 AOP 代理
Spring AOP is proxy-based. It is vitally important that you grasp the semantics of what that last statement actually
means before you write your own aspects or use any of the Spring AOP-based aspects supplied with the Spring Framework.
Spring AOP 是基于代理的。在编写自己的方面或使用 Spring 框架提供的任何基于 Spring AOP 的方面之前,理解最后那个陈述的实际语义至关重要。
Consider first the scenario where you have a plain-vanilla, un-proxied, nothing-special-about-it, straight object
reference, as the following code snippet shows:
首先考虑这样一个场景:你有一个普通的、未经代理的、没有任何特殊之处的、直接的对象引用,如下代码片段所示:
If you invoke a method on an object reference, the method is invoked directly on that object reference, as the following
image and listing show:
如果您在对象引用上调用一个方法,该方法将直接在对象引用上调用,如下图和列表所示:

Things change slightly when the reference that client code has is a proxy. Consider the following diagram and code
snippet:
当客户端代码引用的是代理时,情况会有所不同。考虑以下图示和代码片段:

The key thing to understand here is that the client code inside the main(..) method of the Main class has a
reference to the proxy. This means that method calls on that object reference are calls on the proxy. As a result, the
proxy can delegate to all of the interceptors (advice) that are relevant to that particular method call.
这里的关键是要理解, Main 类的 main(..)
方法内的客户端代码有一个对代理的引用。这意味着对该对象引用的方法调用是对代理的调用。因此,代理可以将与该特定方法调用相关的所有拦截器(建议)委托给它们。
However, once the call has finally reached the target object (the SimplePojo reference in this case), any method calls
that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference,
and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice
associated with a method invocation getting a chance to run.
然而,一旦调用最终到达目标对象(在这种情况下为 SimplePojo 引用),它可能对其自身进行的任何方法调用,例如 this.bar() 或
this.foo() ,都将针对 this 引用而不是代理来调用。这具有重要的意义。这意味着自我调用不会导致与方法调用相关联的建议有机会运行。
Okay, so what is to be done about this? The best approach (the term "best" is used loosely here) is to refactor your
code such that the self-invocation does not happen. This does entail some work on your part, but it is the best,
least-invasive approach.
好的,那么我们应该如何解决这个问题呢?最佳的方法(在这里“最佳”一词的使用是宽松的)是将你的代码重构,使得自我调用不会发生。这确实需要你做些工作,但这是最佳、最不侵入性的方法。
The next approach is absolutely horrendous, and we hesitate to point it out, precisely because it is so horrendous. You
can (painful as it is to us) totally tie the logic within your class to Spring AOP, as the following example shows:
下一种方法绝对糟糕,我们犹豫不决是否指出它,正是因为它太糟糕了。你可以(尽管对我们来说很痛苦)完全将你类中的逻辑与 Spring AOP
绑定,如下例所示:
This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in
an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being
created, as the following example shows:
这完全将您的代码耦合到 Spring AOP 中,并且使类本身意识到它正在 AOP 上下文中使用,这与 AOP
的原则相悖。此外,在创建代理时还需要进行一些额外的配置,以下示例显示:
Finally, it must be noted that AspectJ does not have this self-invocation issue because it is not a proxy-based AOP
framework.
最后,必须指出,AspectJ 没有这个问题,因为它不是一个基于代理的 AOP 框架。
5.9. 使用程序创建@AspectJ 代理
In addition to declaring aspects in your configuration by using either <aop:config> or <aop:aspectj-autoproxy>, it
is also possible to programmatically create proxies that advise target objects. For the full details of Spring’s AOP
API, see the next chapter. Here, we want to focus on the ability to automatically create proxies by using
@AspectJ aspects.
除了使用 <aop:config> 或 <aop:aspectj-autoproxy> 在配置中声明方面之外,还可以通过编程创建代理来建议目标对象。有关
Spring AOP API 的详细信息,请参阅下一章。在这里,我们想关注使用@AspectJ 方面自动创建代理的能力。
You can use the org.springframework.aop.aspectj.annotation.AspectJProxyFactory class to create a proxy for a target
object that is advised by one or more @AspectJ aspects. The basic usage for this class is very simple, as the following
example shows:
您可以使用 org.springframework.aop.aspectj.annotation.AspectJProxyFactory 类为受一个或多个@AspectJ
切面建议的目标对象创建一个代理。此类的基本用法非常简单,如下例所示:
See
the javadoc
for more information.
查看 javadoc 获取更多信息。
5.10. 在 Spring 应用程序中使用 AspectJ
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 AOP。在本节中,我们将探讨在需求超出 Spring AOP 本身提供的功能时,如何使用 AspectJ
编译器或织入器来代替或补充 Spring AOP。
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.
Spring 附带一个小型的 AspectJ 方面库,该库作为 spring-aspects.jar 独立提供在你的发行版中。您需要将其添加到类路径中才能使用其中的方面。使用
AspectJ 在 Spring 和其他 Spring 方面中讨论如何使用 Spring 依赖注入领域对象的内容以及如何使用该库。通过 Spring IoC 配置
AspectJ 方面讨论了如何使用 AspectJ 编译器编织的 AspectJ 方面进行依赖注入。最后,Spring 框架中的 AspectJ 负载时编织介绍了为使用
AspectJ 的 Spring 应用程序进行负载时编织的介绍。
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.
Spring 容器实例化和配置在应用程序上下文中定义的 bean。也可以要求 bean 工厂根据包含要应用配置的 bean 定义名称配置一个预存在的对象。
spring-aspects.jar 包含一个利用此功能的注解驱动方面,允许注入任何对象。这种支持旨在用于任何容器控制之外的创建的对象。
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.
域对象通常属于这一类别,因为它们通常通过 new 运算符或 ORM 工具在数据库查询的结果下程序化创建。
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:
@Configurable 注解标记一个类为 Spring 驱动的配置的候选者。在最简单的情况下,你可以将其纯粹用作标记注解,如下例所示:
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.myapp.domain.Account). Since the default name for a bean 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:
当以此方式用作标记接口时,Spring 通过使用与完全限定类型名称相同的 bean 定义(通常是原型作用域)来配置注解类型(在本例中为
Account )的新实例。由于 bean 的默认名称是其类型的完全限定名称,因此,如以下示例所示,省略 id 属性是声明原型定义的一种便捷方式:
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:
如果您想明确指定要使用的原型 bean 定义的名称,您可以直接在注解中这样做,如下例所示:
Spring now looks for a bean definition named account and uses that as the definition to configure new Account
instances.
Spring 现在寻找名为 account 的 bean 定义,并使用该定义来配置新的 Account 实例。
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).
您还可以使用自动装配来避免完全指定专用的 bean 定义。要使 Spring 应用自动装配,请使用 @Configurable 注解的 autowire
属性。您可以通过类型或名称分别指定 @Configurable(autowire=Autowire.BY_TYPE) 或
@Configurable(autowire=Autowire.BY_NAME) 进行自动装配。作为替代,最好通过 @Autowired 或 @Inject 在字段或方法级别为您的
@Configurable bean 指定显式的、基于注解的依赖注入(有关详细信息,请参阅基于注解的容器配置)。
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.
最后,您可以通过使用 dependencyCheck 属性(例如, @Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)
)来为新创建和配置的对象中的对象引用启用 Spring 依赖性检查。如果此属性设置为 true ,Spring 将在配置后验证所有属性(非基本类型或集合)都已设置。
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()).
请注意,仅使用注解本身没有任何作用。是 spring-aspects.jar 中的 AnnotationBeanConfigurerAspect
对注解的存在进行操作。本质上,这个方面表示,“在返回使用 @Configurable 注解的类型的新对象的初始化之后,使用 Spring
根据注解的属性配置新创建的对象”。在这种情况下,“初始化”指的是新实例化的对象(例如,使用 new 操作符实例化的对象)以及正在反序列化的
Serializable 对象(例如,通过 readResolve())。
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 @Configurable declaration, as follows:
如果您希望在构造函数体运行之前注入依赖项,并且使其在构造函数体中可用,您需要在 @Configurable 声明中定义此操作,如下所示:
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 编程指南》的附录中找到有关各种切入点类型语言语义的更多信息。
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). If you use Java-based configuration, you can add
@EnableSpringConfigured to any @Configuration class, as follows:
为此,必须使用 AspectJ 织入器将注解类型编织。您可以使用构建时的 Ant 或 Maven 任务来完成此操作(例如,请参阅 AspectJ
开发环境指南)或加载时编织(请参阅 Spring 框架中的 AspectJ 加载时编织)。 AnnotationBeanConfigurerAspect 本身需要由 Spring
配置(以便获取用于配置新对象的 bean 工厂的引用)。如果您使用基于 Java 的配置,可以将 @EnableSpringConfigured 添加到任何
@Configuration 类中,如下所示:
If you prefer XML based configuration, the Spring context namespace defines a convenient
context:spring-configured element, which you can use as follows:
如果您更喜欢基于 XML 的配置,Spring context 命名空间定义了一个方便的 context:spring-configured 元素,您可以使用如下方式:
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.
实例化在方面配置之前创建的 @Configurable 对象会导致向调试日志发出消息,并且不会对对象进行配置。一个例子可能是 Spring
配置中的一个 bean,当它被 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:
在这种情况下,您可以使用 depends-on bean 属性手动指定该 bean 依赖于配置方面。以下示例展示了如何使用 depends-on 属性:
Do not activate @Configurable processing through the bean configurer aspect unless you really mean to rely on its
semantics at runtime. In particular, make sure that you do not use @Configurable on bean classes that are registered
as regular Spring beans with the container. Doing so results in double initialization, once through the container and
once through the aspect.
不要在通过 Bean 配置器切面激活 @Configurable 处理,除非你真的打算在运行时依赖其语义。特别是,确保不要在注册为常规 Spring
容器的 Bean 类上使用 @Configurable 。这样做会导致双重初始化,一次通过容器,一次通过切面。
@Configurable Objects 单元测试@Configurable 对象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.
支持 @Configurable 的目标之一是能够在不涉及硬编码查找困难的情况下,独立对领域对象进行单元测试。如果 @Configurable
类型尚未由 AspectJ 织入,则注解在单元测试期间没有影响。您可以在被测试的对象中设置模拟或存根属性引用,并按正常流程进行。如果
@Configurable 类型已被 AspectJ 织入,您仍然可以在容器外正常进行单元测试,但每次构建 @Configurable 对象时都会看到一个警告消息,表明它尚未由
Spring 配置。
与多个应用程序上下文协同工作
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.
用于实现 @Configurable 支持的 AnnotationBeanConfigurerAspect 是一个 AspectJ 单例方面。单例方面的作用域与 static
成员的作用域相同:每个定义类型的类加载器有一个方面实例。这意味着,如果您在同一个类加载器层次结构中定义多个应用程序上下文,您需要考虑在哪里定义
@EnableSpringConfigured bean 以及在哪里放置 spring-aspects.jar 在类路径上。
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).
考虑一个典型的 Spring Web 应用程序配置,它有一个共享的父应用程序上下文,该上下文定义了常见的业务服务、支持这些服务所需的一切,以及每个
servlet(包含特定于该 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.
所有这些上下文都存在于相同的类加载器层次结构中,因此 AnnotationBeanConfigurerAspect
只能持有其中一个的引用。在这种情况下,我们建议在共享(父)应用程序上下文中定义 @EnableSpringConfigured
bean。这定义了您可能希望注入到域对象中的服务。
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).
一个后果是,您无法通过使用 @Configurable 机制(您可能根本不想这么做)来配置对在子(servlet 特定)上下文中定义的 bean 的引用的域对象。
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).
当在同一容器中部署多个 Web 应用时,确保每个 Web 应用通过自己的类加载器加载 spring-aspects.jar 中的类型(例如,通过将
spring-aspects.jar 放置在 WEB-INF/lib 中)。如果仅将 spring-aspects.jar 添加到容器级别的类路径中(因此由共享的父类加载器加载),所有
Web 应用将共享相同的方面实例(这可能不是你想要的)。
5.10.2. AspectJ 的其他 Spring 方面
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.
除了 @Configurable 方面外, spring-aspects.jar 包含一个 AspectJ 方面,您可以使用它来驱动对带有 @Transactional
注解的类型和方法进行 Spring 的事务管理。这主要用于希望在使用 Spring 框架的事务支持之外的用户。
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 注解的方面是 AnnotationTransactionAspect 。当您使用此方面时,必须注解实现类(或该类中的方法或两者),而不是类实现的接口(如果有)。AspectJ
遵循 Java 的规则,即接口上的注解不会继承。
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.
类中方法上的 @Transactional 注解会覆盖类注解(如果存在)给出的默认事务语义。任何可见性的方法都可以注解,包括私有方法。
Annotating non-public methods directly is the only way to get transaction demarcation for the execution of such
methods.
直接注释非公共方法是实现此类方法执行事务划分的唯一途径。
Since Spring Framework 4.2, spring-aspects provides a similar aspect that offers the exact same features for the
standard javax.transaction.Transactional annotation. Check JtaAnnotationTransactionAspect for more details.
自 Spring Framework 4.2 以来, spring-aspects 提供了一种类似的功能,为标准 javax.transaction.Transactional
注解提供了完全相同的特性。查看 JtaAnnotationTransactionAspect 获取更多详细信息。
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:
面向 AspectJ 程序员,希望使用 Spring 配置和事务管理支持但不希望(或不能)使用注解的, spring-aspects.jar 也包含 abstract
方面,您可以扩展以提供自己的切入点定义。有关 AbstractBeanConfigurerAspect 和 AbstractTransactionAspect
方面的更多信息,请参阅源代码。例如,以下摘录显示了如何编写一个方面,通过使用匹配完全限定类名的原型 bean 定义来配置领域模型中定义的所有对象实例:
5.10.3. 使用 Spring IoC 配置 AspectJ 方面
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.
当您在 Spring 应用程序中使用 AspectJ 方面时,自然既想又期望能够用 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.
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
运行时本身负责方面创建,通过 Spring 配置 AspectJ 创建的方面的方法取决于方面使用的 AspectJ 实例化模型(per-xxx子句)。
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:
大多数 AspectJ 方面是单例方面。这些方面的配置很简单。您可以通过正常引用方面类型并包含 factory-method="aspectOf" bean
属性来创建一个 bean 定义。这确保了 Spring 通过向 AspectJ 请求而不是自己尝试创建实例来获取方面实例。以下示例显示了如何使用
factory-method="aspectOf" 属性:
1
Note the factory-method="aspectOf" attribute 注意 factory-method="aspectOf" 属性
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.
非单例方面更难配置。然而,通过创建原型 bean 定义并使用 spring-aspects.jar 提供的 @Configurable 支持,一旦 AspectJ
运行时创建了方面实例,就可以配置方面实例。
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.
如果您有一些想要与 AspectJ 编织的@AspectJ 方面(例如,使用加载时编织用于领域模型类型)以及您想要与 Spring AOP
一起使用的其他@AspectJ 方面,并且这些方面都在 Spring 中配置,您需要告诉 Spring AOP 的@AspectJ 自动代理支持应使用配置中定义的@AspectJ
方面的哪个确切子集进行自动代理。
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/> 声明中使用一个或多个 <include/> 元素来实现这一点。每个 <include/>
元素指定一个名称模式,并且只有名称至少匹配其中一个模式的 bean 被用于 Spring AOP 自动代理配置。以下示例展示了如何使用
<include/> 元素:
Do not be misled by the name of the <aop:aspectj-autoproxy/> element. Using it results in the creation of Spring AOP
proxies. The @AspectJ style of aspect declaration is being used here, but the AspectJ runtime is not involved.
不要被 <aop:aspectj-autoproxy/> 元素的名称所误导。使用它会导致创建 Spring AOP 代理。这里使用的是@AspectJ 风格的切面声明,但并未涉及
AspectJ 运行时。
5.10.4. 使用 AspectJ 在 Spring 框架中进行加载时织入
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.
加载时织入(LTW)是指将 AspectJ 方面织入应用程序的类文件的过程,这些类文件正在被加载到 Java 虚拟机(JVM)中。本节的重点是配置和使用
Spring 框架中的 LTW。
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.
本节不是 LTW 的一般性介绍。有关 LTW 的具体细节以及仅使用 AspectJ(完全不涉及 Spring)配置 LTW 的详细信息,请参阅 AspectJ
开发环境指南中的 LTW 部分。
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.
Spring 框架为 AspectJ LTW 带来的价值在于能够实现对织入过程的更细粒度控制。"纯"AspectJ LTW 是通过使用 Java(5+)代理实现的,通过在启动
JVM 时指定 VM 参数来开启。
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).
因此,这是一个 JVM 级别的设置,在某些情况下可能没问题,但通常有点过于粗糙。Spring 启用的 LTW 允许您根据每个 ClassLoader
来开启 LTW,这更加细致,在“单 JVM 多应用”的环境中(如在典型的应用服务器环境中)可能更有意义。
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.
此外,在特定环境中,这种支持使得在不修改应用服务器启动脚本的情况下实现加载时织入成为可能,该启动脚本通常用于添加
-javaagent:path/to/aspectjweaver.jar 或(如我们在本节后面所述) -javaagent:path/to/spring-instrument.jar
。开发者配置应用程序上下文以启用加载时织入,而不是依赖通常负责部署配置(如启动脚本)的管理员。
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.
现在销售演讲已经结束,让我们首先快速浏览一下使用 Spring 的 AspectJ LTW 的示例,然后详细介绍示例中引入的元素。要查看完整示例,请参阅
Petclinic 示例应用程序。
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.
我们可以在之后立即应用一个更细粒度的分析工具到该特定区域。
The example presented here uses XML configuration. You can also configure and use @AspectJ
with Java configuration. Specifically, you can use the @EnableLoadTimeWeaving annotation as an
alternative to <context:load-time-weaver/> (see below for details).
此处展示的示例使用 XML 配置。您还可以使用 Java 配置配置和使用@AspectJ。具体来说,您可以使用 @EnableLoadTimeWeaving 注解作为
<context:load-time-weaver/> 的替代(详情见下文)。
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:
以下示例展示了配置文件方面,并不花哨。它是一个基于时间的配置文件分析器,使用@AspectJ 风格的方面声明:
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:
我们还需要创建一个 META-INF/aop.xml 文件,以通知 AspectJ 织入器我们想要将我们的 ProfilingAspect 织入到我们的类中。这个文件约定,即在
Java 类路径上存在一个名为 META-INF/aop.xml 的文件(或文件),是标准的 AspectJ。以下示例显示了 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:
现在我们可以继续配置 Spring 特有的部分。我们需要配置一个 LoadTimeWeaver (稍后解释)。这个类加载器编织器是负责将一个或多个
META-INF/aop.xml 文件中的方面配置编织到您的应用程序中的类中的基本组件。好处是它不需要很多配置(有一些更多选项您可以指定,但这些将在稍后详细说明),如下面的示例所示:
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:
现在所有必需的工件(方面、 META-INF/aop.xml 文件和 Spring 配置)都已就绪,我们可以创建以下具有 main(..) 方法的驱动类来演示
LTW 的实际操作:
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:
我们还有最后一件事要做。本节介绍中确实提到,可以使用 Spring 在单个 ClassLoader 的基础上选择性开启
LTW,这是正确的。然而,在这个例子中,我们使用一个 Java 代理(由 Spring 提供)来开启 LTW。我们使用以下命令来运行之前显示的
Main 类:
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
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.
-javaagent 是一个用于指定和启用代理对在 JVM 上运行的程序进行度量的标志。Spring 框架附带了一个这样的代理,即
InstrumentationSavingAgent ,它包含在作为先前示例中 -javaagent 参数值的 spring-instrument.jar 中。
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:
程序执行 Main 的输出看起来类似于以下示例。(我在 calculateEntitlement() 实现中引入了 Thread.sleep(..)
语句,以便分析器实际上能够捕捉到除了 0 毫秒之外的内容( 01234 毫秒不是由 AOP 引入的额外开销)。以下列表显示了运行我们的分析器时得到的输出:
Calculating entitlement
StopWatch 'ProfilingAspect': running time (millis) = 1234
ms % Task name
01234 100% calculateEntitlement
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:
由于这个 LTW 是通过使用完整的 AspectJ 实现的,我们不仅限于建议 Spring beans。以下对 Main 程序的轻微修改可以得到相同的结果:
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 容器,然后在 Spring 的上下文之外创建一个新实例的 StubEntitlementCalculationService
。性能分析建议仍然被编织进去。
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.
诚然,这个例子很简单。然而,Spring 中 LTW 支持的原理在早期示例中已经全部介绍,本节剩余部分将详细解释每个配置和用法背后的“为什么”。
The ProfilingAspect used in this example may be basic, but it is quite useful. It is a nice example of a
development-time aspect that developers can use during development and then easily exclude from builds of the
application being deployed into UAT or production.
该示例中使用的 ProfilingAspect 可能很简单,但非常实用。这是一个很好的开发时特性的例子,开发人员可以在开发期间使用,然后轻松地从部署到
UAT 或生产环境的应用程序构建中排除。
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.
LTW 中使用的方面必须是 AspectJ 方面。您可以使用 AspectJ 语言本身编写它们,或者以@AspectJ 风格编写您的方面。然后,您的方面既是有效的
AspectJ 方面,也是 Spring AOP 方面。
Furthermore, the compiled aspect classes need to be available on the classpath.
此外,编译后的方面类需要在类路径上可用。
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).
AspectJ LTW 基础设施通过使用一个或多个位于 Java 类路径上的 META-INF/aop.xml 文件进行配置(直接或更常见地,在 jar 文件中)。
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.
该文件的结构和内容在 AspectJ 参考文档的 LTW 部分有详细说明。由于 aop.xml 文件是 100%的 AspectJ,我们在此处不再进一步描述。
必需的库(JARS)
At minimum, you need the following libraries to use the Spring Framework’s support for AspectJ LTW:
至少需要以下库来使用 Spring 框架对 AspectJ LTW 的支持:
spring-aop.jar
aspectjweaver.jar
If you use the Spring-provided agent to enable instrumentation, you also need:
如果您使用 Spring 提供的代理来启用仪器化,您还需要:
spring-instrument.jarThe 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.
Spring 的 LTW 支持的关键组件是 LoadTimeWeaver 接口(在 org.springframework.instrument.classloading 包中),以及随
Spring 发行版一起提供的众多其实例。一个 LoadTimeWeaver 负责在运行时向 ClassLoader 添加一个或多个
java.lang.instrument.ClassFileTransformers ,这为各种有趣的应用打开了大门,其中之一恰好是方面的 LTW。
If you are unfamiliar with the idea of runtime class file transformation, see the javadoc API documentation for the
java.lang.instrument package before continuing. While that documentation is not comprehensive, at least you can see
the key interfaces and classes (for reference as you read through this section).
如果您不熟悉运行时类文件转换的概念,请在继续之前查看 java.lang.instrument 包的 javadoc API
文档。虽然该文档并不全面,但至少您可以查看关键接口和类(作为您阅读本节时的参考)。
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.)
配置特定 ApplicationContext 的 LoadTimeWeaver 可以简单到只需添加一行。(请注意,你几乎肯定需要使用 ApplicationContext
作为你的 Spring 容器——通常,一个 BeanFactory 是不够的,因为 LTW 支持使用 BeanFactoryPostProcessors 。)
To enable the Spring Framework’s LTW support, you need to configure a LoadTimeWeaver, which typically is done by using
the @EnableLoadTimeWeaving annotation, as follows:
要启用 Spring 框架的 LTW 支持,您需要配置一个 LoadTimeWeaver ,这通常是通过使用 @EnableLoadTimeWeaving 注解来完成的,如下所示:
Alternatively, if you prefer XML-based configuration, use the <context:load-time-weaver/> element. Note that the
element is defined in the context namespace. The following example shows how to use <context:load-time-weaver/>:
或者,如果您更喜欢基于 XML 的配置,请使用 <context:load-time-weaver/> 元素。请注意,该元素定义在 context
命名空间中。以下示例显示了如何使用 <context:load-time-weaver/> :
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:
前一个配置自动为您定义和注册了多个 LTW 特定基础设施 bean,例如 LoadTimeWeaver 和 AspectJWeavingEnabler 。默认的
LoadTimeWeaver 是 DefaultContextLoadTimeWeaver 类,它尝试装饰自动检测到的 LoadTimeWeaver 。自动检测到的
LoadTimeWeaver 的确切类型取决于您的运行时环境。以下表格总结了各种 LoadTimeWeaver 实现:
Table 13. DefaultContextLoadTimeWeaver LoadTimeWeavers
表 13. 默认 ContextLoadTimeWeaver LoadTimeWeavers
Runtime Environment 运行环境
LoadTimeWeaver implementationLoadTimeWeaver 实现
Running in Apache Tomcat运行在 Apache Tomcat 上
TomcatLoadTimeWeaver
Running in GlassFish (limited to EAR deployments)
运行在 GlassFish(限于 EAR 部署)
GlassFishLoadTimeWeaver
Running in Red Hat’s JBoss AS or WildFly
运行在 Red Hat 的 JBoss AS 或 WildFly 上
JBossLoadTimeWeaver
Running in IBM’s WebSphere
运行在 IBM 的 WebSphere
WebSphereLoadTimeWeaver
Running in Oracle’s WebLogic
运行在 Oracle 的 WebLogic
WebLogicLoadTimeWeaver
JVM started with Spring InstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar)
JVM 从 Spring InstrumentationSavingAgent ( java -javaagent:path/to/spring-instrument.jar )开始
InstrumentationLoadTimeWeaver
Fallback, expecting the underlying ClassLoader to follow common conventions (namely addTransformer and optionally a
getThrowawayClassLoader method)
回退,期望底层 ClassLoader 遵循常见约定(即 addTransformer 和可选的 getThrowawayClassLoader 方法)
ReflectiveLoadTimeWeaver
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.
请注意,该表仅列出在使用 DefaultContextLoadTimeWeaver 时自动检测到的 LoadTimeWeavers 。您可以指定要使用的确切
LoadTimeWeaver 实现。
To specify a specific LoadTimeWeaver with Java configuration, implement the LoadTimeWeavingConfigurer interface and
override the getLoadTimeWeaver() method. The following example specifies a ReflectiveLoadTimeWeaver:
要指定具有 Java 配置的特定 LoadTimeWeaver ,实现 LoadTimeWeavingConfigurer 接口并重写 getLoadTimeWeaver()
方法。以下示例指定了 ReflectiveLoadTimeWeaver :
If you use XML-based configuration, you can specify the fully qualified classname as the value of the weaver-class
attribute on the <context:load-time-weaver/> element. Again, the following example specifies a
ReflectiveLoadTimeWeaver:
如果您使用基于 XML 的配置,您可以在 <context:load-time-weaver/> 元素的 weaver-class 属性中指定完全限定的类名。再次强调,以下示例指定了一个
ReflectiveLoadTimeWeaver :
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.
配置中定义和注册的 LoadTimeWeaver 可以通过使用众所周知的名称 loadTimeWeaver 从 Spring 容器中检索。请记住,
LoadTimeWeaver 仅作为 Spring 的 LTW 基础设施添加一个或多个 ClassFileTransformers 的机制存在。实际执行 LTW 的
ClassFileTransformer 是来自 org.aspectj.weaver.loadtime 包的 ClassPreProcessorAgentAdapter 类。有关如何实际实施织入的详细信息,请参阅
ClassPreProcessorAgentAdapter 类的类级别 javadoc,因为这超出了本文档的范围。
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:
配置中还有一个最终属性需要讨论: aspectjWeaving 属性(或使用 XML 时的 aspectj-weaving )。此属性控制 LTW
是否启用。它接受三种可能值之一,默认值为 autodetect 如果该属性不存在。以下表格总结了三种可能的值:
Table 14. AspectJ weaving attribute values
表 14. AspectJ 织入属性值
Annotation Value 注释值
XML Value XML 值
Explanation 说明
ENABLED
on
AspectJ weaving is on, and aspects are woven at load-time as appropriate.
AspectJ 织入开启,方面在加载时按需织入。
DISABLED
off
LTW is off. No aspect is woven at load-time.
LTW 已关闭。在加载时没有方面被编织。
AUTODETECT
autodetect
If the Spring LTW infrastructure can find at least one META-INF/aop.xml file, then AspectJ weaving is on. Otherwise,
it is off. This is the default value.
如果 Spring LTW 基础设施可以找到至少一个 META-INF/aop.xml 文件,则 AspectJ 织入开启。否则,它处于关闭状态。这是默认值。
环境特定配置
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.
本节包含在使用 Spring 的 LTW 支持时,您在应用服务器和 Web 容器等环境中需要设置和配置的任何附加内容。
汤姆猫,JBoss,WebSphere,WebLogic
Tomcat, JBoss/WildFly, IBM WebSphere Application Server and Oracle WebLogic Server all 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.
Tomcat、JBoss/WildFly、IBM WebSphere Application Server 和 Oracle WebLogic Server 都提供了一般应用程序 ClassLoader
,该应用程序能够进行本地仪器化。Spring 的本地 LTW 可以利用这些 ClassLoader 实现,以提供 AspectJ
线程。您可以简单地启用如前所述的加载时线程,具体来说,您不需要修改 JVM 启动脚本以添加
-javaagent:path/to/spring-instrument.jar 。
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:
请注意,在 JBoss 上,您可能需要禁用应用服务器扫描,以防止它在应用程序实际启动之前加载类。一个快速的解决方案是在您的工件中添加一个名为
WEB-INF/jboss-scanning.xml 的文件,内容如下:
通用 Java 应用程序
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.
当在不受特定 LoadTimeWeaver 实现支持的环境中需要类插桩时,JVM 代理是一般解决方案。对于此类情况,Spring 提供了
InstrumentationLoadTimeWeaver ,它需要一个特定的 Spring(但非常通用的)JVM 代理 spring-instrument.jar ,由常见的
@EnableLoadTimeWeaving 和 <context:load-time-weaver/> 设置自动检测。
To use it, you must start the virtual machine with the Spring agent by supplying the following JVM options:
使用它,您必须通过提供以下 JVM 选项来启动带有 Spring 代理的虚拟机:
-javaagent:/path/to/spring-instrument.jar
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).
请注意,这需要修改 JVM 启动脚本,这可能会阻止您在应用服务器环境中使用它(取决于您的服务器和操作策略)。
That said, for one-app-per-JVM deployments such as standalone Spring Boot applications, you typically control the entire
JVM setup in any case.
尽管如此,对于每个 JVM 部署一个应用,例如独立的 Spring Boot 应用,你通常在任何情况下都控制整个 JVM 设置。
More information on AspectJ can be found on the AspectJ website.
更多关于 AspectJ 的信息可以在 AspectJ 网站上找到。
Eclipse AspectJ by Adrian Colyer et. al. (Addison-Wesley, 2005) provides a comprehensive introduction and reference
for the AspectJ language.
《Eclipse AspectJ》由 Adrian Colyer 等人(Addison-Wesley,2005 年)编写,为 AspectJ 语言提供了全面的介绍和参考。
AspectJ in Action, Second Edition by Ramnivas Laddad (Manning, 2009) comes highly recommended. The focus of the book
is on AspectJ, but a lot of general AOP themes are explored (in some depth).
《AspectJ 实战(第 2 版)》由 Ramnivas Laddad 著(Manning,2009 年)备受推荐。本书的重点是 AspectJ,但深入探讨了大量的通用 AOP
主题。