AOP 的七大术语是理解 Spring AOP 的基础。这七个概念构成了 AOP 从“在哪里做”到“做什么”再到“怎么做到”的完整链条。
一、切面(Aspect)
切面是横切关注点的模块化。它是一个类,里面包含了切点和通知的定义。通俗说,切面就是“把哪些通用逻辑,应用到哪些方法上”的封装。
在 Spring 中,用@Aspect标注一个类,它就成为一个切面。
@Aspect@ComponentpublicclassLogAspect{@Pointcut("execution(* com.example.service.*.*(..))")publicvoidserviceLayer(){}@Around("serviceLayer()")publicObjectlog(ProceedingJoinPointpjp)throwsThrowable{System.out.println("开始执行:"+pjp.getSignature().getName());Objectresult=pjp.proceed();System.out.println("执行完成");returnresult;}}LogAspect就是一个切面,它把“日志记录”这个横切关注点模块化了。
二、连接点(Join Point)
连接点是程序执行过程中可以插入切面的点。在 Spring AOP 中,连接点始终代表方法的执行。
一个类中的所有方法都可以是连接点。但并不是每个连接点都会被增强,具体哪些被增强由切点决定。
@ServicepublicclassUserService{publicvoidregister(Useruser){}// 这是一个连接点publicvoidupdate(Useruser){}// 这也是一个连接点publicUserfindById(Longid){}// 这也是一个连接点}Spring AOP 只支持方法级别的连接点。AspectJ 还支持字段访问、构造器调用等更细粒度的连接点,但 Spring AOP 不支持。
三、切点(Pointcut)
切点是一组连接点的集合,用表达式定义“在哪些方法上织入通知”。切点决定了哪些连接点会被增强。
Spring AOP 使用 AspectJ 切点表达式语法。
@Pointcut("execution(* com.example.service.*.*(..))")publicvoidserviceLayer(){}这个切点匹配com.example.service包下所有类的所有方法。
常见的切点表达式:
| 表达式 | 含义 |
|---|---|
execution(* com.example.service.*.*(..)) | service 包下所有方法 |
execution(* com.example.service.UserService.*(..)) | UserService 类的所有方法 |
@annotation(com.example.annotation.Log) | 所有标注了 @Log 的方法 |
within(com.example.service.*) | service 包下所有类 |
args(java.lang.String) | 参数为 String 的方法 |
切点是“筛选器”,从所有连接点中选出需要增强的那些。
四、通知(Advice)
通知是切面在切点上执行的具体动作,定义了“做什么”以及“什么时候做”。Spring AOP 提供了五种通知。
| 通知 | 注解 | 执行时机 |
|---|---|---|
| 前置通知 | @Before | 目标方法执行前 |
| 后置通知 | @After | 目标方法执行后(无论是否异常) |
| 返回通知 | @AfterReturning | 目标方法正常返回后 |
| 异常通知 | @AfterThrowing | 目标方法抛出异常后 |
| 环绕通知 | @Around | 目标方法执行前后,最强大 |
@Before("serviceLayer()")publicvoidbefore(JoinPointjp){System.out.println("执行前:"+jp.getSignature().getName());}@AfterReturning(pointcut="serviceLayer()",returning="result")publicvoidafterReturning(JoinPointjp,Objectresult){System.out.println("返回:"+result);}@Around("serviceLayer()")publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{longstart=System.currentTimeMillis();Objectresult=pjp.proceed();System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");returnresult;}@Around是功能最全的通知,可以控制目标方法是否执行、修改返回值、处理异常。
五、目标对象(Target)
目标对象是被切面增强的原始业务对象。它包含核心业务逻辑,不包含任何切面代码。
@ServicepublicclassUserService{publicvoidregister(Useruser){System.out.println("注册用户:"+user.getName());}}UserService实例就是目标对象。Spring AOP 会为它生成代理对象,在代理对象中织入通知逻辑。
目标对象也叫“被通知对象”(Advised Object)。
六、代理(Proxy)
代理是织入通知后生成的对象。它包含了目标对象的业务逻辑,同时在方法调用前后插入了通知逻辑。客户端调用的是代理对象,而不是目标对象。
Spring AOP 使用两种代理:
- JDK 动态代理:目标类实现了接口时使用,代理类实现同一接口。
- CGLIB 代理:目标类没有接口时使用,代理类继承目标类。
// 从容器中获取的是代理对象UserServiceuserService=context.getBean(UserService.class);System.out.println(userService.getClass().getName());// 输出:com.sun.proxy.$Proxy12 或 UserService$$EnhancerBySpringCGLIB$$xxx代理对象是 AOP 的实现载体。没有代理,通知就无法织入到目标方法上。
七、织入(Weaving)
织入是把切面应用到目标对象并创建代理对象的过程。它回答了“通知是怎么加到目标方法上的”。
织入可以在不同时机发生:
| 织入时机 | 说明 | 代表 |
|---|---|---|
| 编译期织入 | 编译时修改字节码 | AspectJ 编译期织入 |
| 类加载期织入 | 类加载时修改字节码 | AspectJ LTW |
| 运行时织入 | 运行时动态生成代理 | Spring AOP |
Spring AOP 采用运行时织入:容器启动时,AbstractAutoProxyCreator在 Bean 初始化后判断是否需要代理,如果需要,就通过 JDK 或 CGLIB 生成代理对象,把通知逻辑织入。
// AbstractAutoProxyCreator.postProcessAfterInitialization()publicObjectpostProcessAfterInitialization(Objectbean,StringbeanName){if(bean!=null){ObjectcacheKey=getCacheKey(bean.getClass(),beanName);if(this.earlyProxyReferences.remove(cacheKey)!=bean){returnwrapIfNecessary(bean,beanName,cacheKey);}}returnbean;}八、七大术语的关系
用一个流程串起来:
定义切面(Aspect) └── 切点(Pointcut)筛选出连接点(Join Point) └── 通知(Advice)定义增强逻辑 └── 织入(Weaving)到目标对象(Target) └── 生成代理(Proxy) └── 客户端调用代理,增强生效| 术语 | 英文 | 一句话说明 |
|---|---|---|
| 切面 | Aspect | 横切关注点的模块化 |
| 连接点 | Join Point | 可以被拦截的方法执行点 |
| 切点 | Pointcut | 定义拦截哪些连接点 |
| 通知 | Advice | 拦截后执行的具体动作 |
| 目标对象 | Target | 被增强的原始业务对象 |
| 代理 | Proxy | 织入通知后生成的对象 |
| 织入 | Weaving | 把切面应用到目标对象的过程 |
九、总结
AOP 的七大术语描述了从“定义增强”到“应用增强”的完整过程:切面定义了横切逻辑,切点筛选出需要增强的方法,通知定义增强动作,织入把增强应用到目标对象上,生成代理对象,客户端调用代理时增强生效。理解这七个术语,就理解了 Spring AOP 的工作原理。