官方文档传送门
本文仅涉及从 11. Aspect Oriented Programming with Spring 到 11.2.5 Introductions 之间的内容, 11.2.5 Introductions 之后的讲的是引入的内容,也与本文无关。
本文中所有被使用引用格式的文字均来自于官方文档,可以打开官方文档后在网页内搜索定位,以更好理解上下文。同时本文的内容的顺序并不完全与官方文档一致,对一些内容舍弃不纳入,对一些内容整理和提前了几节。
type : 可以认为此处使用 class 不够完备,因为还有 interface 之类的也符合当前语句的说明。所以使用 type 。
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.
我们约定将 Advice 翻译为 增强 。抛开引用内容(官方的文档的安排是有问题的。应当先有 Advice 的概念,再有其它的概念。文档中未将 Adive 置于第一个并且对它的解释中使用了其它概念术语,这是不妥当的。),增强作为动词,可以理解为指针对类的方法,非侵犯地(不改动方法本身)为其在被调用之前与被调用之后添加新的功能。作为名词,可以理解为之前说的容纳/实现新的功能的方法。
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
).
我们约定将 Aspect 翻译为 切面 。切面是对用于增强的方法按照一定理解和规则进行整理分类后的模块,其中包含了符合这一模块的概念和理解的用于增强的方法。
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.
我们约定将 joint point 翻译为 接入点 。接入点是可被增强的内容,在 Spring AOP 中可以简单地理解为一个类的方法。
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.
我们约定将 Pointcut 翻译为 切点 。切点是一条规则,也是根据这条规则被筛选出来的接入点的集合。
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.)
我们约定将 introduction 翻译为 引入 。引入是指为一个 type 在改动它的源码的基础上为之增加方法的行为。
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.
我们约定将 target 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 proxy
翻译为 代理人
。代理人指经由 Spring AOP
指派,代理源对象实现对源对象方法的增强,并将所有调用源对象的被增强的方法的请求都先由自己经手的另一个对象。在 Spring AOP
中,代理人要么使用JDK动态代理功能创建出来,要么使用CGLIB代理功能创建。
Weaving : linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.
我们约定将 Weaving 翻译为 织入 。前面说的代理人经由 Spring AOP 指派,这个指派就是这里的织入。
另外
我们约定,统一使用使用 调用 一词来描述接入点作为方法被调用/被执行。
从上到下我们分别约定翻译为:前置增强,返回后增强,抛出异常后增强,后置增强,环绕增强。
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 defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.
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.
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
并不实现对成员对象的赋值,更新(也就是我们说的构造器, this.filed = something
)的拦截。仅支持对注册为 Spring bean
的实体类的方法进行增强。
Spring AOP 默认使用 JDK动态代理 来为实现了接口并具有切点(即使具有的切点并不是实现的接口的方法)的类进行增强。而对不实现任何接口的类则默认使用 CGLIB代理。
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 container 一起提供常见的优秀的java商用领域的解决方案。因此对于非常细节处进行增强,不是 Spring AOP 的目标,请使用 AspectJ 代替吧。
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.
必需准备好 aspectjweaver.jar
。
关于使用 maven
引入 aspectjweaver.jar
的 pom.xml
的代码
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>自己写</version> </dependency>
@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.
这一段说明了引入 aspectjwearver
不代表将底层实现从 JDK动态代理或CGLIB代理 改为了ASPECTJ。
必需通知 Spring 你要使用它。
显然先要有 Spring 框架。我们的官方文档直接定位到了 Spring AOP 这块,其实回到顶部看目录,第一节的标题的就是 Getting Started with Spring ,如果 Spring 环境还没有配好,先按照文档配好。
然后在一个配置类上增加 @EnableAspectJAutoProxy
注解
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
XML写法文档有,此处略。
请注意之前我们对切面的理解:切面是对用于增强的方法按照一定理解和规则进行整理分类后的模块,其中包含了符合这一模块的概念和理解的用于增强的方法。而一个类含有切面,不代表它的所有方法均是切面,简单来说,并未用于增强的方法便不包含在该类的切面中。
形如
package org.xyz; import org.aspectj.lang.annotation.Aspect; @Aspect public class NotVeryUsefulAspect { }
XML写法文档有,此处略。
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.
如开头所说,类是类,切面是切面,不过是这个切面(模块)都在这个类中,不代表这个类就不正常了。
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.
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).
@Pointcut
一次定义,处处引用。 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:
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. */ @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. */ @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. */ @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. * * 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. * * 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.) */ @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. */ @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))") public void dataAccessOperation() {} }
对于一个被多个增强应用的切点规则,可以将之写入 @Pointcut
注解内,而这个注解则注解在一个方法上。之后向要应用该切点规则,只需在增强的注解的内容中写被该 @Pointcut
注解的方法的全限定名,而不必反复写切点规则。
例子(本例中 com.xyz.myapp.SystemArchitecture.dataAccessOperation()
便是上面那段代码里的(最后那个)的切点规则):
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() { // ... } }
当然,直接写也没什么问题。
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() { // ... } }
value
属性的值)
接下来的内容一般是写在 @Pointcut
这个定义切点规则和 @Before
`@After 等定义增强时机的注解的括号中的。因为只有它(切点规则表达式),所以就被默认赋给了注解中的
value 属性。实际上,还有
argNames 属性可用。如果说这两个都使用,那就必须明确指定
value = "起点规则表达式"`。
这里先谈切点规则表达式。
以下限定符的格式省略了它们是被包在 @Pointcut()
切点声明注解或者 @Before
, @Around
等标识增强时机的注解的括号之中的。
execution - for matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP
Spring AOP users are likely to use the execution
pointcut designator the most often. The format of an execution expression is:
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.
modifiers-pattern
public
, protect
, private
。 ret-type-pattern
*
表示所有 type
都行的意思。 declaring-type-pattern
.
连接。 *
可以作为通配符使用。 要么不写,要么写全
。 name-pattern
*
作为通配符,比如 set*
表示以set开头,后接零个多个其它字符的方法名的接入点。 param-pattern
()
表示匹配不接收任何传参的接入点。 (..)
表示零个至多个参数都可以,即完全不在这件事上有限制。 *
代表一个参数,但不限制该参数的 type
。例如 (*,String)
要求切入点接受两个参数,第一个参数的 type
没有限制,第二个参数必须是 String
。 throws-pattern
一些例子
execution(public * *(..))
execution(* set*(..))
AccountService
interface: execution(* com.xyz.service.AccountService.*(..))
execution(* com.xyz.service.*.*(..))
execution(* com.xyz.service..*.*(..))
within - limits matching to join points within certain types (simply the execution of a method declared within a matching type when using Spring AOP)
在某一 type 内部的所有接入点。之前在时就已经探讨过, type 用于写 class 感觉并不完善的情况下。这里就是一个例子,如果要完善的话,显然还要说接口,说包(package)才完善(当然我也不确定这样是否完善了)。
一些例子
within(com.xyz.service.*)
within(com.xyz.service..*)
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
这两个限定符我网上也找了不少资料,但是都没有举具体的实例,让人十分困惑。所以对于两者的区别作为疑问留存,无法给出。 两者的共性效果就是限制调用接入点的实例是被指定的 type (类或者接口)的实例 。
举例说明;
在一个基础的引入了AOP的 Spring Boot 应用中
@SpringBootApplication @EnableAspectJAutoProxy public class LogbackandaopApplication { public static void main(String[] args) { SpringApplication.run(LogbackandaopApplication.class, args); //其中DemoA实现了IDemo接口,而DemoB未实现任何接口 DemoA demoA = (DemoA) ApplicationContextProvider.getBean("demoA"); DemoB demoB = (DemoB) ApplicationContextProvider.getBean("demoB"); demoA.IDemoTest(); demoB.IDemoTest(); } }
其中:
package xyz.d613.logbackandaop.demo; public interface IDemo { void IDemoTest(); }
@Component public class DemoA implements IDemo { @Override public void IDemoTest() { System.out.println("A"); } }
/* 注意!DemoB类未实现任何接口! */ @Component public class DemoB { public void IDemoTest() { System.out.println("B"); } }
定义切面为
@Component @Aspect public class MyAspect { @Before("execution(* *.IDemoTest(..)) && this(...logbackandaop.demo.IDemo)") public void advice() throws Throwable { System.out.println("MyAspect advice"); } }
运行此应用,得到控制台上的输出:
MyAspect advice A B
可以发现A被前置增强了,而B没有。这就是 this(...logbackandaop.demo.IDemo)
在起作用。尽管B拥有满足 execution(* *.IDemoTest(..))
的方法,但它不是 IDemo
接口的实例。故被排除。
args - limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types
限定接入点为接入点被调用时传入的实参(按照顺序地)都是args限定符中声明的 type 的实例。
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
.
假设有如下的类型
public interface A; public class B implements A;
那么切点 @Pointcut(execution(* *(A))
不会对 public anytype method(B);
生效。但 @Pointcut(args(A))
则会对 public anytype method(B);
拦截并进行增强。这就是所谓 the argument passed at runtime
。
利用args限定符使增强获得切点的参数
只限定头部几个参数,后面的不关心该怎么写,跳转过去后注意看加粗部分与上下文。
两者的区别留作疑问。
@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
@Transactional
annotation: @target(org.springframework.transaction.annotation.Transactional)
@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)
@Transactional
annotation: @within(org.springframework.transaction.annotation.Transactional)
限制接入点被调用时,调用它的实例所属的类必须带有指定的注解。在上述例子中为 @Transactional
。
@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)
@Classified
annotation: @args(com.xyz.security.Classified)
限定接入点参数的数量的同时,对应自己指定的顺序,被传入的实参的所属的类有被@args限定符指定的注解。
@annotation - limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation
@Transactional
annotation: @annotation(org.springframework.transaction.annotation.Transactional)
接入点(指方法本身而不是其所在的类)要带有@annotation限定符指定的注解。
不知为何对 java.lang.Override
注解无效。
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:
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.
可以使用通配符以一次性指定多个 bean 。
使用 Bean
这个术语,就意味着其必须是被 Spring IOC
所管理的类,即带有 @Component
或 @Controller
之类的注解或通过配置交由Spring的Bean容器管理。
argNames
属性 在开篇已经说过
接下来的内容一般是写在 @Pointcut
这个定义切点规则和 @Before
`@After 等定义增强时机的注解的括号中的。因为只有它(切点规则表达式),所以就被默认赋给了注解中的
value 属性。实际上,还有
argNames 属性可用。如果说这两个都使用,那就必须明确指定
value = "起点规则表达式"`。
这一节关于另一个属性: argNames
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:
这里用我的例子。
假设有这么一个类有这么一个方法想要被增强:
package root.demo public class Demo { public void printf(String info){ System.out.println(info); } }
确定切点切到这个接入点身上并不麻烦,以下两种写法都行:
package root.aspects; @Aspect public class MyAspect { @Before("execution(* root.demo.Demo.printf(java.lang.String))") public void advice(){ } //另一种写法 @Before("execution(* root.demo.Demo.printf(..) && args(java.lang.String)") public void advice(){ } }
然而问题在于,我想要获得切点中的那个被传入的 String
类型的参数,怎么办?这样写么?
@Before("execution(* root.demo.Demo.printf(java.lang.String))") public void advice(String s){ }
这里的 s
是不会得到任何值的。
那么要怎么做呢,官方文档的意思,这么写:
@Before(value = "execution(* xyz.d613.logbackandaop.demo.Demo.printf(..)) && args(s))", argNames = "s") public void advice(String s){ System.out.println(s); }
请注意,此处 args(s)
中,虽然 args
仍是限定符,但其内部的值显然不可能是一个 type
了。在使用 argNames
的使用,原来限定符的用法发生了改变。写在 value
中的各个限定符(排除 execution
)中的值总是要和 argNames
属性中的值存在一一对应关系,而 argNames
中的值又于增强的函数签名的参数表中的参数一一对应,如这里的 argNames="s"
中的 s
与 public void advice(String s)
中的 String s
中的 s
对应。
这样建立起联系后,传入切点中的参数就能被传入增强中并交给指定增强的方法签名中的指定的参数。同时 Spring AOP
也会反过来(由于 String s
的这个 String
)意识到相当于有一个 args(java.lang.String)
的限定符。
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:
@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:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()") public void audit(JoinPoint jp) { // ... use jp }
这部分在说 JointPoint
接口以及它的子接口,实现类,在作为增强的参数表中的一员时,不需要额外准备,只要写好 (JointPoint jp...)
(其中 JointPoint
可用特定实现类或子接口换掉,具体看其它部分)即可。
关于这部分知识更多细节,见获知被增强的切点的相关信息。
@Before
前置增强
Before advice is declared in an aspect using the @Before
annotation:、
@AfterReturnning
返回后增强
After returning advice runs when a matched method execution returns normally. It is declared using the @AfterReturning
annotation:
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:
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).
Please note that it is not possible to return a totally different reference when using after-returning advice.
@AfterThrowing
抛出异常后增强
After throwing advice runs when a matched method execution exits by throwing an exception. It is declared using the @AfterThrowing
annotation:
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 you don't need restrict the exception type, use Throwable
as the exception type otherwise) and bind the thrown exception to an advice parameter.
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).
@After
后置增强
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.
从文档中的括号也可以看出来,后置增强相当于对应 try{}catch(){}finally{}
中的 finally
增强。对于抛出异常导致的调用结束或正常返回的调用结束,它均能处理。它经常用来释放资源。
@Around
环绕增强 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).
环绕增强能在调用前增强,也能在调用后增强,更能同时实现两者,还能经过它决定被调用的方法到底会不会真的被调用。
如果需要以线程安全的方式(例如,启动和停止计时器)在方法执行之前和之后共享状态,则通常使用环绕增强。
在可以不用环绕增强的情景下,尽量不要使用环绕增强,否则会带来额外的开支。
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.
增强的参数声明的第一个必须是 ProceddingJointPoint
的实例或其子类的实例(不过它自己其实是 org.aspectj.lang.JoinPoint
的实现类)。在增强的方法体中使用 ProceddingJointPoint#proceed(Object ...)
方法,才会使切点被真的调用。如果不使用此方法,则切点不会被真正调用。
ProceddingJointPoint#proceed(Object ...)
的返回值就是切点的返回值,实际传入的参数(对应 Object ...
则应该是切点所需要的参数。
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).
高优先级的增强在切点被调用前的时机的增强执行顺序中更靠前,在切点被调用后的时机的增强执行顺序中更靠后。
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. 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.
当两个相同优先级的增强在同一个切点的同一个时机时,它们的执行顺序顺序无法确定。如果这两个增强所在的切面不同,但你可以使用 @Order
注解(或实现 org.springframework.core.Ordered
的 getValue()
方法)在切面上来确定两个增强(其实是切面)的优先级。 @Order
的值更小的那个优先级更高。
但如果是在同一个切面,那就没法子了。而且既然是同一个切面,那么请你好好整理一下代码,避免这种情况出现吧,因为这样可能引起很糟糕的后果。
实话实说我惊呆了,到这里居然就结束了,那么五种增强冲突时怎么整?比如前置增强和环绕增强在调用前这一时机,谁先谁后,官方文档不告知的?秀啊!
经过网上资料的汇总和自己的实验,可以认为有以下结论:
org.aspectj.lang.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.
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:
@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:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()") public void audit(JoinPoint jp) { // ... use jp }
以上两段引用在官方文档中亦不在同一个地方,但整理认为它们所表达的意思只有一个:
任何增强的参数声明都可以不做额外准备地将第一个参数声明为 (JointPoint jp)
,比如
@Aspect public class MyAspect { @Before("execution(* *.IDemoTest(..))") public void advice(org.aspectj.lang.JoinPoint jp) throws Throwable { System.out.println("MyAspect advice"); System.out.println(jp); System.out.println(jp.getSignature()); } }
而 Spring AOP
会自动地将切点,增强的相关信息封装为接口 JointPoint
的实例(如前面的所用的,通常是一个 ProceedingJointPoint
,所以一般写参数的时候也是写 ProceedingJointPoint
而不是直接写这个接口)。同时接口 JointPoint
也提供了一些用于调取出被封装的信息的方法,这个实例必然会将之实现。那么具体有哪些信息以及怎么使用,请见 官方文档
。
不过较为常用的方法其实文档(上面摘抄过来的引用)中已经给出了。
第一种方法就是使用上面一节,先获取切点相关信息,即 JoinPoint
得实例,然后通过 JoinPoint#getArgs()
方法获得切点的参数们,一个 Object[]
数组。
第二种方法,文档介绍的使用 args
限定符。
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:
@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.
例子的 @Before
注解的前半段,是在引用 com.xyz.myapp.SystemArchitecture.dataAccessOperation()
上的 @Pointcut
注解所定义的切点规则。后半段则是再在前半段筛出来的切点中筛出第一个参数为 Account
, 之后有什么参数,有没有参数不限制
的接入点。这里为什么第一个参数为 Account
,不是因为 args(account,..)
中写了 account
, 然后 Spring 会自动把首字母大写,然后明白过来是 Account
类。而是因为这个 account
和 public void validateAccount(Account account)
中的 account
对应,于是 Spring 从 Account account
中得知 args(account,..)
中的 account
要求的是一个 Account
的实例。实际上将 account
改为 dfaljlkdj
,只要增强的参数表也跟着改为 public void validateAccount(Account dfaljlkdj)
,那么一切就能正常工作。
上文说到, @Before
注解的前半段是引用一个切点规则。那么能否直接让那个切点把后半段的事情也做了,我就一个引用完事呢?答案是可以,写法如下:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") private void accountDataAccessOperation(Account account) {} @Before("accountDataAccessOperation(account)") public void validateAccount(Account account) { // ... }
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.
First the definition of the @Auditable
annotation:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditable { AuditCode value(); }
And then the advice that matches the execution of @Auditable
methods:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)") public void audit(Auditable auditable) { AuditCode code = auditable.value(); // ... }
Spring AOP can handle generics used in class declarations and method parameters. Suppose you have a generic type like this:
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:
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)") public void beforeSampleMethod(MyType param) { // Advice implementation }
首先,一个切点有泛型,那么它(切点是一个个方法)所属的 type
必然能接收泛型(如引用中的第一段代码),所以在 type
与方法之间的点前添加一个加号: Sample+.sampleGenericMethod(*)
,表明这个 type
要接收泛型。
令人遗憾的是,这个 MyType
并没有什么值得称道的功能——我还以为这个对应泛型里的 T
,所以我可以通过 param.getClass()
来获得被增强的切点的泛型的具体类型。但实际上这个 MyType
就是表示这个地方由你来填,没有什么功能。如果相不对泛型的类型做限制——实际上只不过是不对切点的第一个参数,即 param
的类型做限制,只不过在例子中 param
是 T param
,看起来就像是对泛型有限制一样——就填入 Object
。
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.
不能使用 Collection
及其实现类和子接口加泛型来筛选参数的类型。