环境:SpringBoot2.7.18
1. 简介
Spring AOP(面向切面编程)是Spring框架的核心特性之一,它以一种非侵入式的方式增强了应用程序的模块性和可维护性。通过AOP,开发者能够将横切关注点(如日志记录、事务管理、安全控制等)从业务逻辑中分离出来,形成独立的切面,从而实现了关注点的模块化。这种分离不仅简化了代码结构,还提高了代码的重用性和灵活性。Spring AOP利用代理机制在运行时动态地将切面织入到目标对象中,无需修改原有代码,极大降低了系统间的耦合度。
实现代理的核心元素
- 切入点
Pointcut定义了哪些方法会被增强,而切点通常通过表达式来定义,这些表达式可以基于方法名、参数类型、注解等多种条件。
- 通知类
通知则是你需要增强的逻辑,这其中包括了前置通知(Before Advice)、后置通知(After Advice)、环绕通知(Around Advice)、异常通知(Throws Advice)和引介通知(Introduction Advice)。
- 处理器
有了上面2个关键元素后,那如何才能创建代理呢?这时候的BeanPostProcessor就是最为关键的类了,它会根据切入点来判断你当前的bean是否符合条件,对于符合条件的则进行代理的创建最终返回给Spring容器。Spring容器中保存的是代理对象。而在Spring中我们最常见的几种注册处理器的方式是:通过下面3个注解
@Configuration
// 开启事务(针对的事务注解@Transactional)
@EnableTransactionManagement
// 开启AOP代理(只要具备上面的1,2条件即可)
@EnableAspectJAutoProxy
// 开启异步支持(针对的是@Async注解)
@EnableAsync
public class AppConfig {}
具备了上面3个核心元素后,是否就一定能为bean对象创建代理呢?这将是接下来要介绍的内容。
2. 不创建代理情况
2.1 环境准备
先准备基础环境进行接下来的测试使用
@Service
public class Service {
public void save() {
System.out.println("Service save...") ;
}
}
将围绕该Service创建代理
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.pack..*.*(..))")
private void log() {
}
@Before("log()")
public void recordLog() {
System.out.println("before log...") ;
}
}
该切面定义了一个前置通知,切入点匹配com.pack包及其子包下的所有方法。
2.2 正常创建代理
到此,以上定义没有任何特殊的程序能正常的创建代理,如下示例:
ConfigurableApplicationContext context = SpringApplication.run(App.class, args) ;
Service service = context.getBean(Service.class);
System.out.println(service.getClass()) ;
service.save();
输出结果
class com.pack.Service$$SpringCGLIB$$0
before log...
Service save ...
正常通过cglib创建代理对象。
2.3 不创建代理
- Service实现Advice接口
public class Service implements Advice {}
再次运行后,输出结果
class com.pack.Service
Service save...
没有创建代理,没有执行通知方法。
- Service实现Pointcut接口
public class Service implements Pointcut {
// 该接口需要实现下面2个方法
// 这里无所谓,默认实现即可
public ClassFilter getClassFilter() {
return null;
}
public MethodMatcher getMethodMatcher() {
return null;
}
}
输出结果:
class com.pack.Service
Service save...
同样,没有创建代理:
- Service实现AopInfrastructureBean接口
public class Service implements AopInfrastructureBean {}
该接口没有任何方法标记接口基础设施类,输出结果
class com.pack.Service
Service save...
没有创建代理
- Service实现Advisor接口
Spring创建代理对象,底层实现即使你通过注解@Aspect方式声明的切面都会将其转换为Advisor这种低级切面。
Advisor接口只有一个抽象方法。
public class Service implements Advisor {
// 空实现即可
public Advice getAdvice() {
return null ;
}
}
输出结果与上面一样,同样不会创建代理。
- 特殊的beanName
给Service一个特殊的beanName。
@Component("com.pack.Service.ORIGINAL")
public class Service {}
这个beanName以当前的完整包名+类名+.ORIGINAL命名,输出结果:
class com.pack.Service
Service save...
没有创建代理,修改beanName:
@Component("xxxooo.ORIGINAL")
当修改成上面的名称后,再次运行:
class com.pack.Service$$SpringCGLIB$$0
before log...
Service save ...
被代理了,这说明beanName只有是"完整包名+类名+.ORIGINAL"才不会创建代理对象。
- 特殊的Advisor
该情况非常特殊也比较复杂,直接上代码:
@Component
public class LogAdvisor extends AspectJPointcutAdvisor {
public LogAdvisor(AbstractAspectJAdvice advice) {
super(advice);
}
@Override
public String getAspectName() {
return "service" ;
}
}
只要上面getAspectName方法返回值与对应Service的beanName一致也将不会创建代理。