1 概述
Spring 的切面编程由 AspectJ 提供支持.
AOP 需要了解的基础:
- 使用
@Aspect
声明一个切面. @After
,@Before
,@Around
定义 建言(advice); 这 3 个注解的参数 就是 切点(PointCut) , 所谓 切点 其实就是一个 拦截规则(可以由注解定义, 也可以由方法规则定义).- 可以把 切点 用
@PonitCut
来定义, 然后放到@After
,@Before
,@Around
当参数, 这样可以复用切点. - 符合 拦截条件 的每一处 也是 连接点(JoinPoint).
2 AOP 示例
下面演示一下两种定义切点的方式:
基于自定义注解的切点 和 基于方法规则的切点.
本示例用到的代码已经放在 GitHub 上.
2.1 自定义一个切点注解 @MyAction
自定义一个注解 @MyAction
用来标记切点的位置:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAction {
String name();
}
注解本身没有功能, 就和 xml 一样.
注解和 xml 都是元数据(解释数据的数据), 也就是所谓的配置.
注解需要被其他程序解析才有用.
MyAction
在 切面定义 被解析成切点.
2.2 定义两个方法用来测试测点
定义两个方法, 在这两个方法上测试切点:
@Service
public class TestService {
public void method1() {
System.out.println("execute first method..");
}
@MyAction(name = "自定义注解")
public void method2() {
System.out.println("execute second method..");
}
}
2.3 定义一个切面
@Aspect // 声明一个切面
@Component
public class TestAspect {
// @Pointcut 用来定义切点. 这里把带有 @MyAction 注解的方法 视为切点.
@Pointcut("@annotation(cn.mitrecx.learn2aop.aop.MyAction)")
public void annotationPointCut() {
}
/**
* After 注解用来声明一个建言, 这里使用 annotationPointCut() 定义的切点.
* After 注解的方法 会在 切点执行之后 执行.
* 换句话说, 在 切点方法 执行后, afterExecution 方法会紧接着被执行.
*/
// @After("@annotation(cn.mitrecx.learn2aop.aop.MyAction)") // 和下面的 @After 等效
@After("annotationPointCut()")
public void afterExecution(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MyAction action = method.getAnnotation(MyAction.class);
System.out.println("After(注解式拦截): " + action.name());
}
/**
* Before 注解用来声明一个建言, 这里使用 方法规则 定义的切点,
* 方法规则 <code>execution(* cn.mitrecx.learn2aop.aop.TestService.*(..))</code> 表示 {@link TestService} 的所有方法.
* <p>
* 简而言之, 在 {@link TestService} 的任一方法开始执行前, 都先执行 beforeExecution 方法.
*/
@Before("execution(* cn.mitrecx.learn2aop.aop.TestService.*(..))")
public void beforeExecution(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("Before(方法规则式拦截): " + method.getName());
}
}
上面 @Before
使用 方法规则式切点 不太好,它会导致 代码逻辑不清晰.
因为我们无法直接从 TestService
类里的方法 看出方法 “篡改” 了(被加了beforeExecution()
逻辑).
2.4 配置类
@Configuration
@ComponentScan("cn.mitrecx.learn2aop.aop")
@EnableAspectJAutoProxy
public class AopConfig {
}
2.5 主程序
public class MyApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AopConfig.class);
TestService testService = context.getBean(TestService.class);
testService.method1();
System.out.println("---------");
testService.method2();
context.close();
}
}
运行结果:
Before(方法规则式拦截): method1
execute first method..
---------
Before(方法规则式拦截): method2
execute second method..
After(注解式拦截): 自定义注解
结果显示, 两个方法规则式切点(methed1, methed2) 的 @Before
建言 在切点方法执行前 被执行;
注解式切点(methed2) 的 @After
建言 在切点方法执行后 被执行.
注意:
这里只是为演示两种定义切点的方式.
个人建议只使用 注解式切点, 因为它看起来更清晰.
Reference
[1]. Spring Boot 实战 - 汪云飞