Spring AOP
Spring AOP术语以及概念
- Join Point: 程序执行中的一个点。该次执行可以是方法调用、异常处理、类初始化或者对象初始化。Spring AOP只支持方法调用。如果还需要使用其他加入点,可以使用Spring和AspectJ结合使用。
- Advice:在加入点需要具体执行的。具体的advice类型有@Before,@After,@Around,@AfterThrowing以及@AfterReturning。
- Pointcut:一个advice必须执行的一个加入点的集合。一个advice不必应用于所有的加入点(joint point),因此切点(pointcut)很好的控制了advice在程序组件的执行。Spring中切点使用表达式,以及支持AspectJ的切点表试语言。
- Aspect:advice以及切点的结合决定了在程序中的逻辑以及应该在哪里执行。Aspect使用常规类的注解@Aspect实现,该注解可以获得Spring AspectJ的支持。
定义切点
切点定义了advice被应用的执行的点,以下是切点支持的标识符:
标识符 | 描述 |
---|---|
execution | 限制匹配方法执行的加入点 |
within | 限制匹配仅某个类型中的加入点,例如:within(com.packt.ch3.TransferService). |
args | 限制匹配指定类型参数的加入点,例如:args(account,..). |
this | 限制匹配bean引用或者spring代理对象是指定类型的实例的加入点,例如:this(com.packt.ch3.TransferService). |
target | 限制匹配给定目标对象是给定类型的一个实例的加入点,例如:target(com.packt.ch3.TransferService). |
@within | 限制匹配申明的类型有给定的注解,例如:@within(org.springframework.transaction.annotation.Transactional). |
@target | 限制匹配目标对象有给定类型的注解,例如:@target(org.springframework.transaction.annotion.Transactional). |
@args | 限制匹配实际的传入参数有给定类型的注解,例如: @args(com.packt.ch3.Lockable). |
@annotation | 限制匹配执行的方法有给定的注解,例如:@annotation(org.springframework.transaction.annotation.Transactinal). |
execution描述符的切点表达式:
* 使用execution():推荐使用方法匹配,以下是匹配的模式(其中[]表示是可选填的):
[Modifiers] ReturnType [ClassType] MethodName ([Arguments]) [throws ExceptionType]
* 可以通过加入其它的切点创建聚合切点,可以使用&&, ||, !操作符连接(这些操作符分别表示AND,OR,以及NOT)。
execution示例:
execution(* com.packt.ch3.bankingapp.service.AccountService.findAccountById(..))
advice类型
- @Before:该advice在加入点位置之前执行,并且在切面中使用@Before注解。示例代码如下:
@Pointcut("execution(*com.packt.ch03.bankingapp.service.TransferService.transfer())")
public void transfer(){}
@Before("transfer()")
public void beforeTransfer(JoinPoint joinPoint) {
LOGGER.info("validate account balance before transferring amount");
}
注意:如果@Before方法抛出异常,目标方法tranfer不会被调用
* @After: 该advice方法在加入点方法正常退出或者没有抛出异常返回时执行的。示例代码:
@Pointcut("execution(*com.packt.ch03.bankingapp.service.TransferService.transfer())")
public void transfer(){}
@After("transfer()")
public void afterTransfer(JoinPoint joinPoint) {
LOGGER.info("Successfully transferred from source account to dest account");
}
- @AfterReturning:如果只想匹配一个方法正常返回时,可以使用该注解,例如:
@Pointcut("execution(*com.packt.ch03.bankingapp.service.TransferService.transfer())")
public void transfer(){}
@AfterReturning(pointcut="transfer() and args(source, dest, amount)",returning="isTransferSuccessful")
public void afterTransferReturns(Joinpoint joinPoint, Acount source, Account dest, Double amount, boolean isTransferSuccessful) {
if (isTransferSuccessful) {
LOGGER.info("Amount transferred successfully ");
//find remaining balance of source account
}
}
- @AfterThrowing:匹配方法抛出异常时调用此advice。这对于记录方法调用异常信息很有帮助,示例代码:
@Pointcut("execution(*com.packt.ch03.bankingapp.service.TransferService.transfer())")
public void transfer(){}
@AfterThrowing(pointcut="transfer()", throwing="minimumAmountException")
public void exceptionFromTransfer(JoinPoint joinPoint, MinimumAmountException minimumAmoutException) {
LOGGER.info("Exception thrown from transfer method: " + minimumAmountException.getMessage());
}
类似的throwing指定的异常类型必须匹配才能调用。
* @Around:该注解是@Before与@After的结合,但是比其更加强大,能够完全控制方法的执行。添加@Around advice的方法的第一个参数应该是ProceedingJoinPoint。代码示例:
@Pointcut("execution(*com.packt.ch03.bankingapp.service.TransferService.transfer())")
public void transfer(){}
@Around("transfer()")
public boolean aroundTransfer(ProceedingJoinPoint proceedingJoinPoint){
LOGGER.info("Inside Around advice, before calling transfer method ");
boolean isTransferSuccessful = false;
try {
isTransferSuccessful = (Boolean) proceedingJointPoint.proceed();
} catch (Throwable e) {
LOGGER.error(e.getMessage(), e);
}
LOGGER.info("Inside Around advice, after returning from transfer method"):
return isTransferSuccessful;
}
在@Around advice注解的方法中,可以调用proceed方法一次,多次甚至是不调用,能够完全控制
切面初始化模型
默认的,声明的切面(aspect)是单例(singleton),因此每个类加载器(class loader)(不是每个虚拟机)的切面对象只有一个实例。切面实例在类加载器作为垃圾回收时会销毁。
如果想使切面相对于类实例拥有私有属性,那么切面需要时有状态的。为此,Spring使用AspectJ支持提供的perthis以及pertarget初始化模型。AspectJ是一个独立的类库,除了有perthis,pertarget外,还有percflow,percflowbelow,pertypewithin等,不过Spring集成的AspectJ不支持这些方式。
使用perthis创建一个有状态的切面对象,示例如下:
@Aspect("perthis(com.packt.ch03.bankingapp.service.TransferService)")
public class TransferAspect {
//Add your per instance attributes holding private data
//Define your advice methods
}
一旦我们@Aspect使用perthis语句,对于每个执行transfer方法(每个唯一的对象在切点匹配表达式的加入点倾向于使用this)的单独TransferService对象会创建一个切面实例。该切面对象在TransferService对象失效时失效。
pertarget与perthis相似,在pertarget中,加入点在匹配切点表达式时每个唯一的目标对象会创建一个切面实例。
Spring使用代理模式,通过创建代理对象编织(weave)切面(aspect)于目标对象。