1. 前言
AOP,英文全称是Aspect Oriented Programming,也叫作面向切面编程。预先定义一个或多个切入点,当程序执行到切点的方法时,会先执行切面相关处理逻辑,再执行原程序代码。
注:本篇文章会结合Spring生命周期源码,介绍AOP是如何整合到Sping容器管理。不会过多地介绍一些基础知识,阅读之前,最好对AOP、CGLIB、Proxy有个基础的了解。
Spring通过动态代理实现AOP,用语言表述可能不大清楚,下面画一张图来对比一下
2. 代理示例
- 1.创建Service接口:
public interface MyService {
void test();
}
- 2.创建ServiceImpl实现类,记得加 @Service 注解,表示由Spring容器管理:
@Service
public class MyServiceImpl implements MyService {
@Autowired
private A a;
@Override
public void test() {
System.out.println("调用MyService.test");;
}
@PostConstruct
public void init(){
System.out.println("MyServiceImpl PostConstruct");
}
}
- 3.创建一个@Component标记的常规类:
@Component
public class A {
public void test(){
System.out.println("a.test...");
}
}
- 4.创建启动类,注意看这时候没有加注解 @EnableAspectJAutoProxy ,因此Spring不会启用AOP功能:
@ComponentScan("com.example")
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(App.class);
A a = ac.getBean(A.class);
a.test();
System.out.println(a.getClass());
System.out.println("------------------------------------------");
MyService service = ac.getBean(MyService.class);
service.test();
System.out.println(service.getClass());
}
}
- 5.执行程序,输出结果如下。此时,所有的类都是普通的JAVA对象:
MyServiceImpl PostConstruct
a.test...
class com.example.demo.A
------------------------------------------
调用MyService.test
class com.example.service.impl.MyServiceImpl
- 6.接下来,准备实现AOP了。在前面的基础上,创建“切面类” TestAspect:
@Component
@Aspect
public class TestAspect {
//这里通过通配符,表示之前的A类和MyServiceImpl类都会被代理。
//具体的@Pointcut配置可以查看官网https://docs.spring.io/spring-framework/docs/5.2.13.RELEASE/spring-framework-reference/core.html#spring-core
@Pointcut("execution(* com.example.*.*.*(..))")
public void myPointCut(){}
@Before("myPointCut()")
public void before(){
System.out.println("before");
}
}
- 7.在启动类增加 @EnableAspectJAutoProxy 注解,然后重新运行程序,新的执行结果如下。此时,A对象变成CGLIB创建的动态代理对象,而service变成JDK创建的动态代理对象:
MyServiceImpl PostConstruct
before
a.test...
class com.example.demo.A$$EnhancerBySpringCGLIB$$7c975a0a
------------------------------------------
before
调用MyService.test
class com.sun.proxy.$Proxy23
3. 问题分析
问题1:Spring在什么时机点进行AOP处理?
答案1:
- 在创建Bean对象之后,调用后置处理器方法AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization()创建动态代理实现
看下面这张图,描述了Bean创建的过程和AOP的调用时机
问题2:上一步提的处理器AnnotationAwareAspectJAutoProxyCreator没有加@Component注解,为什么能被Spring扫描到并起作用?
答案2:----------------------------------------------------------------------------------------
-
Spring AOP生效必须加注解@EnableAspectJAutoProxy,该注解使用@Import将AspectJAutoProxyRegistrar加入了Spring容器
-
AspectJAutoProxyRegistrar是实现ImportBeanDefinitionRegistrar接口的处理器,在Spring扫描类的过程中,会调用所有实现类的 registerBeanDefinitions 方法
-
AspectJAutoProxyRegistrar#registerBeanDefinitions() 将 AnnotationAwareAspectJAutoProxyCreator 加入了 Spring 容器
问题3:Spring采用哪种动态代理机制,CGLIB还是JDK?
答案3:----------------------------------------------------------------------------------------
- 默认情况下,实现了业务接口的Bean会采用JDK动态代理,例如:ServiceImpl。其他情况下,一般会采用CGLIB动态代理。
- 设置注解 @EnableAspectJAutoProxy 的属性 proxyTargetClass = true,会强制 CGLIB 动态代理
修改之前的例子,使用注解 @EnableAspectJAutoProxy(proxyTargetClass = true) ,重新运行程序,执行结果如下:
MyServiceImpl PostConstruct
before
a.test...
class com.example.demo.A$$EnhancerBySpringCGLIB$$7c975a0a
------------------------------------------
before
调用MyService.test
class com.example.service.impl.MyServiceImpl$$EnhancerBySpringCGLIB$$98d88524
看吧… 全部变成CGLIB创建的代理对象
4. 结尾
本篇的AOP基本原理就介绍到这里了,后面有新的想法会不断补充,也欢迎大家提出新的见解。