在 AspectJ用法 里介绍了 spring AOP 的用法。这里以切面LogAspect的生效流程,简单分析下SpringAOP原理。
切面LogAspect 定义如下:
java
@Aspect
@Component
public class LogAspect {
@Before(value = "execution(String org.example.topic.aop.demo.StudentServiceImpl.*(String))")
public void logBeforeProceed(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
String output = String.format("LogAspect.logBefore: methodName=%s, args=%s", methodName, args);
System.out.println(output);
}
LogAspect 类是 Spring AOP(面向切面编程)的一个典型应用,其工作原理涉及 Spring 容器的 Bean 生命周期、代理机制以及 AOP 的织入过程。
1. @Aspect 和 @Component 的作用
java
@Aspect
@Component
public class LogAspect { ... }
@Component:告诉 Spring 容器这是一个普通的 Spring Bean,Spring 会在启动时通过组件扫描(如@ComponentScan)将其注册为一个 Bean。@Aspect:表明这个类是一个切面(Aspect),即包含通知(Advice)和切点(Pointcut)的逻辑单元。Spring AOP 会特别处理带有@Aspect注解的 Bean。
✅ 所以
LogAspect本身就是一个 Spring 管理的 Bean,并且被识别为切面。
2. 切点(Pointcut)定义
java
@Before(value = "execution(String org.example.topic.aop.demo.StudentServiceImpl.*(String))")
这是一个 前置通知(Before Advice),其切点表达式含义如下:
execution(...):匹配方法执行连接点。String:返回类型必须是String。org.example.topic.aop.demo.StudentServiceImpl.*:目标类是StudentServiceImpl,任意方法名(*)。(String):方法参数必须是单个String类型。
✅ 这个切点会匹配
StudentServiceImpl中所有返回String、接受一个String参数的方法。
3. Spring 如何创建代理(Proxy)
Spring AOP 是基于 代理模式 实现的。当容器发现某个 Bean 需要被 AOP 切面增强时,会为其创建一个 代理对象,而不是直接返回原始 Bean。
3.1 代理类型选择:
- 如果目标类(如
StudentServiceImpl)实现了接口 ,Spring 默认使用 JDK 动态代理(基于接口)。 - 如果目标类 没有实现接口 ,Spring 会使用 CGLIB 代理(通过继承目标类生成子类)。
📌 假设
StudentServiceImpl没有实现接口,Spring 会用 CGLIB 生成一个StudentServiceImpl$$EnhancerBySpringCGLIB子类作为代理。
3.2 代理的作用:
- 当你从 Spring 容器获取
StudentServiceImpl的 Bean 时,实际拿到的是 代理对象。 - 调用代理对象的方法时,会先经过 AOP 拦截器链(包括你的
@Before通知),再调用原始方法。
4. AOP 织入(Weaving)过程
在 Spring 容器启动过程中,会执行以下关键步骤:
-
Bean 扫描与注册
LogAspect被@Component注册为 Bean。StudentServiceImpl也被注册为 Bean(假设它也有@Service或@Component)。
-
BeanPostProcessor 处理
Spring 使用
AnnotationAwareAspectJAutoProxyCreator(一个特殊的BeanPostProcessor)来处理 AOP:- 它会扫描所有
@AspectBean(如LogAspect)。 - 分析其中的切点表达式。
- 检查其他普通 Bean(如
StudentServiceImpl)是否匹配这些切点。
- 它会扫描所有
-
创建代理
- 如果
StudentServiceImpl匹配LogAspect中的切点,AnnotationAwareAspectJAutoProxyCreator会在postProcessAfterInitialization阶段替换原始 Bean 为代理对象。
- 如果
-
方法调用时的拦截
- 当你调用
studentService.someMethod("hello")时:- 实际调用的是代理对象的方法。
- 代理对象内部会构建一个 拦截器链(Interceptor Chain)。
- 首先执行
logBeforeProceed(因为是@Before)。 - 然后调用原始
StudentServiceImpl的目标方法。
- 当你调用
5. JoinPoint 的作用
java
public void logBeforeProceed(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
...
}
JoinPoint是 AOP 中的"连接点",代表程序执行过程中的一个点(如方法调用)。- Spring AOP 会自动将当前方法调用的上下文(方法名、参数等)封装到
JoinPoint对象中,并传入通知方法。
6. 流程总结
java
Spring 启动
↓
扫描 @Component → 注册 LogAspect 和 StudentServiceImpl 为 Bean
↓
AnnotationAwareAspectJAutoProxyCreator 发现 @Aspect
↓
解析切点表达式:execution(String StudentServiceImpl.*(String))
↓
检查 StudentServiceImpl 是否匹配 → 是!
↓
为 StudentServiceImpl 创建代理(JDK/CGLIB)
↓
容器中注入的是代理对象
↓
调用 studentService.xxx("arg")
↓
代理拦截 → 执行 LogAspect.logBeforeProceed(JoinPoint)
↓
输出日志
↓
调用原始 StudentServiceImpl.xxx("arg")
补充说明
- 仅对 Spring Bean 有效 :AOP 只对 Spring 容器管理的 Bean 生效。如果
new StudentServiceImpl()手动创建对象,AOP 不会生效。 - 代理限制 :CGLIB 无法代理
final方法;JDK 代理只能代理接口方法。 - 性能:AOP 通过动态代理实现,有一定开销,但通常可忽略。
通过以上机制,LogAspect 就能在不修改 StudentServiceImpl 代码的情况下,透明地在方法调用前插入日志逻辑,体现了 AOP 的"横切关注点"分离思想。
7. 相关文档
-
AspectJ用法: https://blog.csdn.net/taotiezhengfeng/article/details/149515395
-
.Spring AOP切点表达式的关键词梳理: https://blog.csdn.net/taotiezhengfeng/article/details/155744545
-
HandlerInterceptor 与 AOP 对比:https://blog.csdn.net/taotiezhengfeng/article/details/155452970