aop就是面向切面编程,是为了解耦而生。aop是不仅工作常用而且面试也经常问,所以看aop源码是非常有必要的。
在看源码之前,我们先要带着结论去看源码,而不是一上来看的一脸懵逼,一定要先总后分。
aop的流程就是在spring的生命周期过程中,会缓存所有的切面,然后在bean初始化的时候,它会找当前bean的所有方法是否跟缓存中的切面有匹配的,如果匹配,说明需要代理并创建代理对象,当调用被增强方法时,执行代理逻辑。
本文基于springboot2.3.5.RELEASE,如果有错,欢迎指出。
依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
</dependencies>
被代理类
@Component
public class User {
int id;
String password;
String username;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public User(){
}
public User(int id, String password, String username) {
this.id = id;
this.password = password;
this.username = username;
}
public String sleep() {
return "睡觉";
}
}
切面类
@Component
@Aspect
public class AspectUser {
@Pointcut("execution(* *.sleep(..))")
public void test(){}
@Before("test()")
public void before(){
System.out.println("刷牙");
}
@After("test()")
public void after() {
System.out.println("关灯");
}
@Around("test()")
public Object around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("洗脸");
Object o = jp.proceed();
System.out.println("上床");
return o;
}
@AfterReturning(pointcut = "test()",returning = "result")
public void afterReturning(JoinPoint jp,Object result) {
System.out.println("闭眼");
System.out.println(result);
}
@AfterThrowing(pointcut = "test()",throwing = "e")
public void afterThrowing(JoinPoint jp,Throwable e) {
System.out.println("没有睡意");
}
}
增强结果
源码解析
当开启aop的时候,在启动类上加上@EnableAspectJAutoProxy即可,所以看aop源码的第一步,就是看这个注解。
spring会解析启动类上的@Import注解,而@Import注解中的类实现ImportBeanDefinitionRegistrar接口 ,所以会调用这个接口的registerBeanDefinitions方法。关于如何解析,这里就不说了,有兴趣的可以搜下spring如何解析各种注解。(后续我也会出篇文章)
我们直接看registerBeanDefinitions方法。
可以看到往spring容易内注入一个
名字为org.springframework.aop.config.internalAutoProxyCreator
class为AnnotationAwareAspectJAutoProxyCreator的beanDefinition
那么,AnnotationAwareAspectJAutoProxyCreator这个类就是aop的关键
AnnotationAwareAspectJAutoProxyCreator的类图如下
从类图上得出结论AnnotationAwareAspectJAutoProxyCreator是个BeanPostProcessor(BeanPostProcessor是spring的扩展机制)
我们来到AbstractApplicationContext的refresh()方法中的finishBeanFactoryInitialization方法,在这个方法中会完成bean的实例化初始化。
protected boolean isInfrastructureClass(Class<?> beanClass) {
boolean retVal = Advice.class.isAssignableFrom(beanClass) || Pointcut.class.isAssignableFrom(beanClass) || Advisor.class.isAssignableFrom(beanClass) || AopInfrastructureBean.class.isAssignableFrom(beanClass);
if (retVal && this.logger.isTraceEnabled()) {
this.logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]");
}
return retVal;
}
判断当前bean是否是基础类或者是否有@Aspect注解
在isInfrastructureClass方法中会判断你是否是基础类如Advice,Pointcut,Advisor,
AopInfrastructureBean,是则加入缓存中
从spring容器内部拿到所有advisor的名字(我这里拿不到,因为我是用的切面类@Aspect)
拿到所有advisor的名字去spring内部拿(经过Bean的生命周期),然后加入advisors这个list中
首先在bean工厂拿到所有的Bean名字
遍历所有的name,根据name获得class,然后通过反射判断是否有注解@Aspect(只有我们的AspectUser能进到这里)
解析我们的切面类,将切面类里面的增强方法封装成Advisor
获取增强方法
获取所有没有@Pointcut注解的方法(注意没有被增强的方法也被获取)
将需要增强的方法封装成Advisor
获取表达式Pointcut,如果没有,说明不需要增强,有的话封装成Advisor并返回(这里不同advisor的advice类型不一样,感兴趣的自己看一下)
总结一下,在bean的周期过程中,实例化之前,会调用AnnotationAwareAspectJAutoProxyCreator的resolveBeforeInstantiation方法,
在这个方法中,会找到容器内中的所有Advisor,并找到容器内的切面类然后解析成Advisor,最终这些Advisor被加到缓存中。
下面就是被增强类的初始化了。
把断点打到User的bean创建过程
先经过实例化,此时User对象还不是代理类,属性都是空
经过属性注入后,初始化Bean
遍历到AnnotationAwareAspectJAutoProxyCreator
判断User这个Bean是否需要代理
找到特定的Advisor,也就是开始匹配切面,如果不为空,说明需要代理
从缓存里面找到所有Advisor(这里不进去了,前面看过了),然后进行匹配
开始匹配
返回匹配成功的advisor,接下来就是创建代理了
创建一个代理工厂,设置advisor跟被代理类,然后生成代理
判断选择用jdk代理还是cglib代理(我这里用的cglib)
创建代理的过程就不看了。
经过初始化后置增强后,bean对象已经被换成代理类了。
当调用被增强方法时,就会被拦截下来
执行@Around
又回到这里
执行@Before
执行完@Before
来到@After
先放行,在finally最终执行after增强逻辑
来到@AfterReturning
先放行,在执行增强逻辑
执行@AfterThrowing
放行,抛异常在catch代码块执行增强逻辑
执行原方法后返回
开始执行AfterReturing增强逻辑
执行After增强逻辑
执行@Around环绕后的增强逻辑
总结,spring aop在bean实例化前置增强时加载好切面并放入缓存,被代理类经过初始化后置增强时,将被代理类所有方法跟缓存中的切面匹配,如果匹配,则生成代理类。当调用被增强方法时,使用责任链加递归的方式执行切面逻辑。