spring aop要点记录
从今天开始,阅读spring framework官方文档中的spring aop部分,并将要点记录如下:
-
spring的面向切面编程
- spring提供两种方式来编写自定义切面:基于XML配置方式、基于@Aspect注解方式
-
AOP概念
- Aspect(切面): 在spring aop中,切面(aspects)就是普通的类通过xml配置或者使用@Aspect注解实现的。
- Join Point(连接点):代表方法执行。
- Advice(通知):包含5中类型:
- Before advice:通知(advice)在连接点之前执行。
- After returning advice:通知在连接点正常完成之后执行。(比如:方法正常返回,未抛出异常)。
- After throwing advice:通知在方法抛出异常时执行。
- After (finally) advice:不管连接点返回正常还是异常,通知都会在返回后执行。
- Around advice:通知环绕在连接点周围。这个是最强的通知,它可以在方法执行前后自定义行为。它也会选择是否继续执行连接点或直接返回自己的返回值或抛出异常来结束执行。
-
通过切入点匹配的连接点的概念是AOP的关键。
- Pointcut(切入点): 匹配Join Point的述语。与切入点密切相关的通知可以在与切入点相匹配的任何连接点上运行。连接点通过切入点表达式(pointcut expressions)匹配是Aop的核心概念。
- Introduction(引入):声明额外的方法和字段代表一种类型。spring aop 允许你将新接口(与其实现)引入到任何的advised object。例如:为了简化缓存,你可以使用引入让一个Bean实现
IsModified接口。(在AspectJ生态中,introduction是一种类型间声明(inter-type declaration))。 - Target object(目标对象):一个或多个切面的通知对象,也称为“advised object”。由于Spring AOP是使用运行时代理实现的,因此该对象始终是代理对象。
- AOP proxy(AOP代理): AOP框架为了实现切面协议(通知方法执行等等)而创建的对象。在spring框架中,一个AOP代理就是一个JDK动态代理或者一个CGLIB代理。
- Weaving(织入):将切面和其他的应用类型或者对象作连接,并创建一个Target object。织入可以在编译时(例如:使用AOP编译器)、加载时、运行时完成。Sping aop在运行时完成织入。
-
spring aop的能力与目标
- spring aop 目前只支持方法执行连接点。
- spring框架的一个重要理念是非侵入性。所以spring可以无缝整合spring aop、ioc 容器、aspectj。
-
aop 代理
-
spring aop默认使用标准JDK动态代理作为AOP代理,这样可以使任何一个接口(或接口集)被代理。spring aop也可以使用CGLIB代理,这对代理类是必须的,但对接口不是。默认情况下,如果一个业务类没有实现接口,就会使用CGLIB。但是在程序中使用接口而不使用类是更好的体现。
-
-
支持@AspectJ
-
启用@AspectJ支持
- 启用@AspectJ支持可通过XML方式或者java方式配置。无论使用哪种方式,都要先保证AspectJ的aspectjweaver.jar在classpath中。
-
java配置方式
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
-
xml配置方式
<aop:aspectj-autoproxy/>
-
声明一个切面
package org.xyz; import org.aspectj.lang.annotation.Aspect; @Aspect public class NotVeryUsefulAspect { }
- 在classpath中,@Aspect注解不能让spring自动检测到切面类作为普通类注入。要实现这个目的,需要增加注解@Component。
- 在spring aop中,一个切面本身不能作为其他切面的通知目标。@Aspect注解,表明这个类是一个切面,而且该类不能被自动代理。
-
声明一个切入点(Pointcut)
- 一个切点定义包含两部分:
- 签名:在注解方式中,签名通过普通的方法定义来实现,并且方法必须是void返回类型。
- 切入点表达式:表明我们感兴趣的是哪个方法。使用@Pointcut注解。
- 下面的例子表示:一个名叫 anyOldTransfer 的切点匹配任何名字为transfer的方法执行
@Pointcut("execution(* transfer(..))") // the pointcut expression private void anyOldTransfer() {} // the pointcut signature - 切入点表达构成了@Pointcut注解的value值。
-
支持的切入点标识符pointcut designators(PCD)
- execution: 为了匹配方法执行连接点。这是spring aop主要的切入点标识符。
- within: 限制在某些确定的类型中匹配到连接点。这些类型是在声明方法的执行时匹配的。
- this: 限制匹配到连接点的匹配,其中bean引用(Spring AOP代理)是给定类型的实例。
- target: 限制匹配到连接点的匹配,其中目标对象(应用对象被代理)是给定类型的实例。
- args: 限制匹配到连接点的匹配,其中参数是给定类型的实例。
- @target: 限制匹配到连接点的匹配,其中执行对象类有一个给定类型的注解。
- @args: 限制匹配到连接点的匹配,其中传递的实际参数的运行时类型具有给定类型的注释。
- @within: 限定匹配具有给定注解类型中的连接点
- @annotation: 限制匹配到连接点的匹配,其中连接点项目具有给定的注解。
-
切入点表达式的形式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
returning type pattern(ret-type-pattern), name-pattern, param-pattern是必填的,其他都是选填项。ret-type-pattern表示一个方法的返回类型,*表示任何类型。name-pattern表示方法名称,可用 * 全部或部分匹配名称。如果指定一个声明类型匹配,在其尾部追加 . 构成名称匹配组件。param-pattern要复杂一些:()表示无参方法,(..)表示任何数量的参数(0个或多个),(*)表示任何数量的单参数,(*,String)表示有两个参数,第一个参数可以是任何类型,第二个参数只能是String类型。
- 一个切点定义包含两部分:
-
示例如下:
1、匹配任何的public 方法 execution(public * *(..)) 2、匹配set开头的任何方法 execution(* set*(..)) 3、匹配任何定义在AccountService接口中的方法 execution(* com.aleda.AccountService.*(..)) 4、匹配任何定义在service包下面的方法 execution(* com.service.*.*(..)) 5、匹配任何定义在service包或者其下任一子包内的方法 execution(* com.service..*.*(..)) 6、任何在service包下的连接点(仅在spring aop中的执行方法)
within(com.aleda.service.*)
7、任何在service包或者其任一子包下的连接点(仅在spring aop中的执行方法)
within(com.aleda.service..*)
8、代理实现了AccountService接口的任何连接点(仅在spring aop中的执行方法)
this(com.aleda.service.AccountService)
9、代理实现了AccountService接口的任何连接点(仅在spring aop中的执行方法)
target(com.aleda.service.AccountService)
10、在运行时传递的参数是可序列化的具有单个参数的任何连接点(仅在spring aop中的执行方法)
args(java.io.Serializable)
这个切入点示例跟execution(**(java.io.Serializable))是不同的。args版本匹配的是在运行时传递的参数是可序列化的,而execution版本匹配的是方法签名声明的是单个Serializable类型的参数。
11、目标对象上标有@Transactional注解的任一连接点(仅在Spring aop中的执行方法)
@target(org.springframework.transaction.annotation.Transactional)
12、目标对象的声明类型上标有@Transactional注解的任一连接点(仅在Spring aop中的执行方法)
@within(org.springframework.transaction.annotation.Transactional)
13、执行方法上标有@Transactional注解的任一连接点(仅在Spring aop中的执行方法)
@annotaion(org.springframework.transaction.annotation.Transactional)
14、仅有单个参数并且在运行时传递的参数上标有@Classified注解的任一连接点(仅在spring aop中的执行方法)
@args(com.aleda.Classified)
15、位于名为tradeService的Spring bean上的任一连接点(仅在Spring aop中的执行方法)
bean(tradeService)
16、位于名子匹配*Service的Spring bean上的任一连接点(仅在Spring aop中的执行方法)
bean(*Service)
-
-
-
-
写一个良好的切入点切入点
-
-
-
切入点指示器有3大组分类:kinded, scoping, contextual。
kinded: 此类指示器选择特定类型的连接点,execution, get, set, call, and handler.
scoping: 此类指示器选择一组感兴趣的连接点,within and withincode
contextual: 此类指示器基于上下文来匹配, this, target and @annotation
一个好的切入点应该至少包括两种类型(kinded and scoping)。仅有kinded 或者 contextual指示器会因为额外的处理和分析而影响织入时的效率(占用时间和内存)。Scoping 指示器可以快速的完成匹配。
-
-
-
声明通知
-
-
通知是与切入点表达式关联的。可以运行在与切入点匹配的方法执行之前、之后或者环绕。切入点表达式可能要么是仅仅指向命名的切入点的引用,要么是在适当位置声明的切入点表达式。
-
-
-
-
Before Advice
-
-
-
使用@Before注解声明一个Before Advice
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() { // ... } }
如果使用in-place切入点表达式,我们可以重写上面的示例:
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
-
-
-
当切入点匹配的方法执行正常返回后,After Returning 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() { // ... } }
有时,我们需要访问在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) { // ... } }
returning属性使用的名字必须与advice方法的参数名称一致。当方法执行返回时,返回值作为对应的参数值传递给advice方法。
-
-
-
-
After Throwing Advice
-
-
-
当匹配的方法执行抛出异常时,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() { // ... } }
通常,我们希望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) { // ... } }
throwing属性使用的名字必须与advice方法的参数名称一致。当方法执行抛出异常时,该异常作为对应的参数值传递给advice方法。
-
-
-
-
After (Finally) Advice
-
-
-
当匹配的方法执行存在时,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
-
-
-
Around Advice环绕在匹配的方法执行四周。

浙公网安备 33010602011771号