赞
踩
11. Aspect Oriented Programming with Spring
使用spring实现面向切面的编程
11.1 Introduction
介绍
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. 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)补充面向对象的编程(OOP)通过提供一个其他的方式来考虑编程的结构。在OOP中的关键是类而在AOP中是切面。切面允许模块化的思维例如事务管理是切入不同的类型和object中的。(例如在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补充了spring的ioc来提供一个方便的中间件解决方案。
Spring 2.0 AOP
spring2.0的AOP
Spring 2.0 introduces a simpler and more powerful way of writing custom aspects using either a schema-based approach or the @AspectJ annotation style. Both of these styles offer fully typed advice and use of the AspectJ pointcut language, while still using Spring AOP for weaving.
spring2.0介绍了一个简单并且有力的方法通过基于schema的方式来书写自定义切面或使用@AspectJ的注解形式。这三种方式提供全面的类型建议和使用AspectJ切入点语言,当使用spring的aop来实现时。
The Spring 2.0 schema- and @AspectJ-based AOP support is discussed in this chapter. Spring 2.0 AOP remains fully backwards compatible with Spring 1.2 AOP, and the lower-level AOP support offered by the Spring 1.2 APIs is discussed in the following chapter.
spring2.0的schema和基于@AspectJ的AOP支持在这一节中介绍。spring2.0兼容了spring1.2的aop,并且低级别的aop支持通过spring1.2的api来提供的部分将在后续的章节中介绍。
AOP is used in the Spring Framework to…?
在spring框架中使用aop是为了……?
…? provide declarative enterprise services, especially as a replacement for EJB declarative services. The most important such service is declarative transaction management.
提供了声明式的企业式服务,尤其是作为替换EJB的声明式服务。最终要的是服务于声明式的事务。
…? allow users to implement custom aspects, complementing their use of OOP with AOP.
允许使用者实现自定义切面,使用AOP来补充他们使用的OOP。
[Note]
注意
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.
如果你对通用的声明式服务或其他之前声明中间件服务例如pooling感兴趣的话,你不需要直接使用spring的aop并且可以跳过本章的大部分内容。
11.1.1 AOP concepts
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 using regular classes (the schema-based approach) or regular classes annotated with the @Aspect annotation (the @AspectJ style).
切面:考虑一个模块切入多个类中。事务管理是一个好的例子在企业Java应用中来考虑切入。在spring的aop中,切面有普通的类实现(基于schema的方式)或使用了@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
below.) Many AOP frameworks, including Spring, model an advice as an interceptor, maintaining 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 allows you to 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允许你实现一个新的接口(并且一个相关的实现)对于通知object。例如,你应该使用一个介绍来实现一个bean实现IsModified接口用于简单的缓存(一个介绍作为AspectJ社区的一个内置定义被知晓)
Target object: object being advised by one or more aspects. Also referred to as the advised object. Since Spring AOP is implemented using runtime proxies, this object will always be a proxied object.
目标object:被一个或多个切面处理的object。也指向被通知的object。自动spring的aop通过运行时代理来实现,这些object永远是代理object。
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 will be a JDK dynamic proxy or a CGLIB proxy.
AOP代理:有aop框架创建的object用于实现切面行为(通知方法的执行等等)。在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.
包围:连接切面和其他应用类型或object来创建一个通知object。可以在编译时完成(例如,使用AspectJ编译器)、加载时或在运行时。spring的aop类似于其他纯Java的AOP框架,在运行时实现包围。
Types of advice:
通知的类型:
Before advice: Advice that executes before a join point, but which 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 executed after a join point completes normally: for example, if a method returns without throwing an exception.
后置通知:在结合点正常完成之后执行,例如,如果一个方法在没有抛出异常的情况下返回后。
After throwing advice: Advice to be executed if a method exits by throwing an exception.
异常通知:如果一个方法抛出了一个异常后执行的通知。
After (finally) advice: Advice to be executed 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. 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. 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 cannot fail to invoke it.
环绕通知时最常用的通知。自从spring的AOP类似于AspectJ,提供了全面的通知类型,我们建议你使用最接近的通知类型可以试想要求的行为。例如,如果你只是需要更行缓存并返回方法的值,你最好实现一个后置通知,尽管环绕通知也可以实现相同的功能。使用特定的通知类型提供一个简单的编程模型而且很少出错。例如,你不需要调用proceed方法在连接点用于环绕通知并且不会出现调用失败的情况。
In Spring 2.0, all advice parameters are statically typed, so that you work with advice parameters of the appropriate type (the type of the return value from a method execution for example) rather than Object arrays.
在spring2.0中,所有的通知参数是静态的类型使得你可以使用适当的参数类型(方法返回值的类型)而不是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. For example, an around advice providing declarative transaction management can be applied to a set of methods spanning multiple objects (such as all business operations in the service layer).
连接点的概念匹配切点是aop的核心是和过时只提供拦截的技术的区别。切点允许通知在继承结构中对目标独立进行处理。例如,环绕通知提供声明式事务管理可以应用在一系列的方法跨越多个Object(例如所有在服务层的业务操作)。
11.1.2 Spring AOP capabilities and goals
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. If you need to advise field access and update join points, consider a language such as AspectJ.
spring的aop目前只支持方法执行的连接点(通知spring的bean的方法执行)。Field的拦截没有实现,尽管支持field拦截可以被添加不需要破坏spring的核心aop的api。如果你需要处理field的方法并且更新连接点,考虑一下类似于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); it is rather to provide a close integration between AOP implementation and Spring IoC to help solve common problems in enterprise applications.
spring的aop是aop于大多数其他aop框架不同。目标不是提供给最完全的aop实现(尽管spring的aop是可以做到的):提供aop实现和ioc的紧密联系可以处理企业应用中的通用问题。
Thus, for example, the Spring Framework’s AOP functionality is normally used in conjunction with the Spring IoC container. Aspects are configured using normal bean definition syntax (although this allows powerful "autoproxying" capabilities): this is a crucial difference from other AOP implementations. There are some things you cannot do easily or efficiently with Spring AOP, such as advise very fine-grained objects (such as domain objects typically): AspectJ is the best choice in such cases. 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功能通常用和spring的ioc容器一同使用。AspectJ可以使用普通的bean定义语法来配置(尽管他允许强有力的“自动代理”功能):这是和其他aop实现的最大不同。有一些事情你无法使用spring的aop简单有效的实现,例如通知非常细颗粒的Object(例如通常的实体类):AspectJ在这种情况下是非常不错的选择。然而,我们的目标是spring的aop提供优秀的解决方法对于企业Java应用的大部分问题就可以了。
Spring AOP will never strive to compete with AspectJ to provide a comprehensive AOP solution. We believe that both proxy-based frameworks like Spring AOP and full-blown frameworks such as AspectJ are valuable, and that they are complementary, rather than in competition. Spring seamlessly integrates Spring AOP and IoC with AspectJ, to enable all uses of AOP to be catered for 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的aop将不会努力和AspectJ比拼来提供全面的aop解决方案。我们相信基于代理的框架例如spring的aop和全功能的框架例如AspectJ是有价值的并且他们是互相补充的而不是竞争。spring无缝的组合了spring的aop和ioc通过AspectJ来允许所有aop的使用可以满足基于spring应用的架构。这样的集成不需要影响spring的aop的api或aop联合api:spring的aop保留的向后兼容。参见后续章节对spring的aop的api的讨论。
[Note]
注意
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/domain model. However, in some places the Spring Framework does give you the option to introduce Spring Framework-specific dependencies into your codebase: 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. The Spring Framework (almost) always offers you the choice though: you have the freedom to make an informed decision as to which option best suits your particular use case or scenario.
spring框架的一个重要的原则是不侵入:这是你在使用特定的框架类和接口在你的应用时应该注意的。然而,在一些情况spring框架给出了处理特定spring依赖的处理方案来实现不侵入,给你这样的建议是因为在特定的场景可能只是方便阅读或以这样的方式来考虑你特定的代码。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 and/or Spring AOP, and you also have the choice of either the @AspectJ annotation-style approach or the Spring XML configuration-style approach. 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.
这样的选择取决于在这一节中如何选择aop框架。你可以选择AspectJ或Spring的aop,你也可以选择@AspectJ的注解方式或spring的xml配置文件形式。事实是这一节选择介绍的@AspectJ方式首先不应该作为spring小组喜欢@AspectJ方式而不是spring的xml方式
See Section 11.4, “Choosing which AOP declaration style to use” for a more complete discussion of the whys and wherefores of each style.
见11.4节,“选择aop定义的方式来使用”获得对选择哪种方式的原因的讨论。
11.1.3 AOP Proxies
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. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes; business classes normally will 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.
spring的aop也使用cglib代理。这对于代理类而不是接口来说是必要的。cglib用于默认如果一个业务Object没有实现一个接口的话。这对于编程来说是很好的练习:使用接口编程而不是类。业务类实现一个或多个业务接口。也可以强制使用cglib在某些场景你需要处理一个方法并没有定义一个接口或你需要传递代理Object给一个方法作为一个具体的类型。
It is important to grasp the fact that Spring AOP is proxy-based. See Section 11.6.1, “Understanding AOP proxies” for a thorough examination of exactly what this implementation detail actually means.
知道spring的aop是基于代理是很重要的事情。见11.6.1节,“理解aop代理”有这句话实际含义的解释。
11.2 @AspectJ support
支持@AspectJ
@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项目中作为AspectJ5发行版的一部分。spring拦截相同的注解类似于AspectJ5,使用AspectJ提供的库用于解析和匹配切点。aop的运行时是出spring的aop并且没有依赖AspectJ的编译和处理器。
[Note]
注意
Using the AspectJ compiler and weaver enables use of the full AspectJ language, and is discussed in Section 11.8, “Using AspectJ with Spring applications”.
使用AspectJ编译器并允许使用全部的AspectJ语言,将会在11.8节中讨论“在spring应用中使用AspectJ”
11.2.1 Enabling @AspectJ Support
允许@AspectJ的支持
To use @AspectJ aspects in a Spring configuration you need to enable Spring support for configuring Spring AOP based on @AspectJ aspects, and autoproxying beans based on whether or not they are advised by those aspects. By autoproxying we mean that if Spring determines that a bean is advised by one or more aspects, it will automatically generate a proxy for that bean to intercept method invocations and ensure that advice is executed as needed.
为了在spring的配置中使用@AspectJ你需要允许spring的支持用于配置spring的aop基于@AspectJ切面并且自动代理bean基于是否他们是否在切面的通知范围内。通过自动代理我们以为spring决定bean是否被一个或多个切面处理,将据此为bean自动生成bean的代理来拦截方法调用并根据需要来执行通知处理。
The @AspectJ support can be enabled with XML or Java style configuration. In either case you will also need to ensure that AspectJ’s aspectjweaver.jar library is on the classpath of your application (version 1.6.8 or later). This library is available in the 'lib' directory of an AspectJ distribution or via the Maven Central repository.
@AspectJ可以通过xml或java风格的配置来支持。不管是哪一种方式你也需要保证AspectJ的aspectjweaver.jar库在你应用的类路径中(版本1.6.8或更高)。这个库在AspectJ发行的lib文件夹中或通过Maven的中心仓库。
Enabling @AspectJ Support with Java configuration
使用java配置运行@AspectJ支持
To enable @AspectJ support with Java @Configuration add the @EnableAspectJAutoProxy annotation:
为了实现使用java的@Configuration来允许@AspectJ需要添加@EnableAspectJAutoProxy注解
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
Enabling @AspectJ Support with XML configuration
使用xml配置允许@AspectJ支持
To enable @AspectJ support with XML based configuration use the aop:aspectj-autoproxy element:
为了实现使用基于xml的配置允许@AspectJ需要使用aop:aspectj-autoproxy元素
<aop:aspectj-autoproxy/>
This assumes that you are using schema support as described in Chapter 41, XML Schema-based configuration. See Section 41.2.7, “the aop schema” for how to import the tags in the aop namespace.
这假设你使用的schema在41章描述,基于schema的配置。见41.2.7节“aop的schema”来引入aop命名空间的schema。
11.2.2 Declaring an aspect
定义一个切面
With the @AspectJ support enabled, any bean defined in your application context with a class that is an @AspectJ aspect (has the @Aspect annotation) will be automatically detected by Spring and used to configure Spring AOP. The following example shows the minimal definition required for a not-very-useful aspect:
允许了@AspectJ的支持,任何定义在你的应用上下文中的bean都可以被@AspectJ切面(使用了@AspectJ注解)将会被自动被spring探测并且使用来配置spring的aop。下面的例子展示了一个小的但不是十分有用的切面的例子:
A regular bean definition in the application context, pointing to a bean class that has the @Aspect annotation:
一个通常的bean的定义在应用上下文中,指向一个定义了@AspectJ注解的bean类
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of aspect here as normal -->
<!-- 像通常一样定义切面的属性 -->
</bean>
And the NotVeryUsefulAspect class definition, annotated with org.aspectj.lang.annotation.Aspect annotation;
并且NotVeryUsefulAspect类的定义,使用了org.aspectj.lang.annotation.Aspect注解
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
Aspects (classes annotated with @Aspect) may have methods and fields just like any other class. They may also contain pointcut, advice, and introduction (inter-type) declarations.
切体(使用@Aspect的类)可以有方法和属性就像其他的类。他们也可以包括切点、通知和内置类型定义。
[Note]
注意
You may register aspect classes as regular beans in your Spring XML configuration, or autodetect them through classpath scanning - just like 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).
你可以注册切面类作为通常bean在你spring的xml配置,或自动探测他们通过类路径扫描,就像其他spring管理的bean。然而注意到@Aspect注解对于类的自动探测是不够的:处于这个原因你需要添加一个@Component注解(或一个自定义注解声明,作为spring组件扫描器的规则之一)。
[Note]
注意
In Spring AOP, it is not possible to have aspects themselves be the target 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注解表明他是一个切面并且排除在自动代理的范围之外。
11.2.3 Declaring a pointcut
定义一个切点
Recall that pointcuts determine join points of interest, and thus enable us to control when advice executes. 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. 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 using the @Pointcut annotation (the method serving as the pointcut signature must have a void return type).
重新调用切点决定连接点,允许我们来控制通知的执行。spring的aop只支持方法的执行对于每个spring的bean,因此你可以考虑匹配spring的bean的方法执行的切点。一个切点的定义有两部分:一个包含名字的签名包含任意参数和一个切点表达式决定实际会影响哪些方法的执行。在@AspectJ注解形式的AOP中,一个切点的签名由常规方法定义来提供,并且切点表达式使用@Pointcut注解指定(方法作为切点签名必须有一个void的返回类型)
An example will help make this distinction between a pointcut signature and a pointcut expression clear. The following example defines a pointcut named 'anyOldTransfer' that will match the execution of any method named 'transfer':
一个例子将会帮助使得切点签名和切点表达式的定义更加清晰。下面的例子定义了一个名字为'anyOldTransfer'将会匹配任何名字为transfer的方法。
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature
The pointcut expression that forms the value of the @Pointcut annotation is a regular AspectJ 5 pointcut expression. For a full discussion of AspectJ’s pointcut language, see the AspectJ Programming Guide (and for extensions, the AspectJ 5 Developers Notebook) or one of the books on AspectJ such as "Eclipse AspectJ" by Colyer et. al. or "AspectJ in Action" by Ramnivas Laddad.
切点表达式由@Pointcut注解的值是一个AspectJ5的切点表达式。关于AspectJ切点语言的描述,见AspectJ的编程指南(作为扩展,请参考AspectJ 5 Developers Notebook)或者Colyer的一本关于AspectJ的书例如“Eclipse AspectJ”。或者是Ramnivas Laddad的"AspectJ in Action"。
Supported Pointcut Designators
支持切点标识符
Spring AOP supports the following AspectJ pointcut designators (PCD) for use in pointcut expressions:
spring的aop支持下面的AspectJ切点标识符用于在切点表达式中使用:
Other pointcut types
其他切点类型
The full AspectJ pointcut language supports additional pointcut designators that are not supported in Spring. These are: call, get, set, preinitialization, staticinitialization, initialization, handler, adviceexecution, withincode, cflow, cflowbelow, if, @this, and @withincode. Use of these pointcut designators in pointcut expressions interpreted by Spring AOP will result in an IllegalArgumentException being thrown.
全部的AspectJ切点语言支持额外的切点标识符在spring中并没有被支持。他们是:call、get、set、preinitialization、staticinitialization、initialization handler、adviceexecution、 withincode、cflow、cflowbelow、if、@this和@withincode。在spring的aop中使用这些标识符会抛出IllegalArgumentException异常。
The set of pointcut designators supported by Spring AOP may be extended in future releases to support more of the AspectJ pointcut designators.
spring的aop或许会在后续的版本中会扩展切点标识符的集合用于支持更多的AspectJ切点标识符。
execution - for matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP
execution - 用于匹配方法执行连接点,是你在spring的aop中会使用的主要的切点标识符
within - limits matching to join points within certain types (simply 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时方法的执行)当目标object(被代理的应用object)是给定类型的一个实例
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时方法的执行)当执行object的执行类有给定类型的注解
@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 type(s)
@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 (method being executed in Spring AOP) has the given annotation
@annotaiion - 限制连接点的匹配当连接点的subject有给定的注解(在spring的aop中被执行的方法)
Because Spring AOP limits matching to only method execution join points, the discussion of the pointcut designators above gives a narrower definition than you will find in the AspectJ programming guide. 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 (bound to this) and the target object behind the proxy (bound to target).
因为spring的aop限制匹配对于方法执行连接点,上面讨论的切点标识符给定了一个狭义的定义并出自AspectJ的编程指南。额外的,AspectJ自身是基于类型敏感的并且在执行连接点和目标引用相同的object,该object执行方法。spring的aop是基于代理系统和区分代理object和目标object在代理之后。
[Note]
注意
Due to the proxy-based nature of Spring’s AOP framework, protected methods are by definition not intercepted, neither for JDK proxies (where this isn’t applicable) nor for CGLIB proxies (where this is technically possible but not recommendable for AOP purposes). As a consequence, any given pointcut will be matched against public methods only!
由于基于代理的spring的aop框架的特性,定义的protected方法不会被处理,不管是JDK的代理(是做不到的)还是CGLIB代理(有技术可以实现但是不建议)。因此,一个给定的切点只会将匹配公共的方法。
If your interception needs include protected/private methods or even constructors, 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 first before making a decision.
如果你的拦截需要包括protected和private方法或构造器,考虑使用基于spring的本地AspectJ而不是spring基于代理的aop框架。他包含不同模式的aop使用使用不同的字符,因此保证你在决定之前熟悉如何处理。
Spring AOP also supports an additional PCD named bean. This PCD allows you to 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(使用通配符)。PCD的bean有下面的形式:
bean(idOrNameOfBean)
The idOrNameOfBean token can be the name of any Spring bean: limited wildcard support using the * character is provided, so if you establish some naming conventions for your Spring beans you can quite easily write a bean PCD expression to pick them out. As is the case with other pointcut designators, the bean PCD can be &&'ed, ||'ed, and ! (negated) too.
idOrNameOfBean可以是任何spring的bean的名字:受限的通配符支持使用*字符,因此如果你建立一些命名的转换对于你的spring的bean你可以安静的写bean的PCD表达式实现他们。由于其他切点标识符,bean的PCD可以是&&、|\和!。
[Note]
注意
Please note that the bean PCD is only supported in Spring AOP - and not in native AspectJ weaving. It is a Spring-specific extension to the standard PCDs that AspectJ defines and therefore not available for aspects declared in the @Aspect model.
请注意PCD的bean只在spring的aop中支持并且不支持本地的AspectJ。他是spring特定的扩展对于标准的PCD,AspectJ的定义并且不支持定义在@AspectJ的模式中。
The bean PCD operates at the instance level (building on the Spring bean name concept) rather than at the type level only (which is what weaving-based AOP is limited to). 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.
PCD的bean操作符在实例的级别(建立在spring的bean名字的内容上)而不是类型级别(基于组织的AOP是限制的)。基于实例的切点标识符是特定的spring的基于代理的aop框架并且和spring的bean工厂集成,所以可以通过特定bean的名字来定义。
Combining pointcut expressions
合并切点表达式
Pointcut expressions can be combined using '&&', '||' and '!'. It is also possible to refer to pointcut expressions by name. The following example shows three pointcut expressions: anyPublicOperation (which matches if a method execution join point represents the execution of any public method); inTrading (which matches if a method execution is in the trading module), and tradingOperation (which matches if a method execution represents any public method in the trading module).
切点表达式可以合并通过&&、||、和!。并且可以通过名字来指向切点表达式。下面的例子展示了三个切点表达式:anyPublicOperation(匹配如果方法执行连接点代表任何公共方法)、inTrading(匹配在trading模式下的方法执行)和tradingOperation(匹配在trading模式下代表任何公共方法的方法执行)。
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
It is a best practice to build more complex pointcut expressions out of smaller named components as shown above. 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可见性规则应用(你可以在相同的类型发现私有的切点、protected的切点在结构中,和公共的切点等)。可见性不影响切点的匹配。
Sharing common pointcut definitions
分享共同的切点定义
When working with enterprise applications, you often want to refer to modules of the application and particular sets of operations from within several aspects. We recommend defining a "SystemArchitecture" aspect that captures common pointcut expressions for this purpose. A typical such aspect would look as follows:
当在企业级应用中时,你通常希望指向应用的模块、特别是来自一些方面的操作集合。我们建议定义"SystemArchitecture"来获得共同切点的表达式。一个典型的切面的例子如下:
package com.xyz.someapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemArchitecture {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.someapp.web package or any sub-package
* under that.
* 一个连接点定义在web层处理方法定义在com.xyz.someapp.web包中或任何下面的子包中
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.someapp.service package or any sub-package
* under that.
* 一个连接点定义在服务层处理方法定义在com.xyz.someapp.service包中或任何下面的子包中
*/
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.someapp.dao package or any sub-package
* under that.
* 一个连接点定义在数据访问层处理方法定义在com.xyz.someapp.dao包中或任何下面的子包中
*/
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
* 一个业务服务是实现了服务接口的任何方法定义。这样的定义假定接口定义在service包中,并且他们的实现在其子包中
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
* the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
* could be used instead.
* 如果你的组服务接口通过功能域(例如,在包com.xyz.someapp.abc.service中和com.xyz.someapp.def.service中)则可以使用切点表达式"execution(* com.xyz.someapp..service.*.*(..))"来代替
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
* 或者,你可以书写表达式使用‘bean’ PCD,类似于"bean(*Service)"。(这假设你的spring服务使用了统一的命名格式)
*/
@Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
public void businessService() {}
/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
* 一个数据访问操作时任何一个方法定义的执行在一个dao的接口上。这样的定义假设接口在dao包中,并且他的的实现在其子包中
*/
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
The pointcuts defined in such an aspect can be referred to anywhere that you need a pointcut expression. For example, to make the service layer transactional, you could write:
切点定义在这样的切面可以被任何地方引用而你需要的是一个切点表达式。例如,为了在服务层中加入事务,你可以写:
<aop:config>
<aop:advisor
pointcut="com.xyz.someapp.SystemArchitecture.businessService()"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
The <aop:config> and <aop:advisor> elements are discussed in Section 11.3, “Schema-based AOP support”. The transaction elements are discussed in Chapter 17, Transaction Management.
<aop:config>和<aop:advisor>元素在11.3节中被讨论,“基于schema的aop支持”。事务元素将在17章中被讨论,事务管理。
Examples
例子
Spring AOP users are likely to use the execution pointcut designator the most often. The format of an execution expression is:
spring aop的用户经常倾向于使用执行切点指示器。执行表达式的格式是:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
All parts except the returning type pattern (ret-type-pattern in the snippet above), name pattern, and 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. Most frequently you will use * as the returning type pattern, which matches any return type. A fully-qualified type name will match 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 specifying a declaring type pattern then 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 of parameters (zero or more). The pattern (*) matches a method taking one parameter of any type, (*,String) matches a method taking two parameters, the first can be of any type, the second must be a String. Consult the Language Semantics section of the AspectJ Programming Guide for more information.
所有的部分期待返回类型模式(上面片段中的ret-type-pattern)、name pattern和参数pattern作为选择。返回类型模式决定方法的返回类型必须按照切点匹配的顺序。你最常用的是*号作为返回类型模式来匹配任何返回类型。一个全限定类型名将指挥匹配方法给定的返回类型。名称模式匹配方法名。你可以使用*通配符作为所有或部分名字模式。如果指定一个定义的类型模式包括一个后续来结合命名模式组件。参数模式会有些许的复杂。()匹配一个方法没有参数而(..)匹配任意个数的参数(0个或多个)(*)匹配一个方法有一个参数但类型任意,(*,String)匹配一个方法有两个参数第一个参数是任意类型但是第二个必须是字符串。查阅AspectJ的语言规范可以获得更多的信息。
Some examples of common pointcut expressions are given below.
一些常用的切点表达式列举如下。
the execution of any public method:
任何公共方法的执行:
execution(public * *(..))
the execution of any method with a name beginning with "set":
任何以set开始的方法
execution(* set*(..))
the execution of any method defined by the AccountService interface:
定义了AccountService接口的任何方法
execution(* com.xyz.service.AccountService.*(..))
the execution of any method defined in the service package:
定义在service包中的任何方法
execution(* com.xyz.service.*.*(..))
the execution of any method defined in the service package or a sub-package:
定义在service包和其子包中的所有方法
execution(* com.xyz.service..*.*(..))
any join point (method execution only in Spring AOP) within the service package:
任何连接点(方法执行只在spring的aop中)在service包中
within(com.xyz.service.*)
any join point (method execution only in Spring AOP) within the service package or a sub-package:
任何连接点(方法执行只在spring的aop中)在service包及其子包中
within(com.xyz.service..*)
any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface:
任何连接点(方法执行只在spring的aop中)代理实现了AccountService接口
this(com.xyz.service.AccountService)
[Note]
注意
'this' is more commonly used in a binding form :- see the following section on advice for how to make the proxy object available in the advice body.
‘this’是一个常用的用于绑定,见后续的章节讨论如何使得代理object在advice中可用
any join point (method execution only in Spring AOP) where the target object implements the AccountService interface:
任何连接点(方法执行只在spring的aop中)当目标object实现了AccountService接口
target(com.xyz.service.AccountService)
[Note]
注意
'target' is more commonly used in a binding form :- see the following section on advice for how to make the target object available in the advice body.
‘target’是一个常用的用于绑定,将后续章节讨论如何使得目标object在advice中可用
any join point (method execution only in Spring AOP) which takes a single parameter, and where the argument passed at runtime is Serializable:
任何连接点(方法执行只在spring的aop中)使用了单一的参数,并且参数在运行时被传递时可以序列化的
args(java.io.Serializable)
[Note]
注意
'args' is more commonly used in a binding form :- see the following section on advice for how to make the method arguments available in the advice body.
‘args’是一个常用的用于绑定,将后续章节讨论如何使得方法参数在advice中可用
Note that the pointcut given in this example is different to execution(* *(java.io.Serializable)): the args version matches if the argument passed at runtime is Serializable, the execution version matches if the method signature declares a single parameter of type Serializable.
注意在这个例子中给定的切点不同于execution(* *(java.io.Serializable)):参数版本匹配如果传递的参数是可以序列化的,execution的版本匹配如果方法的签名定义了一个可序列化的参数类型
any join point (method execution only in Spring AOP) where the target object has an @Transactional annotation:
任何连接点(方法执行只在spring的aop中)当目标object有一个@Transactional注解
@target(org.springframework.transaction.annotation.Transactional)
[Note]
注意
'@target' can also be used in a binding form :- see the following section on advice for how to make the annotation object available in the advice body.
‘@targer’也可以以绑定的形式使用,见后续章节讨论如何使得注解object在advice中可用
any join point (method execution only in Spring AOP) where the declared type of the target object has an @Transactional annotation:
任何连接点(方法执行只在spring的aop中)当目标object的定义类型有@Transactional注解
@within(org.springframework.transaction.annotation.Transactional)
[Note]
注意
'@within' can also be used in a binding form :- see the following section on advice for how to make the annotation object available in the advice body.
‘@within’也可以以绑定的形式使用,见后续章节讨论如何使得注解object在advice中可用
any join point (method execution only in Spring AOP) where the executing method has an @Transactional annotation:
任何连接点(方法执行只在spring的aop中)当执行的方法有@Transactional注解
@annotation(org.springframework.transaction.annotation.Transactional)
[Note]
注意
'@annotation' can also be used in a binding form :- see the following section on advice for how to make the annotation object available in the advice body.
‘@annotation’也可以以绑定的形式使用,见后续章节讨论如何使得注解object在advice中可用
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注解
@args(com.xyz.security.Classified)
[Note]
注意
'@args' can also be used in a binding form :- see the following section on advice for how to make the annotation object(s) available in the advice body.
‘@args’也可以以绑定的形式使用,见后续章节讨论如何使得注解object在advice中可用
any join point (method execution only in Spring AOP) on a Spring bean named tradeService:
任何连接点(方法执行只在spring的aop中)在一个spring的bean名为tradsService
bean(tradeService)
any join point (method execution only in Spring AOP) on Spring beans having names that match the wildcard expression *Service:
任何连接点(方法执行只在spring的aop中)在spring的bean名字的结尾是Service
bean(*Service)
Writing good pointcuts
书写好的切点
During compilation, AspectJ processes pointcuts in order to try and optimize matching performance. Examining code and determining if each join point matches (statically or dynamically) a given pointcut is a costly process. (A dynamic match means the match cannot be fully determined from static analysis and a test will be placed in the code to determine if there is an actual match when the code is running). On first encountering a pointcut declaration, AspectJ will rewrite it into an optimal form for the matching process. What does this mean? 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.
在编译中AspectJ执行切点用于尝试和优化匹配性能。检查代码并且决定如果每个连接点匹配(静态或动态)一个给定切点是一个高代价的过程。(动态匹配意味着匹配可以从静态分析中决定并且测试在代码中会决定实际匹配当代码运行时)。第一次处理切点定义,AspectJ将改写他成为最优的形式为了匹配程序。这是什么意思?基本的切点以DNF(析取范式)重写并且切点的组件被排序以便这些组件可以容易的实现检查。这意味着你不需要考虑理解不同切点指示器的性能并且替代他们在切点定义时以任意的顺序。
However, AspectJ can only work with what it is told, and 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. The existing designators naturally fall into one of three groups: kinded, scoping and context:
然而,AspectJ只能工作在他被指定的情形,并且对于匹配最优的性能你应当考虑在定义中试图实现和缩小搜索空间用于匹配。已有的指示器自然进入三个不同的部分:kinded、scoping和context;
Kinded designators are those which select a particular kind of join point. For example: execution, get, set, call, handler
Kinded指示器选择特定类型的连接点。例如,execution、get、set、call、handler
Scoping designators are those which select a group of join points of interest (of probably many kinds). For example: within, withincode
Scoping指示器选择一组有兴趣的连接点(当然许多kind)。例如,within、withincode
Contextual designators are those that match (and optionally bind) based on context. For example: this, target, @annotation
Contextual指示器匹配(和选择性绑定)基于context。例如,this、target、@annotation
A well written pointcut should try and include at least the first two types (kinded and scoping), whilst the contextual designators may be included if wishing to match based on join point context, or bind that context for use in the advice. Supplying either just a kinded designator or just a contextual designator will work but could affect weaving performance (time and memory used) due to all the extra processing and analysis. Scoping designators are very fast to match and their usage means AspectJ can very quickly dismiss groups of join points that should not be further processed - that is why a good pointcut should always include one if possible.
一个良好的切点应当试图并包括至少两个类型(kinded和scoping),同时contextual指示器或许会被包括如果希望匹配基于连接点上下文或绑定在advice中使用的上下文。只是提供kinded指示器或只提供contextual指示器也会工作但是可能影响处理性能(时间和内存的使用)在所有额外的处理和分析中。scoping指示器很快匹配并且使用AspectJ可以快速排除不会被处理的连接点组,也就是说为什么一个好的切点表达式很重要。
11.2.4 Declaring advice
定义advice
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.
advice和切点表达式相连并且在之前执行或匹配切点在方法执行的前后。切点表达式可以是简单的引用一个命名切点或一个切点表达式的定义。
Before advice
在advice之前
Before advice is declared in an aspect using the @Before annotation:
在advice之前在切面使用@Before注解
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
If using an in-place pointcut expression we could rewrite the above example as:
如果使用一个内置切点表达式我们可以重写上面的例子如下:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
After returning advice
在返回advice之后
After returning advice runs when a matched method execution returns normally. It is declared using the @AfterReturning annotation:
当一个匹配方法执行正常返回之后返回advice执行。使用@AfterReturning注解来定义:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
[Note]
注意
Note: it is of course possible to have multiple advice declarations, and other members as well, all inside the same aspect. We’re just showing a single advice declaration in these examples to focus on the issue under discussion at the time.
注意:当然可以有多个advice定义并且其他也可以在同一个切面中。我们只是展示一个简单的advice定义在这些例子中为了是关注符合当前讨论的问题。
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 for this:
有些时候你需要在advice中访问实际的返回值。你可以使用@AfterReturning在返回值之后如下:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
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 will be 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 ( Object in this case, which will match any return value).
在返回属性中使用的名字必须和advice方法中的参数名相关。当一个方法执行返回,返回值将传递给advice方法作为相关参数值。一个返回逻辑也约束匹配这些方法的执行并返回特定类型的值(在这个例子中的object将会匹配任何返回值)。
Please note that it is not possible to return a totally different reference when using after-returning advice.
请注意不能返回一个不同的引用当使用after-returning的advice时。
After throwing advice
在抛出异常advice之后
After throwing advice runs when a matched method execution exits by throwing an exception. It is declared using the @AfterThrowing annotation:
当方法执行并抛出一个异常时after throwing的advice执行。需要使用@AfterThrowing注解来定义
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}
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. 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.
你经常希望当给定类型的异常被抛出时执行advice,并且你也需要在advice中访问抛出的异常。使用throwing属性对于严格的匹配情况(如果可以,使用Throwable作为异常类型)并绑定对于advice参数抛出的异常。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
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 will be 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属性中使用的名字必须和advice方法中的参数名相关。当一个方法执行并抛出一个异常时,异常将会传递给advice方法作为相关的参数值。处理逻辑也可以严格匹配方法的执行根据异常定义的类型(在这个例子中使用的时DataAccessException异常)
After (finally) advice
在最终advice之后
After (finally) advice runs however a matched method execution exits. It is declared using the @After annotation. After advice must be prepared to handle both normal and exception return conditions. It is typically used for releasing resources, etc.
当匹配方法执行之后after(finally)的advice会被执行。使用@After注解来定义。after的advice必须被准备来处理正常或异常的返回条件。通常用于释放资源等等。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
Around advice
环绕advice
The final kind of advice is around advice. Around advice runs "around" a matched method execution. It has the opportunity to do work both before and after the method executes, and to determine when, how, and even if, the method actually gets to execute at all. Around advice is often used if you need to share state before and after a method execution in a thread-safe manner (starting and stopping a timer for example). Always use the least powerful form of advice that meets your requirements (i.e. don’t use around advice if simple before advice would do).
最后一种advice是环绕advice。环绕advice围绕方法执行。可以在方法执行之前和执行之后执行并且定义何时做什么尽管方法实际获得到了执行。环绕advice也经常用于如果你需要分享方法执行的开始和结束在线程安全的情况(例如开始和结束计时器)。尽可能小的使用advice根据你的需要(例如,不要使用环绕advice如果你只是需要在之前执行)
Around advice is declared using the @Around annotation. The first parameter of the advice method must be of type ProceedingJoinPoint. Within the body of the advice, calling proceed() on the ProceedingJoinPoint causes the underlying method to execute. The proceed method may also be called passing in an Object[] - the values in the array will be used as the arguments to the method execution when it proceeds.
使用@Around注解来定义环绕advice。第一个参数必须是ProceediingJoinPoint类型。在advice中调用ProceedingJoinPoint中的proceed方法调用需要执行的方法。proceed方法也可以被调用传递一个object数组——数组的值将会被当作参数在方法执行时被使用。
[Note]
注意
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 (Don’t worry if this doesn’t make sense right now!). 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 are compiling @AspectJ aspects written for Spring and using 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.
处理的行为被调用通过使用一个object数组作为一个小的不同和环绕advice相比,通过AspectJ编译器被编译。对于环绕advice的定义使用传统的AspectJ语言,传递参数的数目必须和参数传递给环绕advice的数目匹配(不是被连接点处理的参数的数目),并且被传递用于处理的参数在给定的参数位置提供原始值在连接点对于绑定值的实体(不要担心如果现在无法理解)。spring处理的方式是简单的并且基于代理会有更好的匹配,execution only semantics。你也可以只需要意识到这个不同如果你编译@AspectJ切面为spring编写并使用proceed来处理参数使用AspectJ编译器。有一种方法可以书写这样的切面并100%匹配spring的aop和AspectJ,将会在后续的章节中讨论advice的参数。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
The value returned by the around advice will be the return value seen by the caller of the method. A simple caching aspect for example could return a value from a cache if it has one, and invoke proceed() 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 quite legal.
环绕advice返回的值将会被调用的方法看到。一个简单捕获切面例如可以从缓存中返回如果有的话,并且调用proceed方法如果没有的话。注意proceed应该被调用一次、多次或者不调用,都是可以的。
Advice parameters
advice参数
Spring offers fully typed advice - meaning that you declare the parameters you need in the advice signature (as we saw for the returning and throwing examples above) rather than work with Object[] arrays all the time. We’ll see how to make argument and other contextual values available to the advice body in a moment. First let’s take a look at how to write generic advice that can find out about the method the advice is currently advising.
spring提供了全部类型的advice,意味着你可以定义参数根据需要在advice的签名中(我们看见并返回或抛出异常的例子如上)都可以使用object数组。我们将会看到如果处理参数和其他contextual值对于advice。首先我们需要了解并找出如何书写通用的advice是建议的写法。
Access to the current JoinPoint
访问当前的JoinPoint
Any advice method may declare as its first parameter, a parameter of type org.aspectj.lang.JoinPoint (please note that around advice is required to declare a first parameter of type ProceedingJoinPoint, which is a subclass of JoinPoint. The JoinPoint interface provides a number of useful methods such as getArgs() (returns the method arguments), getThis() (returns the proxy object), getTarget() (returns the target object), getSignature() (returns a description of the method that is being advised) and toString() (prints a useful description of the method being advised). Please do consult the javadocs for full details.
任何advice方法可以定义作为第一个参数,一个org.aspectj.lang.JoinPoint类型的参数(请注意环绕advice需要定义第一个参数类型为ProceedingJoinPoint,是JoinPoint的子类。JoinPoint接口提供一些有用的方法例如getArgs来返回方法参数、getThis返回代理object、getTarget来返回目标object、getSignature返回方法的描述根据建议和toString打印方法被建议的有用描述。)请参考javadocs来了解更加详细的信息。
Passing parameters to advice
向advice传递参数
We’ve 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 a parameter name is used in place of a type name in an args expression, then the value of the corresponding argument will be 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:
我们已经看到如何绑定异常的返回值(使用returning和after throwing的advice)。为了使得参数在advice中可用,你可以使用绑定参数的形式。如果一个参数名被在参数表达式的类型,则相关的参数值将被作为参数传递后被advice调用。一个例子就可以说明这件事。假设你希望建议dao操作的执行并传递一个Account的object作为第一个参数并且你需要访问account在advice中。你可以书写如下:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
The args(account,..) part of the pointcut expression serves two purposes: firstly, 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; secondly, it makes the actual Account object available to the advice via the account parameter.
args(account,..)部分在切点表达式中有两个目的:第一,他严格匹配方法的执行当方法有至少一个参数时,并且传递给这些参数是Account的实例。第二,他使得实际的Account的object在通过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 just refer to the named pointcut from the advice. This would look as follows:
另一个方法书写就是定义一个切点然后“提供”Account的object当他匹配连接点并且只是引用advice中的命名切点。可以如下所示:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
The interested reader is once more referred to the AspectJ programming guide for more details.
感兴趣的读者可以参考AspectJ的编程指南了解更多详尽的信息。
The proxy object ( this), target object ( target), and annotations ( @within, @target, @annotation, @args) can all be bound in a similar fashion. The following example shows how you could match the execution of methods annotated with an @Auditable annotation, and extract the audit code.
代理object、目标object和注解可以以类似的形式绑定。下面的例子展示了如何匹配方法的执行基于@Auditable注解并提取出了audit的代码。
First the definition of the @Auditable annotation:
首先是@Auditable注解的定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
And then the advice that matches the execution of @Auditable methods:
然后是advice匹配@Auditable方法的执行
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
Advice parameters and generics
advice参数和泛型
Spring AOP can handle generics used in class declarations and method parameters. Suppose you have a generic type like this:
spring的aop可以处理定义在类定义中的泛型和方法参数。假设你有一个泛型如下:
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
You can restrict interception of method types to certain parameter types by simply typing the advice parameter to the parameter type you want to intercept the method for:
你可以限制方法的拦截类型对于特定的参数类型通过定义advice参数和你需要拦截的参数类型:
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
That this works is pretty obvious as we already discussed above. However, it’s worth pointing out that this won’t work for generic collections. So you cannot define a pointcut like this:
根据我们上面的讨论这是很明显的。然而,值得指出不能用于泛型集合。因此你不能如下定义切点:
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
// Advice implementation
}
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的值。为了达到一些相似的事情你必须键入集合的类型并手动检查元素的类型。
Determining argument names
决定参数的名字
The parameter binding in advice invocations relies on matching names used in pointcut expressions to declared parameter names in (advice and pointcut) method signatures. Parameter names are not available through Java reflection, so Spring AOP uses the following strategies to determine parameter names:
参数在advice中绑定依赖于名字的匹配在切点表达式中定义的参数名(advice和切点)在方法签名中。参数名不能通过java的反射来获取因此spring的aop使用下面的策略来决定参数的名字:
If the parameter names have been specified by the user explicitly, then the specified parameter names are used: both the advice and the pointcut annotations have an optional "argNames" attribute which can be used to specify the argument names of the annotated method - these argument names are available at runtime. For example:
如果参数名被用户显示定义,则定义的参数名将被使用:advice和切点注解有一个可选属性‘argNames’可以用来定义参数的名字有关被注解的方法,在运行时可以指定三个参数。如下:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}
If the first parameter is of the JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart type, you may leave out 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 need not include it:
如果第一个参数是JoinPoint、ProceedingJoinPoint或JoinPoint.StaticPart类型,你可以忽略argNames属性的值。例如,如果你修改签名的advice来接收连接点的object,argNames属性不需要包含他。
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
The special treatment given to the first parameter of the JoinPoint, ProceedingJoinPoint, and JoinPoint.StaticPart types is particularly convenient for advice that do not collect any other join point context. In such situations, you may simply omit the "argNames" attribute. For example, the following advice need not declare the "argNames" attribute:
对第一个参数是JoinPoint、ProceedingJoinPoint和JoinPoint.StaticPart类型则特别方便对于advice不需要收集任何连接点上下文。在这种情况下,你可以简单的省略argNames属性。例如下面的advice不需要定义argNames属性:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
// ... use jp
}
Using the 'argNames' attribute is a little clumsy, so if the 'argNames' attribute has not been specified, then Spring AOP will look at the debug information for the class and try to determine the parameter names from the local variable table. This information will be present as long as the classes have been compiled with debug information ( '-g:vars' at a minimum). The consequences of compiling with this flag on are: (1) your code will be slightly easier to understand (reverse engineer), (2) the class file sizes will be very slightly bigger (typically inconsequential), (3) the optimization to remove unused local variables will not be applied by your compiler. In other words, you should encounter no difficulties building with this flag on.
使用argNames属性是不推荐的,因此如果argNames属性没有被定义则spring的aop将会在调试信息中查找类并试图决定参数名从本地的变量表。这个信息将会持续从类被编译带有调试信息(至少使用‘-g:vars’)。编译这个标志的结果是(1)你的代码将会被易于理解(逆向工程),(2)类文件的大小将会有些大(通常不重要),(3)对非使用本地变量的优化将不会应用于你的编译器。一句话,你需要在此标志开启的情况下没有问题的被构建。
[Note]
注意
If an @AspectJ aspect has been compiled by the AspectJ compiler (ajc) even without the debug information then there is no need to add the argNames attribute as the compiler will retain the needed information.
如果一个@AspectJ切面被AspectJ编译器编译甚至没有调试信息就不需要添加argNames属性作为编译器将保持需要的信息。
If the code has been compiled without the necessary debug information, then Spring AOP will attempt to deduce the pairing of binding variables to parameters (for example, if only one variable is bound in the pointcut expression, and the advice method only takes one parameter, the pairing is obvious!). If the binding of variables is ambiguous given the available information, then an AmbiguousBindingException will be thrown.
If all of the above strategies fail then an IllegalArgumentException will be thrown.
如果代码需要被编译在没有必要的调试信息的情况下,spring的aop将会尝试推断并配对绑定变量对于参数(例如,如果一个变量绑定在切点表达式中并且advice方法只有一个参数则配对是明显的)。如果变量的绑定是有歧义的根据给定的信息则AmbiguousBindingException异常会被抛出。如果所有给定的策略失败则会抛出IllegalArgumentException异常。
Proceeding with arguments
处理参数
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 simply to ensure that the advice signature binds each of the method parameters in order. For example:
我们之前注意到我们可以描述如果书写调用带参数的proceed并一致的跨spring的aop和AspectJ。解决方案是简单的被允许的而且advice签名绑定每个方法参数是有顺序的。例如:
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
String accountHolderNamePattern) throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}
In many cases you will be doing this binding anyway (as in the example above).
在许多例子中你讲这样使用绑定(例如上面的例子)
Advice ordering
advice的顺序
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. 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).
发生了什么当多个advice需要同时在同一个连接点执行时?spring的aop允许相同的处理规则作为AspectJ来决定advice的执行顺序。最高的advice首先被执行在进入连接点时(因此给定两个前置advice,高优先的先执行)。出连接点时最高优先的最后执行(因此给定两个后置advice则高优先级的后执行)。
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.getValue() (or the annotation value) has the higher precedence.
当两个通知定义在不同的切面并且都需要在同一个连接点执行,除非你定义了否则执行的顺序并没有被定义。你可以控制执行的顺序通过定义优先级。以spring的方式需要实现org.springframework.core.Ordered接口在切面类或使用Order注解。给定两个切面,切面返回Ordered.getValue中的较低值(或注解中的值)有高优先级。
When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the declaration order via 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 - which can be ordered at the aspect level.
当两个advice定义在同一个切面并且需要在同一个连接点执行,并且顺序未定义(即没有方法获得定义的顺序通过反射)。考虑将一个advice方法与一个advice方法合并根据每个连接点在每个切面类或将advice切分为切面类,可以在aspect级别指定顺序。
11.2.5 Introductions
引入
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中内部类型的声明)允许一个切面定义advice的object并实现给定的接口,并提供接口的实现对于这些object。
An introduction is made 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 UsageTracked, and an implementation of that interface DefaultUsageTracked, the following aspect declares that all implementors of service interfaces also implement the UsageTracked interface. (In order to expose statistics via JMX for example.)
引入可以使用@DeclareParents注解来定义。注解被使用定义匹配类型有一个新的父类()例如,给定一个接口UsageTracked和一个实现接口DefaultUsageTracked,下面的切面定义了所有服务接口的实现也实现了UsageTracked接口。(为了暴露统计根据JMX作为例子)
@Aspect
public class UsageTracking {
@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
@Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}
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 will implement the UsageTracked interface. Note that in the before advice of the above example, service beans can be directly used as implementations of the UsageTracked interface. If accessing a bean programmatically you would write the following:
实现的接口有注解属性的类型来决定。@DeclareParents属性值在AspectJ类型模式中:任何bean匹配类型将会实现UsageTracked接口。注意上面例子中的前置advice,服务bean可以直接被使用实现UsageTracked接口。如果编程访问一个bean你可以书写如下:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
11.2.6 Aspect instantiation models
切面实例化模型
[Note]
注意
(This is an advanced topic, so if you are just starting out with AOP you can safely skip it until later.)
(这是一个高级话题,如果你只是刚开始使用aop你可以跳过这一段内容)
By default there will be 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和pertypewthin目前还不支持)
A "perthis" aspect is declared by specifying a perthis clause in the @Aspect annotation. Let’s look at an example, and then we’ll explain how it works.
一个perthis方面定义通过perthis标准在@AspectJ注解中。让我们看一个例子并且我们会解释他是怎样工作的。
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {
private int someState;
@Before(com.xyz.myapp.SystemArchitecture.businessService())
public void recordServiceUsage() {
// ...
}
}
The effect of the 'perthis' clause is that one aspect instance will be created for each unique service object executing 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. Before the aspect instance is created, none of the advice within it executes. As soon as the aspect instance has been created, the advice declared within it will execute at matched join points, but only when the service object is the one this aspect is associated with. See the AspectJ programming guide for more information on per-clauses.
perthis策略的影响是一个方面实例将被创建对于每个服务object执行一个业务服务(每个独立的object绑定到this在连接点匹配切点表达式)。方面实例第一次被创建且一个方法被调用在服务object。方面在范围之外当服务object在范围之外时。在方面实例之前被创建,没有一个advice会被执行。很快方面的实例已经被创建,advice定义了他会执行在匹配连接点,但是只有当服务object是一个可以连接方面时。见AspectJ编程指南获取更多信息对于每个策略。
The 'pertarget' instantiation model works in exactly the same way as perthis, but creates one aspect instance for each unique target object at matched join points.
pertarget实例化模型和perthis的工作方式相同,但是创建一个方面实例对于每个独立的目标object对于匹配的连接点。
11.2.7 Example
例子
Now that you have seen how all the constituent parts work, let’s put them together to do something useful!
注意你已经知道所有的组成部分如何工作,让我们组合他们做一些有用的事情!
The execution of business services can sometimes fail due to concurrency issues (for example, deadlock loser). If the operation is retried, it is quite likely to succeed next time round. For business services where it is appropriate to retry in such conditions (idempotent operations that don’t need to go back to the user for conflict resolution), we’d like 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 via an aspect.
业务的执行有时候可以失败由于并发的原因(例如死锁)。如果重试操作,可能会在下次就会成功。对于业务逻辑当可以适当重试在一些业务条件(幂指操作不需要返回给用户对于冲突的环境),我们倾向于明显的重试操作来避免客户端看到PessimisticLockingFailureException异常。这药酒切断多个服务在服务层并且以后通过切面贯彻执行。
Because we want to retry the operation, we will need to use around advice so that we can call proceed multiple times. Here’s how the basic aspect implementation looks:
因为我们希望可以重试操作,我们将会需要使用环绕advice因此我们可以调用proceed多次。基本的方面实现如下:
@Aspect
public class ConcurrentOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
PessimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}
catch(PessimisticLockingFailureException ex) {
lockFailureException = ex;
}
} while(numAttempts <= this.maxRetries);
throw lockFailureException;
}
}
Note that the aspect implements the Ordered interface so 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 will both be configured by Spring. The main action happens in the doConcurrentOperation around advice. Notice that for the moment we’re applying the retry logic to all businessService()s. We try to proceed, and if we fail with an PessimisticLockingFailureException we simply try again unless we have exhausted all of our retry attempts.
注意到方面实现了Ordered接口因此我们可以设置方面的优先级高于业务的advice(我们希望刷新在我们重试的时候刷新业务)。最大重试次数和order属性将通过spring来配置。主要的操作在doConcurrentOperation的环绕advice中。注意我们重试的逻辑对于所有的业务服务。我们尝试处理并且如果我们失败并得到PessimisticLockingFailureException我们可以简单的尝试多次知道我们消耗掉所有的重试次数。
The corresponding Spring configuration is:
相关的spring的配置如下:
<aop:aspectj-autoproxy/>
<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
</bean>
To refine the aspect so that it only retries idempotent operations, we might define an Idempotent annotation:
为了重新定义方面因此我们只要重试幂指操作,我们可以定义幂指注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
and use the annotation to annotate the implementation of service operations. The change to the aspect to only retry idempotent operations simply involves refining the pointcut expression so that only @Idempotent operations match:
并且使用注解声明服务操作的实现。方面的改变对于只是重试幂指操作简单的调用改善切点表达式以方便@Idempotent操作的匹配:
@Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
...
}
11.3 Schema-based AOP support
基于schema的aop支持
If you prefer an XML-based format, then Spring also offers support for defining aspects using the new "aop" namespace tags. The exact same pointcut expressions and advice kinds are supported as when using the @AspectJ style, hence in this section we will focus on the new syntax and refer the reader to the discussion in the previous section (Section 11.2, “@AspectJ support”) for an understanding of writing pointcut expressions and the binding of advice parameters.
如果你倾向于基于xml的形式,spring也支持定义方面通过使用新的aop命名空间标签。相同的切点表达式和advice类型也支持当使用@AspectJ风格时,因此在这一节我们将会关注新的语法来引导读者来讨论之前的章节(11.2节,对@AspectJ的支持)用于理解书写切点表达式并绑定advice参数。
To use the aop namespace tags described in this section, you need to import the spring-aop schema as described in Chapter 41, XML Schema-based configuration. See Section 41.2.7, “the aop schema” for how to import the tags in the aop namespace.
为了使用定义在此节中的aop命名空间,你需要导入spring-aop的schema并且41章中进行讨论,基于xml的配置。见41.2.7节,aop的schema来了解如果导入标签在sop命名空间中。
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 these must be declared in that order).
在你的spring的配置中,所有的方面和advisor元素必须被包裹通过<aop:config>元素(你可以有多个该元素在一个应用的上下文配置中)。一个<aop:config>元素可以包含切点、advisor和方面元素(注意他们的声明必须按照一定的顺序)。
[Warning]
警告
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 are already using explicit auto-proxying via the use of BeanNameAutoProxyCreator or suchlike. The recommended usage pattern is to use either just the <aop:config> style, or just the AutoProxyCreator style.
<aop:config>风格的配置使得spring的自动代理机制变得复杂。这将可能导致问题(例如advice没有被定义)如果你已经使用了明确的自动代理通过使用BeanNameAutoProxyCreator或类似的。建议的使用模式是只是使用<aop:config>风格或只是使用AutoProxyCreator风格。
11.3.1 Declaring an aspect
定义一个方面
Using the schema support, an aspect is simply a regular Java object defined as a bean in your Spring application context. The state and behavior is captured in the fields and methods of the object, and the pointcut and advice information is captured in the XML.
使用schema的支持,一个方面可以是简单的java的object定义作为一个bean在你的spring应用上下文。状态和行为会被捕获在fields和object的方法中并且切点和advice信息被捕获在xml中。
An aspect is declared using the <aop:aspect> element, and the backing bean is referenced using the ref attribute:
一个方面定义使用了<aop:aspect>元素并且后续的bean引用了这个bean通过ref属性:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
The bean backing the aspect (" `aBean`" in this case) can of course be configured and dependency injected just like any other Spring bean.
bean支持aspect可以被配置并独立被注入就像其他的spring的bean。
11.3.2 Declaring a pointcut
定义一个切点
A named pointcut can be declared inside an <aop:config> element, enabling the pointcut definition to be shared across several aspects and advisors.
一个命名的切点可以被定义在<aop:config>元素中,允许切点定义被分享跨越多个方面和advisors。
A pointcut representing the execution of any business service in the service layer could be defined as follows:
一个切点代表业务服务的执行在服务层可以被定义如下:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
Note that the pointcut expression itself is using the same AspectJ pointcut expression language as described in Section 11.2, “@AspectJ support”. If you are using 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:
注意切点表达式本身使用了相同的AspectJ切点表达式语言在11.2节,@AspectJ的支持。如果你在使用基于schema的风格,你可以引用命名的切点定义在切点表达式的类型中。另一种方法定义上面的切点可以如下:
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.myapp.SystemArchitecture.businessService()"/>
</aop:config>
Assuming you have a SystemArchitecture aspect as described in the section called “Sharing common pointcut definitions”.
假设你有一个SystemArchitecture方面描述为“共享通用的切点定义”
Declaring a pointcut inside an aspect is very similar to declaring a top-level pointcut:
定义一个切点在方面是非常相似的和定义一个顶层切点:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
Much the same way in an @AspectJ aspect, pointcuts declared using the schema based definition style may collect join point context. For example, the following pointcut collects the 'this' object as the join point context and passes it to advice:
相同的方式在@AspectJ方面,切点定义使用基于schema的定义风格可以收集连接点上下文。例如,下面的切点收集了this的object作为连接点上下文并传递给advice:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
The advice must be declared to receive the collected join point context by including parameters of the matching names:
这个advice必须被定义来接收收集连接点上下文通过包括匹配名称参数。
public void monitor(Object service) {
...
}
When combining pointcut sub-expressions, '&&' is awkward within an XML document, and so the keywords 'and', 'or' and 'not' can be used in place of '&&', '||' and '!' respectively. For example, the previous pointcut may be better written as:
当组合切点子表达式,&&是不适合在xml文档中使用的,并且关键字and、or和not可以使用来代替&&、||、和!。例如,前面的切点表达式可以最好写成如下:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) **and** this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
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并且不会被使用作为命名的切点对于复合形式的切点。命名的切点支持在基于schema的定义风格比@AspectJ的风格要有更多的限制。
11.3.3 Declaring advice
定义advice
The same five advice kinds are supported as for the @AspectJ style, and they have exactly the same semantics.
五个advice类型支持在@AspectJ风格并且有相同的语法。
Before advice
前置的advice
Before advice runs before a matched method execution. It is declared inside an <aop:aspect> using the <aop:before> element.
前置的advice在匹配方法执行之前被调用。他声明在<aop:aspect>中使用<aop:before>元素。
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</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:
这里dataAccessOperation是一个切点定义的id在顶层级别。定义内置切点代替pointcut-ref属性在pointcut属性中:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
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 containing the advice. Before a data access operation is executed (a method execution join point matched by the pointcut expression), the "doAccessCheck" method on the aspect bean will be invoked.
方法属性定义了一个方法doAccessCheck用于支持advice的内容。这个方法必须用于bean引用通过方面元素包括在advice中。在数据访问操作之前被执行(一个方法的执行连接点匹配通过切点表达式),方面bean的doAccessCheck方法将会被调用。
After returning advice
在返回之后的advice
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. For example:
返回之后的advice执行当一个匹配方法执行已经结束。定义在<aop:aspect>中以相同的方式同前置advice。例如:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
Just as in the @AspectJ style, it is possible to get hold of the return value within the advice body. Use the returning attribute to specify the name of the parameter to which the return value should be passed:
就像@AspectJ风格,可以获得advice内容的返回值。使用返回属性定义参数的名字来传递返回值:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
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, the method signature may be declared as:
doAccessCheck方法必须定义一个参数名字为retVal。这个参数的类型约束匹配以同样的方式描述同@AfterReturning。例如,方法签名可以定义为:
public void doAccessCheck(Object retVal) {...
After throwing advice
在抛出异常后的advice
After throwing advice executes when a matched method execution exits by throwing an exception. It is declared inside an <aop:aspect> using the after-throwing element:
当匹配方法执行抛出一个异常则执行抛出异常后的advice。定义在<aop:aspect>中的after-throwing元素中:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
method="doRecoveryActions"/>
...
</aop:aspect>
Just as in the @AspectJ style, it is possible to get hold of the thrown exception within the advice body. Use the throwing attribute to specify the name of the parameter to which the exception should be passed:
和@AspectJ风格一样,可以捕获advice中抛出的异常。使用throwing属性来定义参数的名字当异常需要被传递时:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
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:
doRecoveryActions必须定义一个参数名字为dataAccessEx。这个参数的类型限制匹配以同样的方式对于@AfterThrowing。例如,方法的签名可以被定义如下:
public void doRecoveryActions(DataAccessException dataAccessEx) {...
After (finally) advice
在finally的advice之后
After (finally) advice runs however a matched method execution exits. It is declared using the after element:
当方法执行完成并退出,则finally的advice会执行。他使用after元素定义:
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>
Around advice
环绕advice
The final kind of advice is around advice. Around advice runs "around" a matched method execution. It has the opportunity to do work both before and after the method executes, and to determine when, how, and even if, the method actually gets to execute at all. Around advice is often used if you need to share state before and after a method execution in a thread-safe manner (starting and stopping a timer for example). Always use the least powerful form of advice that meets your requirements; don’t use around advice if simple before advice would do.
最后一种advice是环绕advice。环绕advice在方法的前后执行。可以选择在方法执行前后执行一些操作,并且决定什么时候、做什么即使方法正常执行。环绕advice经常用于你需要共享状态在方法执行前后在一个线程安全的情况(例如开始和结束时间)。建议使用最小的advice类型来符合你的需要,不要使用环绕advice如果你之前需要在之前做些什么。
Around advice is declared using the aop:around element. The first parameter of the advice method must be of type ProceedingJoinPoint. Within the body of the advice, calling proceed() on the ProceedingJoinPoint causes the underlying method to execute. The proceed method may also be calling passing in an Object[] - the values in the array will be used as the arguments to the method execution when it proceeds. See the section called “Around advice” for notes on calling proceed with an Object[].
环绕advice定义通过使用aop:around元素。第一个advice方法的参数必须是ProcceedingJoinPoint类型。在advice体中,调用proceed方法对于ProceedingJoinPoint实现方法的执行。执行方法也可以调用通过一个object,数组中的值可以被使用作为参数在方法的执行中。见章节“环绕advice”来了解处理object数组。
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>
The implementation of the doBasicProfiling advice would be exactly the same as in the @AspectJ example (minus the annotation of course):
doBasicProfiling的advice的实现是相同的在@AspectJ的例子中(不需要course的注解)
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
Advice parameters
advice参数
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 the section called “Advice parameters” for details. If you wish to explicitly specify argument names for the advice methods (not relying on the detection strategies previously described) then this is done using the arg-names attribute of the advice element, which is treated in the same manner to the "argNames" attribute in an advice annotation as described in the section called “Determining argument names”. For example:
基于schema的定义方式支持类型advice在相同的方法和@AspectJ支持的描述相同,通过名字匹配切点参数。见章节“advice的参数”获取细节。如果你希望明确的定义参数名对于advice方法(不依赖于之前描述的探查策略)则可以使用advice元素的arg-names属性,其处理方式和advice注解中的argNames属性是相同的,描述在章节“决定参数的名字”,例如:
<aop:before
pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
method="audit"
arg-names="auditable"/>
The arg-names attribute accepts a comma-delimited list of parameter names.
arg-names属性接受一个逗号分隔的参数名列表。
Find below a slightly more involved example of the XSD-based approach that illustrates some around advice used in conjunction with a number of strongly typed parameters.
发现下面的简单调用的例子基于XSD方式声明了一些环绕advice并且和一些强类型参数共同使用。
package x.y.service;
public interface FooService {
Foo getFoo(String fooName, int age);
}
public class DefaultFooService implements FooService {
public Foo getFoo(String name, int age) {
return new Foo(name, age);
}
}
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:
接下来是方面。注意profile方法接受一些强类型的参数,第一参数是连接点用于处理方法调用:这个参数用于声明profile作为环绕advice来使用:
package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
public class SimpleProfiler {
public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
try {
clock.start(call.toShortString());
return call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
}
}
Finally, here is the XML configuration that is required to effect the execution of the above advice for a particular join point:
最后,这里是xml的配置被要求来影响上面对于特定连接点advice的执行:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the object that will be proxied by Spring's AOP infrastructure -->
<!-- 这将会被spring的aop来实现代理 -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- this is the actual advice itself -->
<!-- 这是实际的advice本身 -->
<bean id="profiler" class="x.y.SimpleProfiler"/>
<aop:config>
<aop:aspect ref="profiler">
<aop:pointcut id="theExecutionOfSomeFooServiceMethod"
expression="execution(* x.y.service.FooService.getFoo(String,int))
and args(name, age)"/>
<aop:around pointcut-ref="theExecutionOfSomeFooServiceMethod"
method="profile"/>
</aop:aspect>
</aop:config>
</beans>
If we had the following driver script, we would get output something like this on standard output:
如果我们有下面的脚本,我们可以获得输出一些内容像在标准输出上:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.FooService;
public final class Boot {
public static void main(final String[] args) throws Exception {
BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
FooService foo = (FooService) ctx.getBean("fooService");
foo.getFoo("Pengo", 12);
}
}
StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0
-----------------------------------------
ms % Task name
-----------------------------------------
00000 ? execution(getFoo)
Advice ordering
advice的顺序
When multiple advice needs to execute at the same join point (executing method) the ordering rules are as described in the section called “Advice ordering”. The precedence between aspects is determined by either adding the Order annotation to the bean backing the aspect or by having the bean implement the Ordered interface.
当多个advice需要执行在相同的连接点时(执行方法)顺序的规则被描述在章节“advice的顺序”。方面之间的优先级有天价凳Order注解来决定对于bean在方面之后或通过bean实现Oredered接口来决定。
11.3.4 Introductions
说明
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中作为内部类型声明而知)允许一个方面来决定advice的object实现给定的接口并提供接口的实现为了这些object。
An introduction is made using the aop:declare-parents element inside an aop:aspect This element is used to declare that matching types have a new parent (hence the name). For example, given an interface UsageTracked, and an implementation of that interface DefaultUsageTracked, the following aspect declares that all implementors of service interfaces also implement the UsageTracked interface. (In order to expose statistics via JMX for example.)
说明通过aop:declare-parents元素在aop:aspect元素中定义。这个元素被使用定义匹配类型有一个新的parent。例如,给定接口UsageTracked接口并且一个接口的实现是DefaultUsageTracked,下面的方面定义所有的服务实现也实现了UsageTracked接口。(为了暴露状态通过JMX作为例子)。
<aop:aspect id="usageTrackerAspect" ref="usageTracking">
<aop:declare-parents
types-matching="com.xzy.myapp.service.*+"
implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
<aop:before
pointcut="com.xyz.myapp.SystemArchitecture.businessService()
and this(usageTracked)"
method="recordUsage"/>
</aop:aspect>
The class backing the usageTracking bean would contain the method:
这个类后面的usageTracking中应该包含方法:
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
The interface to be implemented is determined by implement-interface attribute. The value of the types-matching attribute is an AspectJ type pattern :- any bean of a matching type will implement the UsageTracked interface. Note that in the before advice of the above example, service beans can be directly used as implementations of the UsageTracked interface. If accessing a bean programmatically you would write the following:
接口实现决定通过实现接口属性。类型匹配属性是一个AspectJ类型模式:任何bean匹配类型将实现UsageTracked接口。注意在上面例子中的前置advice,服务bean可以被直接用于作为UsageTracked接口的实现。如果编程访问一个bean你可以这样写:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
11.3.5 Aspect instantiation models
方面实例化模型
The only supported instantiation model for schema-defined aspects is the singleton model. Other instantiation models may be supported in future releases.
对于定义schema的方面的实例化只支持单例的模式。其他的实例化模式将会在未来被支持。
11.3.6 Advisors
指导者
The concept of "advisors" is brought forward from the AOP support defined in Spring 1.2 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. The advice itself is represented by a bean, and must implement one of the advice interfaces described in Section 12.3.2, “Advice types in Spring”. Advisors can take advantage of AspectJ pointcut expressions though.
advisors的内容被提重从aop支持中定义在spring1.2中并且没有直接的等价替换在AspectJ。一个advisor类似于一个小型自包含的方面有一个单一的advice。advice本身通过一个bean来代表,并且必须实现一个advice接口描述在章节12.3.2中,“spring中的advice类型”。advisor利用了AspectJ切点表达式。
Spring supports the advisor concept with the <aop:advisor> element. You will most commonly see it used in conjunction with transactional advice, which also has its own namespace support in Spring. Here’s how it looks:
spring支持advisor内容通过使用<aop:advisor>元素。你将通常发现混合使用传统的advice,将有自己的命名空间支持在spring中。下面展示如何使用:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
As well as the pointcut-ref attribute used in the above example, you can also use the pointcut attribute to define a pointcut expression inline.
和point-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.
定义advisor的优先级因此advice可以按顺序参加,使用order属性来定义advisor的Ordered的值。
11.3.7 Example
例子
Let’s see how the concurrent locking failure retry example from Section 11.2.7, “Example” looks when rewritten using the schema support.
让我们看同步锁失败重试的例子在11.2.7节,“例子”当重写schema支持时。
The execution of business services can sometimes fail due to concurrency issues (for example, deadlock loser). If the operation is retried, it is quite likely it will succeed next time round. For business services where it is appropriate to retry in such conditions (idempotent operations that don’t need to go back to the user for conflict resolution), we’d like 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 via an aspect.
业务服务的执行有时候失败由于同步问题(例如,死锁)。如果操作被重试了,可能在下次就会成功。对于业务服务当在适当的条件下重试(幂等操作不需要返回给用户为了冲突解决),我们倾向于透明的重试操作来避免客户端看到一个PessimisticLockingFailureException异常。这就要求清晰的切断多个业务服务在服务层,并且是一个方面的理想实现。
Because we want to retry the operation, we’ll need to use around advice so that we can call proceed multiple times. Here’s how the basic aspect implementation looks (it’s just a regular Java class using the schema support):
因为我们希望重试操作,我们将会需要环绕advice因此我们可以调用proceed多次。因此基本方面的实现如何工作(只是普通的java类使用schema支持):
public class ConcurrentOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
PessimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}
catch(PessimisticLockingFailureException ex) {
lockFailureException = ex;
}
} while(numAttempts <= this.maxRetries);
throw lockFailureException;
}
}
Note that the aspect implements the Ordered interface so 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 will both be configured by Spring. The main action happens in the doConcurrentOperation around advice method. We try to proceed, and if we fail with a PessimisticLockingFailureException we simply try again unless we have exhausted all of our retry attempts.
注意方面实现Ordered接口因此我们可以设置方面的优先级高于事务advice(我们希望一个新鲜的事务当我们每次重试的时候)。最大重试次数和顺序属性将通过spring来配置。主要的操作发生在doConcurrentOperation的环绕advice方法中。我们尝试处理,如果我们失败并返回一个PessimisticLockingFailureException异常我们可以简单的重试除非我们耗尽了所有的重试次数。
[Note]
注意
This class is identical to the one used in the @AspectJ example, but with the annotations removed.
这个类于一个使用在@AspectJ例子中的相同,但是注解被移除了。
The corresponding Spring configuration is:
相关的spring配置是:
<aop:config>
<aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:around
pointcut-ref="idempotentOperation"
method="doConcurrentOperation"/>
</aop:aspect>
</aop:config>
<bean id="concurrentOperationExecutor"
class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
</bean>
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 only retries genuinely idempotent operations, by introducing an Idempotent annotation:
注意我们假设的时间是幂等的。如果情况不是我们定义的因此只是重试的幂等时间的操作,通过引入@Idempotent注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
and using the annotation to annotate the implementation of service operations. The change to the aspect to retry only idempotent operations simply involves refining the pointcut expression so that only @Idempotent operations match:
并且使用注解来注解服务操作的实现。方面的改变用于重试只是幂等的操作简单的重新改善切点表达式因此只有@Idempotent操作可以匹配:
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..)) and
@annotation(com.xyz.myapp.service.Idempotent)"/>
11.4 Choosing which AOP declaration style to use
选择使用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, @AspectJ annotation style, or the Spring XML style? These decisions are influenced by a number of factors including application requirements, development tools, and team familiarity with AOP.
当你决定一个方面时最好的方式来实现给定的需求,那如何决定使用Spring的AOP还是AspectJ,并且是AspectJ语言风格还是@AspectJ注解的风格,或者是spring的xml风格?这些决定被一系列因素影响包括应用的需要、开发工具和团队对AOP的熟悉程度。
11.4.1 Spring AOP or full AspectJ?
使用spring的aop还是全面使用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. If you only need to advise the execution of operations on Spring beans, then Spring AOP is the right choice. If you need to advise objects not managed by the Spring container (such as domain objects typically), then you will need to use AspectJ. You will 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).
使用最简单的内容让他工作。spring的aop和全面使用AspectJ是类似的并且不需要介绍AspectJ的编译器、包装器给你的开发和构建程序。如果你只是需要建议处理spring的bean的操作流程,spring的aop是一个很不错的选择。如果你需要处理object而不需要被spring容器管理(例如领域对象),你将会需要使用AspectJ。你将也可以使用AspectJ如果你希望介入连接点而不是简单的方法(例如,获得属性或设置连接点等等)。
When using AspectJ, you have the choice of the AspectJ language syntax (also known as the "code style") or the @AspectJ annotation style. Clearly, if you are not using Java 5+ then the choice has been made for you… use the code style. If aspects play a large role in your design, and you are able to use the AspectJ Development Tools (AJDT) plugin for Eclipse, then the AspectJ language syntax is the preferred option: it is cleaner and simpler because the language was purposefully designed for writing aspects. If you are not using Eclipse, or have only a few aspects that do not play a major role in your application, then you may want to consider using the @AspectJ style and sticking with a regular Java compilation in your IDE, and adding an aspect weaving phase to your build script.
当使用AspectJ,你可以选择AspectJ的语法(也就是代码格式)或@AspectJ的注解风格。当然,如果你没有使用Java5以上的版本则你只能使用代码的形式。如果AspectJ在你的设计中起到很大的作用并且你可以使用AspectJ集成到Eclipse中的工具AJDT的话AspectJ的语法回事较好的选择,他会干净简单因为语言的设计目的就是为了写方面。如果你没有使用Eclipse,或者有一些方面没有起到很重要的作用在你的应用中,你可以考虑使用@AspectJ的注解风格和正常的Java编译绑定在你的IDE中并且添加方面和你的构建脚本组合。
11.4.2 @AspectJ or XML for Spring AOP?
@AspectJ注解或xml对于spring的aop
If you have chosen to use Spring AOP, then you have a choice of @AspectJ or XML style. There are various tradeoffs to consider.
如果你选择使用spring的aop,你可以选择@AspectJ的注解模式或xml的风格。下面有一些权衡你可以考虑。
The XML style will be most familiar to existing Spring users and it is backed by genuine POJOs. When using AOP as a tool to configure enterprise services then XML can be a good choice (a good test is whether you consider the pointcut expression to be a part of your configuration you might want to change independently). With the XML style arguably it is clearer from your configuration what aspects are present in the system.
xml的风格将会更加熟悉对于已有的spring的用户并且和通用的pojo绑定。当你使用aop作为一个工具来配置企业服务而不是xml可以是一个很好的选择(一个好的测试是当你考虑切点表达式对于一部分你的配置你可以独立的进行修正)。使用xml风格可能会使得你的配置更加清晰当切面应用在你的应用中时。
The XML style has two disadvantages. Firstly it does not fully encapsulate the implementation of the requirement it addresses in a single place. The DRY principle says that there should be a single, unambiguous, authoritative representation of any piece of knowledge within a system. 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 using the @AspectJ style there is a single module - the aspect - in which this information is encapsulated. 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. For example, in the @AspectJ style you can write something like:
xml风格有两个缺点。首先他没有全面压缩需求的实现并且在同一个位置体现。DRY原则说应该是单一的、没有歧义的、命令式对于一个系统的每个部分的内容。当使用xml的风格,对知识的需要取决于对于bean类的定义,并且xml在配置文件中。当使用@AspectJ的风格就是单一的模式,方面,是封装了这些信息的。第二,xml风格有一些限制在表达方面和@AspectJ风格相比较:只有单例的方面实例化模型是支持的并且不支持核名定义在xml中的命名切点,在@AspectJ风格中你可以书写如下:
@Pointcut(execution(* get*()))
public void propertyAccess() {}
@Pointcut(execution(org.xyz.Account+ *(..))
public void operationReturningAnAccount() {}
@Pointcut(propertyAccess() && operationReturningAnAccount())
public void accountPropertyAccess() {}
In the XML style I can declare the first two pointcuts:
在xml风格中我可以声明前两个切点:
<aop:pointcut id="propertyAccess"
expression="execution(* get*())"/>
<aop:pointcut id="operationReturningAnAccount"
expression="execution(org.xyz.Account+ *(..))"/>
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 the @AspectJ aspects can be understood (and thus consumed) both by Spring AOP and by AspectJ - so if you later decide you need the capabilities of AspectJ to implement additional requirements then it is very easy to migrate to an AspectJ-based approach. On balance the Spring team prefer the @AspectJ style whenever you have aspects that do more than simple "configuration" of enterprise services.
@AspectJ风格支持额外的实例化模式和富切点组合。对于保持方面作为一个模块化单元是存在优势的。他对于@AspectJ的方面也存在优势可以理解(并且充满)被spring的aop和AspectJ,因此如果你以后做决定使用AspectJ来实现额外的需要则迁移也是很简单的。总而言之,spring小组倾向于@AspectJ风格无论你有方面来做企业服务的简单配置。
11.5 Mixing aspect types
混合方面的类型
It is perfectly possible to mix @AspectJ style aspects using the autoproxying support, schema-defined <aop:aspect> aspects, <aop:advisor> declared advisors and even proxies and interceptors defined using the Spring 1.2 style in the same configuration. All of these are implemented using the same underlying support mechanism and will co-exist without any difficulty.
可以混合@AspectJ风格的方面使用自动代理支持,定义schema的<aop:aspect>方面、<aop:advisor>定义advisor和代理、拦截器使用spring1.2的风格在相同的配置中。所有实现使用相同的底层支持策略并且可以共存而不存在排斥。
11.6 Proxying mechanisms
代理策略
Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred whenever you have a choice).
spring的aop使用JDK的动态代理或者cglib创建代理对于给定的object。(JDK动态代理是更好的选择)
If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used. All of the interfaces implemented by the target type will be proxied. If the target object does not implement any interfaces then a CGLIB proxy will be created.
如果目标object被代理则需要实现一个接口比JDK动态代理中。所有接口实现通过目标类型将会被代理。如果目标object没有实现任何接口则将会创建cglib代理。
If you want to force the use of CGLIB proxying (for example, to proxy every method defined for the target object, not just those implemented by its interfaces) you can do so. However, there are some issues to consider:
如果你希望强制使用cglib代理(例如,代理每一个方法定义在目标object中,并不只是实现他的接口)你可以这样做。然而,有一些问题需要考虑:
final methods cannot be advised, as they cannot be overridden.
最终方法将不会被考虑,并且不能被覆盖。
As of Spring 3.2, it is no longer necessary to add CGLIB to your project classpath, as CGLIB classes are repackaged under org.springframework and included directly in the spring-core JAR. This means that CGLIB-based proxy support 'just works' in the same way that JDK dynamic proxies always have.
作为spring3.2,不需要添加cglib到你的工程的classpath中,作为cglib类被重新打包在org.springframework中并且包括独立在spring-core的jar中。他意味着基于cglib的代理支持以同样的方式工作且jdk动态代理也支持。
As of Spring 4.0, the constructor of your proxied object will NOT be called twice anymore since the CGLIB proxy instance will be created via 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.
在spring4.0中,你代理object的构造器将不会被调用两次自从cglib代理实例将会通过Objenesis来创建开始。只有你的jvm不允许构造器绕路,你可以看到双重调用和相应的debug日志在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:
为了强制使用cglib代理设置proxy-target-class属性,属于<aop:config>元素:
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>
To force CGLIB proxying when using the @AspectJ autoproxy support, set the 'proxy-target-class' attribute of the <aop:aspectj-autoproxy> element to true:
当使用@AspectJ自动代理支持时强制cglib代理,设置<aop:aspectj-autoproxy>元素的proxy-target-class属性:
<aop:aspectj-autoproxy proxy-target-class="true"/>
[Note]
注意
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 will force the use of CGLIB proxies for all three of them.
更加清晰的,使用proxy-target-class="true"在<tx:annotation-driven/>、<aop:aspectj-autoproxy/>或<aop:config/>元素中将强制使用cglib代理对于所有。
11.6.1 Understanding AOP proxies
理解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基于aop方面通过提供的Spring框架。
Consider first the scenario where you have a plain-vanilla, un-proxied, nothing-special-about-it, straight object reference, as illustrated by the following code snippet.
考虑第一个场景当你有一个单纯的功能、没有被代理、没有任何特殊、直接的object的引用,作为说明看下面的代码片段。
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
If you invoke a method on an object reference, the method is invoked directly on that object reference, as can be seen below.
如果你的调用一个方法在一个object的引用,方面被直接调用在object的引用中,可以参考如下:
public class Main {
public static void main(String[] args) {
Pojo pojo = new SimplePojo();
// this is a direct method call on the 'pojo' reference
pojo.foo();
}
}
Things change slightly when the reference that client code has is a proxy. Consider the following diagram and code snippet.
如果有一个代理则事情会发生改变。考虑下面的图表和代码片段。
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
The key thing to understand here is that the client code inside the main(..) of the Main class has a reference to the proxy. This means that method calls on that object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. 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 execute.
关键来理解这里是客户端代码在main中引用了代理。这意味着方法调用对于object引用将调用代理,并且这样代理可以委托可拦截器相关的特定方法调用。然而,一个调用最终会到达目标object,在这个例子中的SimplePojo引用,任何方法调用都会传递给他,例如this.bar()或this.foo(),都会调用这个引用,并且不是代理。这有重要的意义。他意味着自我调用不是在advice中的结果,连接方法的调用有机会改变执行。
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. For sure, this does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and I am almost reticent to point it out precisely because it is so horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing this:
好,因此我们怎么做?最好的方法(最好使用松散的架构)可以重构你的代码因此自我调用不会发生。肯定的是,有必要你工作的一部分但是他是最好的,最少被侵入的方式。下一个方法是绝对惊人的并且我不会讲述因为他是如此的惊人。你可以绑定你的逻辑在你的类中或spring的aop中如下:
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
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:
这将你的代码和spring的aop完全连接在一起,并且使得类本身意识到aop上下文被使用,是aop文件中的一部分。他也要求一些额外配置当代理被创建时:
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.adddInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
factory.setExposeProxy(true);
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
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框架。
11.7 Programmatic creation of @AspectJ Proxies
编程创建@AspectJ代理
In addition to declaring aspects in your configuration using either <aop:config> or <aop:aspectj-autoproxy>, it is also possible programmatically to 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 using @AspectJ aspects.
此外定义方面在你的配置中使用<aop:config>或<aop:aspectj-autoproxy>,可以编程创建代理对于目标object。对于spring的aop的全部细节api,见下一章节。这里我们希望关注于自动创建代理通过使用@AspectJ方面。
The class org.springframework.aop.aspectj.annotation.AspectJProxyFactory can be used to create a proxy for a target object that is advised by one or more @AspectJ aspects. Basic usage for this class is very simple, as illustrated below. See the javadocs for full information.
类org.springframework.aop.aspectj.annotation.AspectJProxyFactory可以被使用创建一个代理对于目标object被一个或多个@AspectJ方面管理。基本的使用对于这个类是非常简单的,描述如下。见javadocs来查看全面的信息。
// create a factory that can generate a proxy for the given target object
// 创建一个工厂可以根据给定的目标object生成代理
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
// 添加一个方面,类必须是@AspectJ方面,你可以根据需要在不同的方面进行调用
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
// 你也可以存在方面实例,object的类型支持必须是@AspectJ方面。
factory.addAspect(usageTracker);
// now get the proxy object...
// 现在获得代理object
MyInterfaceType proxy = factory.getProxy();
11.8 Using AspectJ with Spring applications
在spring应用中使用AspectJ
Everything we’ve covered so far in this chapter is pure Spring AOP. In this section, we’re going to look at how you can use the AspectJ compiler/weaver instead of, or in addition to, Spring AOP if your needs go beyond the facilities offered by Spring AOP alone.
我们讨论每一件事情在这一节中是纯spring的aop。在这节中,我们了解如何使用AspectJ编译、包装器作为代理或作为额外的辅助,spring的aop如果你需要由spring的aop单独提供的内容。
Spring ships with a small AspectJ aspect library, which is available standalone in your distribution as spring-aspects.jar; you’ll need to add this to your classpath in order to use the aspects in it. Section 11.8.1, “Using AspectJ to dependency inject domain objects with Spring” and Section 11.8.2, “Other Spring aspects for AspectJ” discuss the content of this library and how you can use it. Section 11.8.3, “Configuring AspectJ aspects using Spring IoC” discusses how to dependency inject AspectJ aspects that are woven using the AspectJ compiler. Finally, Section 11.8.4, “Load-time weaving with AspectJ in the Spring Framework” provides an introduction to load-time weaving for Spring applications using AspectJ.
spring和小的AspectJ方面库在一起,可以在spring-aspects.jar中独立使用,你将需要添加到你的classpath中确保可以使用其中的方面。11.8.1节中,“使用AspectJ独立注入领域object通过spring”和11.8.2节中“其他spring方面对于AspectJ”讨论这个库并且如何你可以使用他。11.8.3节中“配置AspectJ方面使用Spring的ioc”讨论如何独立注入AspectJ方面通过AspectJ的编译器。最后,11.8.4节中,“加载时包装AspectJ在spring框架”提供一个介绍对于加载时包装spring应用通过使用AspectJ。
11.8.1 Using AspectJ to dependency inject domain objects with Spring
使用spring中的AspectJ独立注入领域对象
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 containing the configuration to be applied. The spring-aspects.jar contains an annotation-driven aspect that exploits this capability to allow dependency injection of any object. The support is intended to be used for objects created outside of the control of any container. Domain objects often fall into this category because they are often created programmatically using the new operator, or by an ORM tool as a result of a database query.
spring容器实例化并且配置在你应用上下文中定义的bean。也可以调用bean工厂来配置已经存在的object给定bean定义的名字包括已经被配置的。spring-aspects.jar包含注解形式的方面利用这样的功能允许独立注入到你的object中。规定用于object创建在容器控制之外。领域object经常区分类别因为经常通过编程来创建使用新的operator或一个ORM工具作为数据库查询的结果。
The @Configurable annotation marks a class as eligible for Spring-driven configuration. In the simplest case it can be used just as a marker annotation:
@Configurable注解标记一个类可以作为spring的配置。在最简单的例子中,他可以被使用只是作为一个标记注解:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable
public class Account {
// ...
}
When used as a marker interface in this way, Spring will configure new instances of the annotated type ( Account in this case) 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 simply to omit the id attribute:
当使用作为一个标记接口以这种方式,spring将会配置新的实例关于注解类型(在例子中是Account)使用bean的定义(通常是原型范围的)有相同的名字和全限定名(com.xyz.myapp.domain.Account)。自此默认bean的名字是全限定名的类型,一个方便的方式巨鼎原型定义可以忽略id属性:
<bean class="com.xyz.myapp.domain.Account" scope="prototype">
<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>
If you want to explicitly specify the name of the prototype bean definition to use, you can do so directly in the annotation:
如果你希望定义原型bean定义的名字来使用,你可以直接在注解中定义:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("account")
public class Account {
// ...
}
Spring will now look for a bean definition named "account" and use that as the definition to configure new Account instances.
spring将会寻找bean的定义名为account并且使用他作为定义来配置新的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: specify either @Configurable(autowire=Autowire.BY_TYPE) or @Configurable(autowire=Autowire.BY_NAME for autowiring by type or by name respectively. As an alternative, as of Spring 2.5 it is preferable to specify explicit, annotation-driven dependency injection for your @Configurable beans by using @Autowired or @Inject at the field or method level (see Section 7.9, “Annotation-based container configuration” for further details).
你也可以使用自动配置来避免定义bean的定义。为了使得spring应用自动配置需要在@Configurable注解中使用autowire属性:@Configurable(autowire=Autowire.BY_TYPE)或@Configurable(autowire=Autowire.BY_NAME用于设置使用类型或名字记性自动包装。作为替代,在spring2.5中最好定义明确的基于注解独立注入你的@Configurable的bean通过使用@Autowired或@Inject在属性或方法级别(见7.9章节,“基于注解的容器配置”来查看更多信息)
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, then Spring will validate after configuration that all properties (which are not primitives or collections) have been set.
最后你允许spring独立检查object引用在新创建和配置的object通过使用dependecyCheck属性(例如:@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true))。如果这个属性设置为true,则spring将会校验所有的属性在配置之后(不是原始类型或集合)。
Using the annotation on its own does nothing of course. 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 (e.g., objects instantiated with the new operator) as well as to Serializable objects that are undergoing deserialization (e.g., via readResolve()).
使用注解并没有什么用。spring-aspects.jar中AnnotationBeanConfigurerAspect是一个存在的注解。因此当方面says“在一个新的类型的object初始化返回并使用了@Confi注解,配置新创建的object使用spring和注解的属性相连”。在这个上下文中,初始化引用的新的实例object(例如,object实例化使用新的operator)可以被序列化被反序列化(例如,通过readResolve())。
[Note]
注意
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' will be fine…? in this context, 'after initialization' means that the dependencies will be injected after the object has been constructed - this means that the dependencies will not be available for use in the constructor bodies of the class. If you want the dependencies to be injected before the constructor bodies execute, and thus be available for use in the body of the constructors, then you need to define this on the @Configurable declaration like so:
其中一个关键的短语在上面的段落中是“本质上”。对于大多数的情况,准确的语意'after returning from the initialization of a new object'在这个上下文中,‘after initialization’意味着独立被注入在object被创建之后--这意味着独立将不会被使用在类的构造器中。如果你希望独立注入在构造器体之前执行并且可以在构造器体中可用,你需要定义在@Configurable注解中定义如下:
@Configurable(preConstruction=true)
You can find out more information about the language semantics of the various pointcut types in AspectJ in this appendix of the AspectJ Programming Guide.
你会发现更多的信息关于语言有关不同的切点类型在AspectJ在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 Section 11.8.4, “Load-time weaving with AspectJ in the Spring Framework”). The AnnotationBeanConfigurerAspect itself needs configuring by Spring (in order to obtain a reference to the bean factory that is to be used to configure new objects). If you are using Java based configuration simply add @EnableSpringConfigured to any @Configuration class.
为了让此工作注解类型必须通过AspectJ来包裹--你也可以使用一个构建时Ant或Maven任务来做这些(见AspectJ开发环境指南的例子)或加载时包裹(见11.8.4节,“加载时在spring框架中使用AspectJ来实现包裹”)。AnnotationBeanConfigurerAspect本身需要通过spring来配置(为了获得bean工厂的引用可以用于配置新的object)。如果你使用基于java的配置简单的添加@EnableSpringConfigured给任何@Configuration类。
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
If you prefer XML based configuration, the Spring context namespace defines a convenient context:spring-configured element:
如果你倾向于基于xml的配置,spring上下文的命名空间定义一个方便的context:spring-configured元素:
<context:spring-configured/>
Instances of @Configurable objects created before the aspect has been configured will result in a message being issued to the debug log and no configuration of the object taking place. An example might be a bean in the Spring configuration that creates domain objects when it is initialized by Spring. In this case you can use the "depends-on" bean attribute to manually specify that the bean depends on the configuration aspect.
使用@Configurable的object实例创建在方面之前被配置将会影响将会记录在调试日志中并且不会被object的配置取代。一个例子可以是spring配置中的bean创建领域object当他被spring初始化时。在这种情况,你可以使用“depends-on”的bean属性来手动定义bean依赖的被配置的方面。
<bean id="myService"
class="com.xzy.myapp.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
[Note]
注意
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 which are registered as regular Spring beans with the container: You would get double initialization otherwise, once through the container and once through the aspect.
不要激活@Configurable进程通过bean的配置方面除非你真的在运行时需要这么做。特殊情况下,保证你不是用@Configurable在bean的类当被注册为普通的spring的bean在容器中:你可以获得两倍的初始化,一个在容器中另一个在方面中。
Unit testing @Configurable objects
单元测试@Configurable的object
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 then the annotation has no affect during unit testing, and you can simply set mock or stub property references in the object under test and proceed as normal. If @Configurable types have been woven by AspectJ then you can still unit test outside of the container as normal, but you will see a warning message each time that you construct an @Configurable object indicating that it has not been configured by Spring.
一个支持@Configurable的目标是允许独立单元测试关于领域object而不需要要硬编码调用。如果@Configurable类型没有被AspectJ处理则注解不会影响在单元测试期间并且你可以简单的设置模拟或根属性引用在object中在测试和执行中。如果@Configurable类型已经被AspectJ处理你也可以单元测试在容器之外,但是你每次将看到警告的信息当你创建@Configurable的object按时他没有被spring配置。
Working with multiple application contexts
多个应用上下文一起工作
The AnnotationBeanConfigurerAspect 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, that is to say 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.
AnnotationBeanConfigurerAspect被用于实现@Configurable支持时一个AspectJ的单例方面。单例方面的范围是和静态成员相同的,也就是说一个方面实例定义在每个类加载中。这意味着如果你定义多个应用上下文使用相同的类加载器结构你需要考虑在哪里定义@EnableSpringConfigured的bean并且在classpath中放置spring-aspects.jar。
Consider a typical Spring web-app configuration with a shared parent application context defining common business services and everything needed to support them, and one child application context per servlet containing definitions particular to that servlet. All of these contexts will co-exist within the same classloader hierarchy, and so the AnnotationBeanConfigurerAspect can only hold a reference to one of them. In this case we recommend defining the @EnableSpringConfigured bean in the shared (parent) application context: this defines the services that you are likely to want to inject into domain objects. A consequence is that you cannot configure domain objects with references to beans defined in the child (servlet-specific) contexts using the @Configurable mechanism (probably not something you want to do anyway!).
考虑一个典型的spring的web应用配置有一个共同的父应用上下文定义相同的业务服务和每一需要支持这些的内容,一个子应用的每个servlet包含特定的定义对于servlet。所有这些上下文将一起存在在相同的类加载器结构中并且AnnotationBeanConfigurerAspect可以有其中的一个引用。在这种情况下我们建议定义@EnableSpringConfigured的bean在共享的父应用上下文,这定义服务你可能需要注入到领域object中。一个结论你不能配置领域object通过应用bean的定义在子(特定servlet)上下文使用@Configurable策略(可能你不会做什么)
When deploying multiple web-apps within the same container, ensure that each web-application loads the types in spring-aspects.jar using its own classloader (for example, by placing spring-aspects.jar in 'WEB-INF/lib'). If spring-aspects.jar is only added to the container wide classpath (and hence loaded by the shared parent classloader), all web applications will share the same aspect instance which is probably not what you want.
当部署多个web应用在相同的容器中,保证每个web应用加载类型的spring-aspects.jar在他们自己的类加载器中(例如,通过替换在WEB-INF/lib下的spring-aspects.jar)。如果spring-aspects.jar只田间到了容器公共的classpath中(因此被父类加载器共享),所有的web应用将共享相同的方面实例而不是希望的。
11.8.2 Other Spring aspects for AspectJ
对于AspectJ的器spring的方面
In addition to the @Configurable aspect, spring-aspects.jar contains an AspectJ aspect that can be used 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方面可以被用于处理spring的业务管理对于类型和方法使用了@Transactional注解。这对用户来说是主要的尝试来使用spring框架的事务支持在spring容器之外。
The aspect that interprets @Transactional annotations is the AnnotationTransactionAspect. When using this aspect, you must annotate the implementation class (and/or methods within that class), not the interface (if any) that the class implements. AspectJ follows Java’s rule that annotations on interfaces are not inherited.
方面解释@Transactional注解是AnnotationTransactionAspect。当使用这个方面时,你必须注解实现类(and/or方法对于类),不是任何类实现的接口。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. Annotating non-public methods directly is the only way to get transaction demarcation for the execution of such methods.
一个方法上的@Transactional注解在类中覆盖默认的事务语义根据给定的类注解(如果存在)。任何可见方法可以被注解,包括私有方法。直接注解非公有方法是唯一的方式获得事务划分对于某些执行的方法。
[Tip]
提示
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框架4.2,spring-aspects.jar提供一个相似的方面提供了一些特性对于标准的java.transaction.Transactional注解。检查JtaAnnotationTransactionAspect获得更多信息。
For AspectJ programmers that want to use the Spring configuration and transaction management support but don’t 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 using prototype bean definitions that match the fully-qualified class names:
对于AspectJ的编程人员希望使用spring的配置和事务管理支持但是不希望或不能使用注解,spring-aspects.jar也包含抽象的方面你可以扩展提供你自己的切点定义。见AbstractBeanConfigurerAspect和AbstractTransactionAspect的源代码获得方面的更多信息。作为一个例子,下面的引用展示了你应当如何书写一个方面来配置所有的object实例定义在领域模型使用原型bean定义匹配全限定类名:
public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {
public DomainObjectConfiguration() {
setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
}
// the creation of a new bean (any object in the domain model)
protected pointcut beanCreation(Object beanInstance) :
initialization(new(..)) &&
SystemArchitecture.inDomainModel() &&
this(beanInstance);
}
11.8.3 Configuring AspectJ aspects using Spring IoC
使用spring的ioc配置AspectJ的方面
When using AspectJ aspects with Spring applications, it is natural to both want and expect to be able to configure such aspects using Spring. The AspectJ runtime itself is responsible for aspect creation, and the means of configuring the AspectJ created aspects via Spring depends on the AspectJ instantiation model (the per-xxx clause) used by the aspect.
当在spring应用中使用AspectJ的方面时,自然希望可以通过spring来配置这样的方面。AspectJ运行时本身代表方面的创建,并且配置AspectJ的方法创建方面通过spring依赖于AspectJ实例模型通过使用方面。
The majority of AspectJ aspects are singleton aspects. Configuration of these aspects is very easy: simply create a bean definition referencing the aspect type as normal, and include the bean attribute 'factory-method="aspectOf"'. This ensures that Spring obtains the aspect instance by asking AspectJ for it rather than trying to create an instance itself. For example:
主要的AspectJ的方面时单例方面。配置这些方面时非常简单的:简单创建一个bean定义引用方面类型,并且包含bean属性'factory-method="aspectOf"'。这允许spring获得方面实例通过询问AspectJ而不是尝试创建一个实例本身。例如:
<bean id="profiler" class="com.xyz.profiler.Profiler"
factory-method="aspectOf">
<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
Non-singleton 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定义并且使用@Configurable支持在spring-aspects.jar中来配置方面实例可以在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 using Spring, then you will need to tell the Spring AOP @AspectJ autoproxying support which exact subset of the @AspectJ aspects defined in the configuration should be used for autoproxying. 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 will be used for Spring AOP autoproxy configuration:
如果你有一些@AspectJ方面你希望通过AspectJ来处理(例如,使用加载时处理对于领域模型类型)和其他@AspectJ方面你希望通过spring的aop来使用,并且这个方面使用spring来配置,你将会需要告诉spring的aop有关@AspectJ自动代理的支持要求@AspectJ方面的子集在配置中将用于自动代理。你可以通过使用一个或多个<include/>元素来实现添加到<aop:aspectj-autoproxy/>中。每个include元素定义一个命名模式并且命名的bean匹配至少其中一个模式将会被spring的aop自动代理配置。
<aop:aspectj-autoproxy>
<aop:include name="thisBean"/>
<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
[Note]
注意
Do not be misled by the name of the <aop:aspectj-autoproxy/> element: using it will result in the creation of Spring AOP proxies. The @AspectJ style of aspect declaration is just being used here, but the AspectJ runtime is not involved.
不要弄错<aop:aspectj-autoproxy/>元素中的名字:使用他会导致spring的aop代理的创建。@AspectJ风格的方面定义也是用在这里,但是AspectJ运行时是不会被调用的。
11.8.4 Load-time weaving with AspectJ in the Spring Framework
在spring框架中使用AspectJ在加载时处理
Load-time weaving (LTW) refers to the process of weaving AspectJ aspects into an application’s class files as they are being loaded into the Java virtual machine (JVM). The focus of this section is on configuring and using LTW in the specific context of the Spring Framework: this section is not an introduction to LTW though. For full details on the specifics of LTW and configuring LTW with just AspectJ (with Spring not being involved at all), see the LTW section of the AspectJ Development Environment Guide.
加载时处理代指AspectJ的处理进程在一个应用的类文件中被加载到java虚拟机中。这一节的重点是关于配置和使用LTW在spring框架的特定上下文中:这一节不是LTW的介绍。关于LTW定义的细节以及使用AspectJ来配置LTW(不需要调用spring),请参考AspectJ开发环境指南的LTW章节。
The value-add that the Spring Framework brings to AspectJ LTW is in enabling much finer-grained control over the weaving process. 'Vanilla' AspectJ LTW is effected using a Java (5+) agent, which is switched on by specifying a VM argument when starting up a JVM. It is thus a JVM-wide setting, which may be fine in some situations, but often is a little too coarse. Spring-enabled LTW enables you to switch on LTW on a per-ClassLoader basis, which obviously 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).
spring框架带给AspectJ LTW的价值增值是使得更加的方便控制通过处理进程。‘Vanilla’AspectJ LTW在java5以上有效并且可以在启动JVM时通过配置VM参数来启用。这是JVM级别的设置在一些情况下有效但是有一些粗糙。spring允许LTW在类加载器之前启用将会更加友好并且可以适用于单JVM多应用的环境(例如典型的应用服务器环境)
Further, in certain environments, this support enables load-time weaving without making any modifications to the application server’s launch script that will be needed to add -javaagent:path/to/aspectjweaver.jar or (as we describe later in this section) -javaagent:path/to/org.springframework.instrument-{version}.jar (previously named spring-agent.jar). Developers simply modify one or more files that form 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/org.springframework.instrument-{version}.jar(之前叫spring-agent.jar)。开发者简单的修改一个或多个文件在应用上下文来运行加载时处理以代替依赖于管理员处理开发的配置例如启动脚本。
Now that the sales pitch is over, let us first walk through a quick example of AspectJ LTW using Spring, followed by detailed specifics about elements introduced in the following example. For a complete example, please see the Petclinic sample application.
现在打折的时候已经过了,让我们首先看一个使用spring的有关AspectJ LTW的入门例子,关注下面例子中定义的细节元素。对于一个更加完整的例子,请参考Petclinic的例子应用。
A first example
第一个例子
Let us 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, what we are going to do is switch on a simple profiling aspect that will enable us to very quickly get some performance metrics, so that we can then apply a finer-grained profiling tool to that specific area immediately afterwards.
让我们假设你是一个应用开发者已经讨论过一些有关系统的性能问题。与其制作性能分析工具,我们需要打开一个小例子的性能分析方面将会允许我们很快获得一些性能标准,因此我们可以应用性能分析工具对于特定领域在以后的时间。
[Note]
注意
The example presented here uses XML style configuration, it is also possible to configure and use @AspectJ with Java Configuration. Specifically the @EnableLoadTimeWeaving annotation can be used as an alternative to <context:load-time-weaver/> (see below for details).
这个例子使用xml的风格配置,当然也可以使用@AspectJ的注解配置。特定的@EnableLoadTimeWeaving注解可以使用<context:load-time-weaver/>来替代(参见下面的细节)。
Here is the profiling aspect. Nothing too fancy, just a quick-and-dirty time-based profiler, using the @AspectJ-style of aspect declaration.
这里是性能分析的方面。不要多想,只是应急的基于时间的分析,使用@AspectJ风格在方面的定义中。
package foo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;
@Aspect
public class ProfilingAspect {
@Around("methodsToBeProfiled()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
StopWatch sw = new StopWatch(getClass().getSimpleName());
try {
sw.start(pjp.getSignature().getName());
return pjp.proceed();
} finally {
sw.stop();
System.out.println(sw.prettyPrint());
}
}
@Pointcut("execution(public * foo..*.*(..))")
public void methodsToBeProfiled(){}
}
We will 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.
我们也需要创建一个META-INF/aop.xml的文件,用于通知AspectJ处理器我们希望处理ProfilingAspect在我们的类中。这个文件规定了文件名在Java的classpath中名字为META-INF/aop.xml是标准的AspectJ文件。
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="foo.*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="foo.ProfilingAspect"/>
</aspects>
</aspectj>
Now to the Spring-specific portion of the configuration. We need to configure a LoadTimeWeaver (all explained later, just take it on trust for now). 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, as can be seen below (there are some more options that you can specify, but these are detailed later).
现在对于特定的配置的spring部分。我们需要配置LoadTimeWeaver(将会在后面解释,现在只是这样写就好)。这个加载时处理是必须的组件代表处理应用配置在一个或多个META-INF/aop.xml文件在你应用的类中。好的一面是他不需要很多的配置,就像下面看到的(有一些选项你可以定义,但是在后面描述)。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- a service object; we will be profiling its methods -->
<bean id="entitlementCalculationService"
class="foo.StubEntitlementCalculationService"/>
<!-- this switches on the load-time weaving -->
<context:load-time-weaver/>
</beans>
Now that all the required artifacts are in place - the aspect, the META-INF/aop.xml file, and the Spring configuration -, let us create a simple driver class with a main(..) method to demonstrate the LTW in action.
现在我们需要一个神器————aspect、META-INF/aop.xml文件、spring的配置————让我们创建一个简单的驱动类带有main方法来示范LTW的运作。
package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);
EntitlementCalculationService entitlementCalculationService
= (EntitlementCalculationService) ctx.getBean("entitlementCalculationService");
// the profiling aspect is 'woven' around this method execution
entitlementCalculationService.calculateEntitlement();
}
}
There is 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, just for this example, we are going to use a Java agent (supplied with Spring) to switch on the LTW. This is the command line we will use to run the above Main class:
这是最后要做的事情。这节的介绍说可以通过spring在类加载器之前启用LTW,而且这是真的。然而,对于这个例子我们希望使用Java的agent(由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 running 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 above example.
javaagent是一个标志用于定义和启用agent来使得程序在jvm上运行。spring框架和一些这样的agent在一起使用例如InstrumentationSavingAgent被包含在spring-instrument.jar中提供了上面的agent在例子中。
The output from the execution of the Main program will look something like that below. (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 :) )
这个程序的输出看起来如下。(我们使用了Thread.sleep语句在calculateEntitlement实现以至于分析器可以实际获取一些非0的毫秒数————01234毫秒并不是AOP的花销时间)
Calculating entitlement
StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms % Task name
------ ----- ----------------------------
01234 100% calculateEntitlement
Since this LTW is effected using full-blown AspectJ, we are not just limited to advising Spring beans; the following slight variation on the Main program will yield the same result.
这个LTW被影响通过使用成熟的AspectJ,我们将不会限制通知spring的bean,下面的部分将会有相同的输出结果。
package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("beans.xml", Main.class);
EntitlementCalculationService entitlementCalculationService =
new StubEntitlementCalculationService();
// the profiling aspect will be 'woven' around this method execution
entitlementCalculationService.calculateEntitlement();
}
}
Notice how in the above program we are simply bootstrapping the Spring container, and then creating a new instance of the StubEntitlementCalculationService totally outside the context of Spring… the profiling advice still gets woven in.
注意上面的程序中我们简单的获得了spring容器,并且创建了StubEntitlementCalculationService的实例在spring的上下文之外,通知依然被调用。
The example admittedly is simplistic… however the basics of the LTW support in Spring have all been introduced in the above example, and the rest of this section will explain the 'why' behind each bit of configuration and usage in detail.
这个例子无可否认是比较简单的,然而基本的LTW支持在spring中已经在上面的例子中被介绍了,并且这一节后续的部分我们将解释为什么这样配置以及相应的细节。
[Note]
注意
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 (of course), and then quite easily exclude from builds of the application being deployed into UAT or production.
上面使用的ProfilingAspect是基本的,但是是有意义的。这是在入门阶段不错的例子开发者可以使用在开发阶段,并且可以在应用构建之外被部署到UAT或生产中。
Aspects
方面
The aspects that you use in LTW have to be AspectJ aspects. They can be written in either the AspectJ language itself or you can write your aspects in the @AspectJ-style. It means that your aspects are then both valid AspectJ and Spring AOP aspects. Furthermore, the compiled aspect classes need to be available on the classpath.
你在LTW中使用的方面必须是AspectJ方面。他们可以以AspectJ语言来书写或你可以以@AspectJ风格来书写。这意味着你的方面必须符合AspectJ和Spring的AOP方面。更进一步,编译方面类需要你的classpath。
'META-INF/aop.xml'
The AspectJ LTW infrastructure is configured 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基础组件被配置在一个或多个META-INF/aop.xml文件中,并且在Java的classpath中可见(或者直接或者间接在jar文件中)
The structure and contents of this file is detailed in the main AspectJ reference documentation, and the interested reader is referred to that resource. (I appreciate that this section is brief, but the aop.xml file is 100% AspectJ - there is no Spring-specific information or semantics that apply to it, and so there is no extra value that I can contribute either as a result), so rather than rehash the quite satisfactory section that the AspectJ developers wrote, I am just directing you there.)
这个文件上下文的结构的细节在AspectJ指南文档中提到,感兴趣的读者可以参考相应的资源。(我建议这一节是简略介绍但是aop.xml文件是100%AspectJ风格的并且没有特定的spring信息或语义在上面,并且没有额外的值我可以作为结果来贡献),因此不是重复AspectJ开发人员书写的部分,我们只是指引你到这。
Required libraries (JARS)
需要jar
At a minimum you will need the following libraries to use the Spring Framework’s support for AspectJ LTW:
至少你需要下面的jar包来作为支持在spring框架中为了AspectJ LTW:
spring-aop.jar (version 2.5 or later, plus all mandatory dependencies)
aspectjweaver.jar (version 1.6.8 or later)
If you are using the Spring-provided agent to enable instrumentation, you will also need:
如果你使用spring提供的agent来允许实例化,你也需要:
spring-instrument.jar
Spring configuration
spring的配置
The key component in Spring’s LTW support is the LoadTimeWeaver interface (in the org.springframework.instrument.classloading package), and the numerous implementations of it that ship with the Spring distribution. A LoadTimeWeaver is responsible for adding one or more java.lang.instrument.ClassFileTransformers to a ClassLoader at runtime, which opens the door to all manner of interesting applications, one of which happens to be the LTW of aspects.
关键的部分在spring的LTW支持时LoadTimeWeaver接口(在org.springframework.instrument.classloading包中),并且有很多的实现在spring的发行版中。一个LoadTimeWeaver代表添加一个或多个java.lang.instrument.ClassFileTransformers给类加载器在运行时,并且打开了管理相关应用的方式,其中一个用于LTW的方面支持。
[Tip]
提示
If you are unfamiliar with the idea of runtime class file transformation, you are encouraged to read the javadoc API documentation for the java.lang.instrument package before continuing. This is not a huge chore because there is - rather annoyingly - precious little documentation there… the key interfaces and classes will at least be laid out in front of you for reference as you read through this section.
如果你不熟悉运行时类文件转化,你可以阅读java的api文档有关java.lang.instrument包在继续之前。这并不是一个繁重的任务因为这里有个简单的文档,关键的接口和类在这节中有介绍。
Configuring a LoadTimeWeaver for a particular ApplicationContext can be as easy as adding one line. (Please note that you almost certainly will need to be using an ApplicationContext as your Spring container - typically a BeanFactory will not be enough because the LTW support makes use of BeanFactoryPostProcessors.)
配置一个LoadTimeWeaver对于特定的ApplicationContext可以很简单如添加一行。(请注意你将会需要使用ApplicationContext作为你spring的容器,通常一个BeanFactory将不会足够因为LTW支持使用BeanFactoryPostProcessors)。
To enable the Spring Framework’s LTW support, you need to configure a LoadTimeWeaver, which typically is done using the @EnableLoadTimeWeaving annotation.
为了启用spring框架堆LTW的支持,你需要配置LoadTimeWeaver,通常可以使用@EnableLoadTimeWeaving注解
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
Alternatively, if you prefer XML based configuration, use the <context:load-time-weaver/> element. Note that the element is defined in the context namespace.
相应的,如果你倾向于xml配置,可以使用<context:load-time-weaver/>。注意元素定义在context的命名空间下。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:load-time-weaver/>
</beans>
The above configuration will define and register a number of LTW-specific infrastructure beans for you automatically, such as a LoadTimeWeaver and an AspectJWeavingEnabler. The default LoadTimeWeaver is the DefaultContextLoadTimeWeaver class, which attempts to decorate an automatically detected LoadTimeWeaver: the exact type of LoadTimeWeaver that will be 'automatically detected' is dependent upon your runtime environment (summarized in the following table).
上面的配置定义并注册一个编号有关特定LTW基础组件bean是自动的,例如LoadTimeWeaver和AspectJWeavingEnabler。默认的LoadTimeWeaver是DefaultContextLoadTimeWeaver类,尝试自动探测LoadTimeWeaver:LoadTimeWeaver的类型将自动被探测独立于你的运行时环境(在下面的表格中有总结)
Table 11.1. DefaultContextLoadTimeWeaver LoadTimeWeavers
Runtime Environment 运行时环境 | LoadTimeWeaver implementation LoadTimeWeaver的实现 |
WebLogicLoadTimeWeaver | |
GlassFishLoadTimeWeaver | |
TomcatLoadTimeWeaver | |
Running in Red Hat’s JBoss AS or WildFly | JBossLoadTimeWeaver |
WebSphereLoadTimeWeaver | |
JVM started with Spring InstrumentationSavingAgent (java -javaagent:path/to/spring-instrument.jar) | InstrumentationLoadTimeWeaver |
ReflectiveLoadTimeWeaver |
Note that these are just the LoadTimeWeavers that are autodetected when using the DefaultContextLoadTimeWeaver: it is of course possible to specify exactly which LoadTimeWeaver implementation that you wish to use.
注意这是被自动探测的LoadTimeWeaver当使用DefaultContextLoadTimeWeaver时:当然你也可以定义你希望使用的LoadTimeWeaver。
To specify a specific LoadTimeWeaver with Java configuration implement the LoadTimeWeavingConfigurer interface and override the getLoadTimeWeaver() method:
为了定义特定的LoadTimeWeaver使用java配置实现LoadTimeWeavingConfigurer接口并重写getLoadTimeWeaver方法:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
}
If you are using 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:
如果你使用基于xml的配置你可以定义全限定名的类名作为weaver-class属性的值在<context:load-time-weaver/>元素中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:load-time-weaver
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</beans>
The LoadTimeWeaver that is defined and registered by the configuration can be later retrieved from the Spring container using the well-known name loadTimeWeaver. Remember that the LoadTimeWeaver exists just 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 javadocs of the ClassPreProcessorAgentAdapter class for further details, because the specifics of how the weaving is actually effected is beyond the scope of this section.
LoadTimeWeaver定义和注册通过配置可以在后续从spring容器中恢复通过使用熟悉的loadTimeWeaver。记住LoadTimeWeaver存在以一种策略的形式服务于spring的LTW基础架构并添加了一个或多个ClassFileTransformers。实际的ClassFileTransformers使得LTW是ClassPreProcessorAgentAdapter类。见ClassPreProcessorAgentAdapter类的javadocs来了解更加详尽的信息,因为如何处理实际影响在一定的范围内。
There is one final attribute of the configuration left to discuss: the aspectjWeaving attribute (or aspectj-weaving if you are using XML). This is a simple attribute that controls whether LTW is enabled or not; it is as simple as that. It accepts one of three possible values, summarized below, with the default value being autodetect if the attribute is not present.
有一个最终属性的配置还没有被讨论:aspectjWeaving属性(如果你使用xml的话则是aspectj-weaving)。这是一个简单的属性控制LTW是否开启,并且也确实很简单。他接受三个值中的其中一个值,总结如下,如果没有设置则使用默认值。
Table 11.2. AspectJ weaving attribute values
Annotation Value 注解值 | XML Value xml值 | Explanation 解释 |
ENABLED | on | AspectJ weaving is on, and aspects will be woven at load-time as appropriate. AspectJ处理打开并且在适当条件下在加载时被处理 |
DISABLED | off | LTW is off… no aspect will be 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, else it is off. This is the default value. 如果在META-INF/aop.xml中发现spring的LTW基础则打开AspectJ处理,否则关闭 |
Environment-specific configuration
特定环境的配置
This last section contains any additional settings and configuration that you will need when using Spring’s LTW support in environments such as application servers and web containers.
最后一节包含额外的设置你需要注意当你使用spring的LTW支持在某些应用服务器和web容器中。
Tomcat
Historically, Apache Tomcat's default class loader did not support class transformation which is why Spring provides an enhanced implementation that addresses this need. Named TomcatInstrumentableClassLoader, the loader works on Tomcat 6.0 and above.
历史上,Apache Tomcat默认的类加载器不支持类transformation,也就是为什么spring提供了一个增强的实现根据需要。名字为TomcatInstrumentableClassLoader,该加载器在Tomcat6.0以上有效。
[Tip]
提示
Do not define TomcatInstrumentableClassLoader anymore on Tomcat 8.0 and higher. Instead, let Spring automatically use Tomcat’s new native InstrumentableClassLoader facility through the TomcatLoadTimeWeaver strategy.
不要再Tomcat8.0及以上定义TomcatInstrumentableClassLoader。作为替代,让spring自动使用Tomcat新的内置InstrumentableClassLoader设施通过TomcatLoadTimeWeaver策略。
If you still need to use TomcatInstrumentableClassLoader, it can be registered individually for each web application as follows:
如果你依然需要使用TomcatInstrumentableClassLoader,可以独立为每个web应用注册书写如下:
Copy org.springframework.instrument.tomcat.jar into $CATALINA_HOME/lib, where $CATALINA_HOME represents the root of the Tomcat installation)
复制org.springframework.instrument.tomcat.jar到$CATALINA_HOME/lib中,其中$CATALINA_HOME代表Tomcat安装的根路径
Instruct Tomcat to use the custom class loader (instead of the default) by editing the web application context file:
指导Tomcat使用自定义类加载器(代替默认值)通过修改web应用上下文文件:
<Context path="/myWebApp" docBase="/my/webApp/location">
<Loader
loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
</Context>
Apache Tomcat (6.0+) supports several context locations:
Apache Tomcat6.0及以上支持一些上下文位置:
server configuration file - $CATALINA_HOME/conf/server.xml
服务配置文件 - $CATALINA_HOME/conf/server.xml
default context configuration - $CATALINA_HOME/conf/context.xml - that affects all deployed web applications
默认上下文配置 - $CATALINA_HOME/conf/context.xml -影响所有被部署的web应用
per-web application configuration which can be deployed either on the server-side at $CATALINA_HOME/conf/[enginename]/[hostname]/[webapp]-context.xml or embedded inside the web-app archive at META-INF/context.xml
在每个web应用配置可以被部署在服务器端在$CATALINA_HOME/conf/[enginename]/[hostname]/[webapp]-context.xml中或内嵌的web-app文件META-INF/context.xml
For efficiency, the embedded per-web-app configuration style is recommended because it will impact only applications that use the custom class loader and does not require any changes to the server configuration. See the Tomcat 6.0.x documentation for more details about available context locations.
考虑效率的话,内嵌的每个web-app的配置风格是被推荐使用的因为他只会影响一个应用使用自定义的类加载器并且不需要修改服务器的配置。见Tomcat6.0.x的文档来查看有关配置上下文的位置的细节。
Alternatively, consider the use of the Spring-provided generic VM agent, to be specified in Tomcat’s launch script (see above). This will make instrumentation available to all deployed web applications, no matter what ClassLoader they happen to run on.
作为替代,考虑使用spring提供的通用VM agent,可以定义在Tomcat的启动脚本中(如上)。这会使得基础组件可以应用于所有被部署的web应用,不管他们实际使用的类加载器。
WebLogic, WebSphere, Resin, GlassFish, JBoss
Recent versions of WebLogic Server (version 10 and above), IBM WebSphere Application Server (version 7 and above), Resin (3.1 and above) and JBoss (6.x or above) provide a ClassLoader that is capable of local instrumentation. Spring’s native LTW leverages such ClassLoaders to enable AspectJ weaving. You can enable LTW by simply activating load-time weaving as described earlier. Specifically, you do not need to modify the launch script to add -javaagent:path/to/spring-instrument.jar.
最新的版本有关weblogic服务器(版本10及以上)、IBM的websphere应用服务器(版本7及以上)、Resin(版本3.1及以上)和JBoss(版本6.x及以上)提供一个类加载器在本地基础组件中可用。spring本地的LTW例如ClassLoaders启用了AspectJ处理。你可以开启LTW通过简单的激活加载时处理如之前的描述。特定的你不需要修改启动脚本来添加 -javaagent:path/to/spring-instrument.jar。
Note that GlassFish instrumentation-capable ClassLoader is available only in its EAR environment. For GlassFish web applications, follow the Tomcat setup instructions as outlined above.
注意GlassFish基础组件ClassLoader在他的EAR环境中可用。对于GlassFish的web应用,遵循Tomcat设置的方式如上。
Note that on JBoss 6.x, the app server scanning needs to be disabled 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 6.x中应用服务器扫描需要被建立来阻止加载类在应用实际开始之前。一个快速的变通方案是添加你自己的配置文件名字为WEB-INF/jboss-scanning.xml并且内容如下:
<scanning xmlns="urn:jboss:scanning:1.0"/>
Generic Java applications
通用的java应用
When class instrumentation is required in environments that do not support or are not supported by the existing LoadTimeWeaver implementations, a JDK agent can be the only solution. For such cases, Spring provides InstrumentationLoadTimeWeaver, which requires a Spring-specific (but very general) VM agent, org.springframework.instrument-{version}.jar (previously named spring-agent.jar).
当类基础组件在环境中被需要不支持货没有被已有的LoadTimeWeaver实现支持,一个JDK的agent是唯一的解决方案。对于这样的情况,spring提供了InstrumentationLoadTimeWeaver,需要特定spring的VM的agent,org.springframework.instrument-{version}.jar(以前的名字为spring-agent.jar)
To use it, you must start the virtual machine with the Spring agent, by supplying the following JVM options:
为了使用他,你必须通过spring的agent来启动虚拟机,通过提供如下的JVM选项:
-javaagent:/path/to/org.springframework.instrument-{version}.jar
Note that this requires modification of the VM launch script which may prevent you from using this in application server environments (depending on your operation policies). Additionally, the JDK agent will instrument the entire VM which can prove expensive.
注意这需要修改VM的启动脚本会阻止你使用应用服务器环境(依赖于你的操作策略)。额外的,JDK的agent将处理整个vm而且代价巨大。
For performance reasons, it is recommended to use this configuration only if your target environment (such as Jetty) does not have (or does not support) a dedicated LTW.
关于性能的原因,建议使用这个配置如果你的目标环境(例如Jetty)没有(或没有提供)专有的LTW。
11.9 Further Resources
更多资源
More information on AspectJ can be found on the AspectJ website.
更多的有关AspectJ的信息可以在AspectJ的网站上查看。
The book Eclipse AspectJ by Adrian Colyer et. al. (Addison-Wesley, 2005) provides a comprehensive introduction and reference for the AspectJ language.
Adrian Colyer的书Eclipse AspectJ提供了详尽的介绍有关AspectJ语言。
The book 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 in Action书,第二版,作者Ramnivas Laddad强烈推荐,关注于AspectJ但是很多通用的AOP主题被探讨。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。