文章目录
- Spring Aspect cheatsheet
- 使用
- Why?
- 概念
- Advice类型
- PointCut常用示例
- Advice使用示例
- 一个完整的例子
- 参考
Spring Aspect cheatsheet
放一些简单易用的spring aspect 语法手册。
使用
完整代码:spring-aop
- 引入库, 参考build.gradle
- 使用
@Aspect
和@Configuration
来标记Aspect。参考LogingAspect
Why?
就像之前写的btrace手册一样, 为了方便查找和实际操作,想分享一些关于写Aspect相关的cheatsheet。
概念
- Aspect
作为某个关注点、横切面在许多类的模块化集合。比如事务管理@Transactional
就是在企业应用中事务相关的的横切面。或者某个用来记录日志的切面。一般情况下切面会包含一个完整
的有意义的集合。一般会包含Pointcut定义和Advice定义。 - Join Point
代表某个程序执行的点。在spring中代表某个方法的执行点。 - Advice
在某个特殊的Join Point,Aspect采取的操作。包含不同的类型(around,before,after)。许多aop框架包括
spring用拦截器来实现Advice,并且维护一个拦截器链来处理某个JointPoint的多个Advice。 - Pointcut
定义满足某些定义条件的方法切面。Advice将在所有满足Pointcut的JoinPoint执行。比如Pointcut可以定义为
在所有的Controller方法上执行(within(com.example.controller.*)
)。本文也将提供更多的例子方便查找。 - Introduction
以类为基础去添加新的方法或者属性。比如可以为某些bean引入一个属性IsModified
来方便实现缓存。 - Target object
等着被aspect增强的类对象(也叫advised object
)。因为spring用代理来实现的,所以这个也叫被代理对象。 - AOP proxy
`
为了实现aspect,通过JDK动态代理,或者cglib代理实现的增强过的对象。 - Weaving
将多个aspect和真正的应用内组合在一起的过程, 可以在编译时(aspectj compiler),加载时,运行时(spring aop)实现。
Advice类型
- Before
- AfterReturning
- AfterThrowing
- Around
around advice会有一个ProceedingJoinPoint参数.这是最强的一类advice。它提供proceed
方法来
调用原有逻辑。其他的几个Advie都是类似于只读型的, 只提供了一个JoinPoint类型的参数。
PointCut常用示例
示例 | 解释 |
| 执行com.xyz.service(不包括子包)的所有类的方法 |
| 执行com.xyz.service(包括子包)的所有类的方法 |
| 执行实现了AccountService的接口的类 |
| 执行 |
| 执行所有public方法 |
| 执行所有方法名以set开始的方法 |
| 执行所有在com.xyz.service.AccountService类里面的方法。 |
| 执行所有在com.xyz.service里面的方法(不包括子包) |
| 执行所有在com.xyz.service里面的方法(包括子包) |
| 某个类具有@Transactional注解 |
| 某个方法具有@Transactional注解 |
具体语法:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
Advice使用示例
- Before
方法开始执行前,可以使用JoinPoint
获取当前执行方法的状态。后续几个都可以获取该参数。
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck(JoinPoint jp) {
}
- AfterReturning
正常返回后:
@AfterReturning(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
- AfterThrowing
抛异常后:
@AfterThrowing(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
- Arround
环绕通知可以使用ProceedingJoinPoint.proceed
来调用原始的实现。
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
public Object timing(ProceedingJoinPoint jp) throws Throwable {
// start stopwatch
Object retVal = jp.proceed();
// stop stopwatch
return retVal;
}
- 给Advice传参数。
除了上面的JoinPoint可以拿到参数外,还可以用如下的方式拿到:
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
上面例子中account是第一个参数。 当然dataAccessOperation指定的PointCut必须保证有至少一个参数。
- Advice的顺序
按照下面顺序执行:
@Around, @Before, @After, @AfterReturning, @AfterThrowing
不同Aspect的还可以通过@Order指定顺序
一个完整的例子
用来统计运行时长和记录请求的例子:
package com.example.springaop;
import org.aspectj.lang.*;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.*;
import java.util.*;
@Configuration
@Aspect
public class LogingAspect {
@Pointcut("within(com.example.springaop.controller.*)")
public void ctrlMethod() {
}
@Pointcut("execution(public * *(..)) ")
public void publicMethod() {}
@Around("ctrlMethod() && publicMethod()")
public Object loggingAndTiming(ProceedingJoinPoint jp) throws Throwable {
long t1 = System.currentTimeMillis();
String className = jp.getSignature().getDeclaringTypeName();
String methodName = jp.getSignature().getName();
String args = Arrays.toString(jp.getArgs());
try {
return jp.proceed();
} catch (Throwable e) {
throw e;
}
finally {
System.out.println(String.format("%s.%s with args: %s , cost time: %d",
className, methodName, args, System.currentTimeMillis() - t1));
}
}
}
参考