使用场景
- 常见用于记录日志, 异常集中处理, 权限验证以及 Web参数有效验证等等
列子1 (演示基本过程
@Aspect
@Component
public class TestAspect {
/**
* 声明一个切入点, 命名 pointcut1
* */
@Pointcut("execution(public java.util.Map com.example.demo.controller.TestService.getMap(String, Integer))")
private void pointcut1() {}
/**
* 锁定的切点方法之前执行
* JoinPoint 获取连接点信息
* - Object[] getArgs() 获取连接点方法运行时的入参列表
* - Signature getSignature() 获取连接点的方法签名对象
* - Object getTarget() 获取连接点所在的目标对象
* - Object getThis() 获取代理对象
* */
@Before("pointcut1()")
public void aspect1(JoinPoint joinPoint) {
System.out.println("TestAspect -> @Before 方法名称:" + joinPoint.getSignature().getName() + ", 参数:" + Arrays.asList(joinPoint.getArgs()));
}
/**
* 锁定的切点方法之后执行
* */
@After("pointcut1()")
public void aspect2() {
System.out.println("TestAspect -> @After");
}
/**
* 锁定的切点方法返回后执行
* */
@AfterReturning(pointcut = "pointcut1()", returning="result")
public void aspect3(Object result){
System.out.println("TestAspect -> @AfterReturning 返回值:" + result);
}
}
@Service
public class TestService {
public Map<String, Object> getMap(String name, Integer age) {
final Map<String, Object> result = new HashMap<>(2);
result.put("name", name);
result.put("age", age);
System.out.println("TestService -> getMap 函数体内输出:" + result);
return result;
}
}
# 测试地址 http://127.0.0.1:8080/aspectTest
@RestController
public class AspectController {
@Autowired
private TestService testService;
@GetMapping(value = "/aspectTest")
public Map<String, Object> aspectTest(@RequestParam(value="name", required=false, defaultValue="大爷") String name,
@RequestParam(value="age", required=false, defaultValue="35") Integer age) {
System.out.println("AspectController -> aspectTest");
return testService.getMap(name, age);
}
}
# 输出
AspectController -> aspectTest
TestAspect -> @Before 方法名称:getMap, 参数:[大爷, 35]
TestService -> getMap 函数体内输出:{name=大爷, age=35}
TestAspect -> @After
TestAspect -> @AfterReturning 返回值:{name=大爷, age=35}
切点表达式
..
两个点表明多个,*
代表一个- 其中权限修饰符是可选, 当不写时不能用
*
代替, 因此第一个*
代表返回类型不限 - 第二个
*
表示指定包下所有类 - 第三个
*
表示指定类下所有方法 (..)
两个点表示指定方法的参数不限
@Pointcut(execution(* com..demo.controller.*.*(..)))
切点复合运算
- 切点表达式可以加运算符
与&&
,或||
,非!
做复合运算
@Before(value="execution(* com.example.demo.controller.TestService.getMap(..)) && args(name, age, ..)")
切点匹配方法
- execution: 用于匹配方法的执行
- within: 用于匹配指定类内的方法的执行
- args: 用于指定匹配方法的参数类型
- target: 用于匹配容器内的类的对象执行方法, 不包括引入接口
- this: 用于匹配容器内的类的对象执行方法, 包括引入接口
注: target和 this两种方法表达式必须全限定名到类名, 不支持
*
通配符
通知注解
- @Before: 前置通知, 在方法执行之前执行
- @After: 后置通知, 在方法执行之后执行
- @AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterReturning(pointcut = "pointcut1()", returning="result")
public void aspect3(JoinPoint joinPoint, Object result){
System.out.println("TestAspect1 -> @AfterReturning 返回值:" + result);
}
# 执行过程
Request -> @Before -> Method -> @After -> @AfterReturning
- @AfterThrowing: 异常通知, 在方法抛出异常之后执行, 意味着跳过返回通知
/**
* 指定方法内抛出异常时才会被通知
* 属性 throwing上设置异常类 Throwable将会捕获任何错误和异常, 或按需设置指定异常类
*/
@AfterThrowing(pointcut = "pointcut1()", throwing="ex")
public void aspect4(JoinPoint joinPoint, TestException ex) {
System.out.println("TestAspect1 -> @AfterThrowing 异常:" + ex.getMessage());
}
# 执行过程
Request -> @Before -> Method -> @After -> @AfterThrowing
- @Around: 环绕通知, 围绕着方法执行
/**
* ProceedingJoinPoint继承于 JoinPoint接口, 并多了两个方法
* - Object proceed() throws Throwable 通过反射执行目标对象连接点处的方法
* - Object proceed(Object[] var1) throws Throwable 通过入参替换原参, 通过反射执行目标对象连接点处的方法
*/
@Around("pointcut1()")
public Object aspect5(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("TestAspect1 -> @Around start");
Object[] args = pjp.getArgs();
for (Object arg : args) {
System.out.println("TestAspect1 -> @Around 参数:" + arg); // 输出指定方法参数
}
long startTime = System.currentTimeMillis();
Object object = pjp.proceed(); // 调用 proceed后指定方法会被执行
long endTime = System.currentTimeMillis();
System.out.println("TestAspect1 -> @Around " + (endTime - startTime) + " ms");
return object;
}
# 输出
AspectController -> aspectTest
TestAspect1 -> @Around start
TestAspect1 -> @Around 参数:大爷
TestAspect1 -> @Around 参数:35
TestAspect1 -> @Before
TestService -> getMap 函数体内输出:{name=大爷, age=35}
TestAspect1 -> @Around 0 ms
TestAspect1 -> @After
TestAspect1 -> @AfterReturning 返回值:{name=大爷, age=35}
# 正常执行过程
Request -> @Around -> @Before -> Method -> @Around -> @After -> @AfterReturning
# 异常执行过程
Request -> @Around -> @Before -> Method -> @Around -> @After -> @AfterThrowing
切面类执行顺序
- 通过 @Order注解设置, 数越小越靠前
@Aspect
@Component
@Order(3)
public class TestAspect1 {}
如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!