- 三哥
内容来自【自学星球】
欢迎大家来了解我的星球,和星主(也就是我)一起学习 Java ,深入 Java 体系中的所有技术。我给自己定的时间是一年,无论结果如何,必定能给星球中的各位带来点东西。
想要了解更多,欢迎访问👉:自学星球
--------------SSM系列源码文章及视频导航--------------
创作不易,望三连支持!
SSM源码解析视频
👉点我
Spring
- Spring 中注入 Bean 的各种骚操作做
- Spring 中Bean的生命周期及后置处理器使用
- Spring 中容器启动分析之refresh方法执行之前
- Spring refresh 方法分析之一
- Spring refresh 方法之二 invokeBeanFactoryPostProcessors 方法解析
- Spring refresh 方法分析之三
- Spring refresh 方法之四 finishBeanFactoryInitialization 分析
- Spring AOP源码分析一
- Spring AOP源码分析二
- Spring 事务源码分析
SpringMVC
MyBatis
- MyBatis 源码分析之 SqlSessionFactory 创建
- MyBatis 源码分析之 SqlSession 创建
- MyBatis 源码分析之 Mapper 接口代理对象生成及方法执行
- MyBatis 源码分析之 Select 语句执行(上)
- MyBatis 源码分析之 Select 语句执行(下)
- MyBatis 源码分析一二级缓存
---------------------【End】--------------------
经历了漫长的 Spring 启动流程分析之后,我们也是来到了 Spring 中的另一个重点 AOP 相关的内容学习。
回顾 Spring 的启动流程,你们还记得那几个点和 AOP 有着千丝万缕的联系嘛!
- resolveBeforeInstantiation 方法
- 第三级缓存
- @EnableAspectJAutoProxy
- 标签<aop:aspectj-autoproxy />
上面这四种是我已知的和 AOP 相关的点,下面我就会重点对其进行分析。
按照惯例,先写一个 AOP 案例。
一、AOP 例子
1、编写切面
@Aspect
@Component
public class AspectJTest {
// 切入点表达式
@Pointcut("execution(* cn.j3code.studyspring.aop..*.*(..))")
public void test() {
}
// 前置通知
@Before("test()")
public void beforeTest() {
System.out.println("beforeTest。。。");
}
// 后置通知
@After("test()")
public void afterTest(JoinPoint p) {
System.out.println("afterTest。。。");
}
// 返回通知
@AfterReturning(pointcut = "test()", returning = "result")
public void afterReturningTest(JoinPoint p, Object result) {
System.out.println("afterReturningTest。。。" + result);
}
// 异常返回通知
@AfterThrowing("test()")
public void afterThrowingTest(JoinPoint p) {
System.out.println("afterThrowingTest。。。");
}
// 环绕通知
//@Around("test()")
public Object aroundTest(ProceedingJoinPoint p) {
System.out.println("before。。。");
Object proceed = null;
try {
proceed = p.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("after。。。");
return proceed;
}
}
2、编写配置类
@EnableAspectJAutoProxy // 开启AOP
@Configuration
@ComponentScan("cn.j3code.studyspring.aop")
public class AspectJConfiguration {
}
3、编写目标类
@Service
public class AspectJService {
public void add() {
System.out.println("AspectJService====add..........");
}
}
4、测试
public class AspectJMain {
public static void main(String[] args) {
// 读取配置文件启动
AnnotationConfigApplicationContext annotationApplicationContext =
new AnnotationConfigApplicationContext(AspectJConfiguration.class);
AspectJService bean = annotationApplicationContext.getBean(AspectJService.class);
bean.add();
}
}
大家可以关注一下配置文件上的一个注解 @EnableAspectJAutoProxy ,它非常重要,如果没有它我们的 AOP 功能将不会起效果。如果大伙是 XML 配置文件的形式启动 Spring 容器,那么则需要在配置文件中配置下面这个标签:
<aop:aspectj-autoproxy/>
ok,例子给大家讲完了,那再来看看 Spring AOP 中的一些术语。
二、AOP 术语
在 Spring AOP 源码中有很多相关的类基本上都是和这些术语有关,所以先了解这个,有助于理解源码。
1、通知(Advice)
通知也可以说是增强,其可分为:前置通知、后置通知、异常通知、最终通知与环绕通知。
2、连接点(JoinPoint)
连接点就是被代理类中的所有方法。
3、切入点(Pointcut)
切入点是被增强过的方法,注意这里它和连接点的区别,有些方法可能并没有被增强,而是直接返回,这些方法只是连接点,并不是切入点。切入点一定是连接点,连接点不一定切入点。
4、切面(Aspect)
切面是通知和切入点的结合,就是上面我们编写的 AspectJTest 类。
5、引入(introduction)
向现有的类添加切面中定义的通知方法,即将切面用到目标类中。
6、目标(target)
被代理的对象,如上面例子中的 AspectJService 类。
7、代理(proxy)
代理出来的对象。
8、织入(weaving)
把切面应用到目标对象来创建新的代理对象的过程。
三、resolveBeforeInstantiation 返回代理的机制
在 Bean 的创建流程中 resolveBeforeInstantiation 方法的作用时机如下图:
由图可知,该方法的返回代理时机是不参与 Bean 的生命周期流程的,即返回的代理对象没有执行 Bean 的生命周期流程。
方法源码:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
Object bean = null;
if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
// Make sure bean class is actually resolved at this point.
// mbd不是合成的,并且BeanFactory中存在InstantiationAwareBeanPostProcessor
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
// 解析beanName对应的Bean实例的类型
Class<?> targetType = determineTargetType(beanName, mbd);
if (targetType != null) {
// 实例化前的后置处理器应用(处理InstantiationAwareBeanPostProcessor)
bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
if (bean != null) {
// 如果返回的bean不为空,会跳过Spring默认的实例化过程
// 所以只能在这里调用BeanPostProcessor实现类的PostProcessorsAfterInitialization方法
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
}
}
// 如果bean不为空,则将beforeInstantiationResolved赋值为true,代表在实例化之前已经解析
mbd.beforeInstantiationResolved = (bean != null);
}
return bean;
}
这里我们的重点就是两个 BeanPostProcessors 应用方法,既 applyBeanPostProcessorsBeforeInstantiation 与 applyBeanPostProcessorsAfterInitialization 分别干了些什么事情。
先看 applyBeanPostProcessorsBeforeInstantiation 方法源码。
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation
protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
// 遍历当前BeanFactory中的BeanPostProcessor
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// 应用InstantiationAwareBeanPostProcessor后置处理器,允许PostProcessorBeforeInstantiation方法返回bean对象的代理
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
// 执行postProcessBeforeInstantiation方法,在Bean实例化前操作,
// 该方法可以返回一个构造完成的Bean实例,从而不会继续执行创建Bean实例的"正规流程",达到"短路"效果
Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
if (result != null) {
// 如果result不为null,也就是有后置处理器返回了bean实例对象,则会跳过Spring默认的实例化过程。
return result;
}
}
}
return null;
}
该方法的执行流程我们是不是已经看过很多这种的了,先获取容器中的所有后置处理器,然后挨个遍历并执行对应的处理器方法。
所以这次我们的重点放到 postProcessBeforeInstantiation 方法干了些啥事。
那么问题来了,这个方法我们是进入那个类呢!这里我就直接说了,这个:AbstractAutoProxyCreator。
因为我们是分析 AOP 所以看名字我们也知道是进入这个类。
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
// 获取BeanClass的缓存key
Object cacheKey = getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
// advisedBeans保存了所有已经做过动态代理的Bean
// 如果被解析过则直接返回
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
// 判断当前bean是否是基础类型:是否实现了Advice,Pointcut,Advisor,AopInfrastructureBean这些接口或是否是切面(@Aspect注解)
// 判断是不是应该跳过 (AOP解析直接解析出我们的切面信息,而事务在这里是不会解析的)
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
// Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
// 获取用户自定义的targetSource, 如果存在则直接在对象实例化之前进行代理创建,
// 避免了目标对象不必要的实例化
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
// 如果有自定义targetSource就要这里创建代理对象
// 这样做的好处是被代理的对象可以动态改变,而不是值针对一个target对象(可以对对象池中对象进行代理,可以每次创建代理都创建新对象
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
// 获取Advisors, 这个是交给子类实现的
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
// 创建代理对象
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
// 返回代理的对象
return proxy;
}
return null;
}
这个方法主要分两个部分:
- 第一部分:容器中没有 TargetSource 类型 Bean,方法返回 null 。
- 第二部分:容器中有 TargetSource 类型 Bean,方法返回 TargetSource 的代理。
先来说说第一部分,如果第一部分成立,那么就会走 if 分支。根据源码意思,如果需要代理的类已经存在了那就不用在进行代理了直接返回 null 。如果不成立,那么继续判断需要代理的类是不是切面类或者需要跳过,当然结果还是返回 null 。
这里有个重点就是是否需要跳过也即:shouldSkip(beanClass, beanName) 方法。
这里我先说该方法重点干了些啥,具体的源码分析留到后面一起分析。就是 shouldSkip 方法会提前将我们的切面类进行解析分析出切面中的通知方法,后续就可以直接从缓存中获得切面类中的各种通知方法。
再来说说第二部分,如果第二部分成立,那么毫无疑问程序中存在 TargetSource 类型的 Bean 需要 Spring 进行相关代理,那么 TargetSource 代理又是个什么鬼,这里我给大家找来了几个资料,如下
资料一:https://www.icode9.com/content-4-1380416.html
总结 resolveBeforeInstantiation 方法的几个重要点:
1、如果程序中不存在 AOP 功能,那么 resolveBeforeInstantiation 就相当于空方法。
2、如果存在 AOP 功能,那么如果程序中如果没有 TargetSource 类型的相关 Bean ,该方法则只会做一件事情就是 shouldSkip 方法的提前解析切面类。
3、如果程序中存在 TargetSource 类型 Bean ,那么程序会对该类型进行相关代理,返回代理对象提前结束 Bean 的创建流程,也即短路。
四、三级缓存返回代理的机制
这里我说的是三级缓存返回代理而不是循环依赖,这个要搞清楚。
不过,如果要分析三级缓存的话,还真要把循环依赖这个东西抛出来说一说,不然很难知道为啥出现一个三级缓存。
4.1 循环依赖
循环依赖的概念在网上已经有很多了,这里就不详细说概念了,那何为循环依赖,简单讲就是 A 中有个属性 B ,B 中有个属性 A ,在实例化 A 和 B 的时候出现的循环引用现象称为循环依赖。
那这个能解决吗?
- set 方式能解决
- 构造器方式不能解决
- 不全是构造器方式也能解决
怎么理解上面的三种情况呢!
第一种:
第二种:
第三种:
前面两种好理解,那第三种是怎么理解呢!
第三种的理解方式为,如果最先创建的 Bean 中属性注入的方式非构造器方式注入则能解决循环依赖,反之则不能,剧透原因会在源码中找到答案。
4.2 三级缓存解决循环依赖
我们按照 4.1 节中的第一种情况分析循环依赖问题。
先来看看 A 和 B 对象的创建的整体流程:
我觉得上面的图已经画的非常清楚了,其中有两个方法尤为重要
- getSingleton(beanName):提前从缓存中获取对象
- addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)):将为创建完的对象放到缓存中
当 A 进行创建的时候,会将半成品放入到缓存中,然后再执行 A 的属性赋值。当发现 A 中有个 B 类型的属性需要赋值时,则会反过来创建 B ,并将半成品 B 放入到缓存中,然后执行 B 的属性赋值。而 B 中又刚好有个 A 属性需要赋值,则正好可以从缓存中获取 A 的半成品进行赋值,最终 B 对象完成属性赋值,创建完成,接着 A 对象的 B 属性也赋值成功,A 对象也创建成功。
现在来解答为啥构造器方式注入不能解决循环依赖:
答:因为执行调用构造器创建对象在将对象放入缓存的流程之前,所以当调用构造器时发现需要自动装配而去缓存中查找时,会找不到对应的值,所以就不能解决此循环依赖。(同理上面循环依赖的第三种)
那最后我们再聊聊为什么三级缓存和 AOP 代理有关系。
来到从缓存中获取对象的方法
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从单例对象缓存中获取beanName对应的单例对象
Object singletonObject = this.singletonObjects.get(beanName);
// 如果单例对象缓存中没有,并且该beanName对应的单例bean正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 尝试给一级缓存对象加锁,因为接下来就要对缓存对象操作了
synchronized (this.singletonObjects) {
// 从早期单例对象缓存中获取单例对象(之所称成为早期单例对象,是因为earlySingletonObjects里
// 的对象的都是通过提前曝光的ObjectFactory创建出来的,还未进行属性填充等操作)
// 尝试从二级缓存earlySingletonObjects这个存储还没进行属性添加操作的Bean实例缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果在早期单例对象缓存中也没有,并且允许创建早期单例对象引用
// 如果还没有获取到并且第二个参数为true,为true则表示bean允许被循环引用
if (singletonObject == null && allowEarlyReference) {
// 从单例工厂缓存中获取beanName的单例工厂
// 从三级缓存singletonFactories这个ObjectFactory实例的缓存里尝试获取创建此Bean的单例工厂实例
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 如果获取到工厂实例
if (singletonFactory != null) {
// 如果存在单例对象工厂,则通过工厂创建一个单例对象
singletonObject = singletonFactory.getObject();
// 将通过单例对象工厂创建的单例对象,放到早期单例对象缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放到earlySingletonObjects缓存了,
// 因此,后续获取beanName的单例对象,可以通过earlySingletonObjects缓存拿到,不需要在用到该单例工厂
this.singletonFactories.remove(beanName);
}
}
}
}
// 返回单例对象
return singletonObject;
}
这段代码基本上就是从三个 Map 中获取对象,而真正返回对象的确实下面这行代码
singletonFactory.getObject()
而这个方法会则会调用存入时传入的匿名内部来中的方法,也既下面代码
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
// 调用 SmartInstantiationAwareBeanPostProcessor 类型的 getEarlyBeanReference 方法获取对象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
这个方法就有意思了,如果程序中没有 SmartInstantiationAwareBeanPostProcessor 类型的后置处理器就相当于下面这个代码
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
return exposedObject;
}
所以 SmartInstantiationAwareBeanPostProcessor 类型是啥?
我们看一下这个类的继承结构图
不难发现,是和 AOP 相关。
而且我们进入到 ibp.getEarlyBeanReference 方法中也可以发现只有下面这个方法有内容,其他都是不作处理
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
this.earlyProxyReferences.add(cacheKey);
}
// 代理 Bean 的流程(后面会分析这个方法)
return wrapIfNecessary(bean, beanName, cacheKey);
}
所以,通过以上一系列的推导,当 A 对象创建的时候注入 B 属性类型时,如果 B 类型是被 AOP 切面的切入点表达式切到的,那么自动装配的就不是 B 的普通类型了,而是 B 的代理类型。
那我们趁热打铁,再来说说面试中常问的,为啥 Spring 中要有三级缓存解决循环依赖而不是二级缓存
?
来,我们结合上面的内容答:从第三级缓存中拿到的对象有两种,代理对象或普通对象。如果只有二级缓存,那么在自动装配的时候,从二级缓存中拿到的对象到底是普通对象呢还是代理对象呢!如果二级缓存都存普通对象显然不合适,因为自动装配的有可能是个代理对象;那我二级缓存都存放代理对象总可以了吧!这也不行,因为自动装配的对象有可能不需要代理。所以面对这种情况,第三级缓存横空出世!需要自动装配的对象,必须先从第三级缓存中拿,在第三级缓存中完成普通对象或代理对象的处理。
好了,今天的内容到这里就结束了,我是 【J3】关注我,我们下期见
。
-
由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。
-
如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。
-
感谢您的阅读,十分欢迎并感谢您的关注。