1. 容器 API

image.png

  • BeanDefinitionRegistry: 定义对 BeanDefinition 的各种增删改查操作
  • DefaultSingletonBeanRegistry: Bean 仓库
  • AutowireCapableBeanFactory: 提供创建 Bean, 自动注入, 初始化已经应用 Bean 的后处理器
  • ConfigurableListableBeanFactory: BeanFactory 配置清单, 指定忽略类型及接口等
  • DefaultListableBeanFactory: Bean 工厂

2. 工作原理

2.1. 工作流程

  1. IOC 是什么
  2. Bean 的声明方式
  3. IOC 的工作流程

IOC 的全称是 Inversion Of Control, 也就是控制反转,它的核心思想是把对象的管理权限交给容器。应用程序如果需要使用到某个对象实例,直接从 IOC 容器中去获取就行,这样设计的好处是降低了程序里面对象与对象之间的耦合性。使得程序的整个体系结构变得更加灵活。

2.1.1. Bean 定义注册

Spring 里面很多方式去定义 Bean,(如图)比如 XML 里面的 <bean> 标签、@Service、 @Component、@Repository、@Configuration 配置类中的@Bean 注解等等。 Spring 在启动的时候,会去解析这些 Bean 然后保存到 IOC 容器里面。
image.png

2.1.2. IOC 容器的初始化

这个阶段主要是根据程序中定义的 XML 或者注解等 Bean 的声明方式 (如图)通过解析和加载后生成 BeanDefinition,然后把 BeanDefinition 注册到 IOC 容器
image.png
通过注解或者 xml 声明的 bean 都会解析得到一个 BeanDefinition 实体,实体中包含 这个 bean 中定义的基本属性。 最后把这个 BeanDefinition 保存到一个 Map 集合里面,从而完成了 IOC 的初始化。 IoC 容器的作用就是对这些注册的 Bean 的定义信息进行处理和维护,它 IoC 容器控制 反转的核心。

2.1.3. Bean 初始化及依赖注入

然后进入到第二个阶段,这个阶段会做两个事情(如图) 1. 通过反射针对没有设置 lazy-init 属性的单例 bean 进行初始化。 2. 完成 Bean 的依赖注入。
image.png

2.1.4. Bean 的使用

(如图)通常我们会通过@Autowired 或者 BeanFactory.getBean() 从 IOC 容器中获取指定的 bean 实例。另外,针对设置 lazy-init 属性以及非单例 bean 的实例化,是在每次获取 bean 对象的时候,调用 bean 的初始化方法来完成实例化的,并且 Spring IOC 容器不会去管理这些 Bean
image.png

2.2. 生命周期

Spring 加载流程图 (徐庶)
https://www.processon.com/view/link/5f15341b07912906d9ae8642

3. 容器刷新

Spring 容器刷新 12 步详细
https://www.processon.com/diagraming/639548487d9c084a6a3d4021

4. 循环依赖

4.1. 循环依赖的种类⭐️🔴

4.1.1. 属性注入依赖

通过三级缓存解决 ❕%%
2153-🏡⭐️◼️Spring 是如何解决循环依赖的?🔜MSTM📝◼️⭐️-point-202301282153%%

4.1.2. 构造方法依赖

解决方案:Spring-2、IOC

懒加载原理:Spring-9、@Lazy

4.1.3. 多例模式

无解

4.2. 变更历史

大概是在Spring2.0.3加入了循环依赖的解决方案,在 springBean 的生命周期管理之后加入的新代码。其中涉及到了代理类生成时机的冲突问题
Spring 正常的代理应该是发生在 bean 初始化后,由 AbstractAutoProxyCreator.postProcessAfterInitialization 处理。而循环依赖要求 bean 在填充属性前就提前生成代理,否则就会出现最终版本不一致错误。所以 Spring 在代码中开了个口子,循环依赖发生时,提前代理,没有循环依赖,代理方式不变,依然是初始化以后代理。
其实也可以不分提前代理和正常代理,全部的 Bean 都直接提前生成代理,但那样的话 AbstractAutoProxyCreator.postProcessAfterInitialization 直接废了,相当于把原本的逻辑推翻重写,如此只是为了解决循环依赖的话就会变得得不偿失,没有完全必要的情况下对核心代码大改甚至推翻重写是一种大忌。

而三级缓存的实现提供了提前生成代理的口子,而不是直接生成代理,只有发生循环依赖执行 getObject 才会生成代理,达到上述循环依赖发生时,提前代理,没有循环依赖,代理方式不变,依然是初始化以后代理的目的。

原文链接:https://blog.csdn.net/u012098021/article/details/107352463/

4.2.1. 关闭默认支持循环依赖

SpringBoot2.6 之后关闭了默认支持循环依赖的开关
开启方法: spring.main.allow-circular-references=true

https://juejin.cn/post/7096798740593246222

4.3. 循环依赖流程⭐️🔴

https://www.processon.com/diagraming/639c04ed1efad465c9cd90da
https://www.processon.com/diagraming/639e8c7f1efad465c9cfdd1a

https://www.processon.com/view/link/5f1fb2cf1e08533a628a7b4c

4.3.1. 各级缓存的加入时机

image.png

4.3.1.1. 一级缓存

Map<String, Object> singletonObjects
new ConcurrentHashMap<>(256)
解决完整 Bean 与不完整 Bean 混乱问题

4.3.1.2. 二级缓存

%%
▶39.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-1708%%
❕ ^kyatt6

Map<String, Object> earlySingletonObjects
new ConcurrentHashMap<>(16)
提供缓存功能,以便从中获取原始或者代理对象,同时能够解决动态代理多次创建问题
只要有循环依赖,不管有没有 AOP,earlySingletonObjects 里都会有数据,比如 ABA,AB 在 3 级缓存中都有数据,B 在后面填充属性 A 时将 A 的三级缓存用掉,生成半成品 A 放入 2 级缓存中,3 级缓存的 A 被删除。而 B 直接从 3 级来到 1 级。
只是二级缓存中可能是原始 Bean,也可能是代理 Bean

4.3.1.3. 三级缓存

Map<String, ObjectFactory<?>> singletonFactories
new HashMap<>(16)

实例对象创建之后,属性填充之前
检测循环依赖条件:单例&允许循环依赖&当前 bean 正在创建中
解决死循环问题

不管有没有循环依赖,三级缓存中都会有数据

4.4. 循环依赖问题汇总

4.4.1. 为什么一级二级是 ConcurrentHashMap

因为先手线程加锁加在查询一级、二级缓存之后,所以一级、二级缓存的查询需要支持并发而不影响性能

https://www.bilibili.com/video/BV1pe4y1h7wK?p=19&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204


https://www.bilibili.com/video/BV1ET4y1N7Sp/?spm_id_from=333.337.search-card.all.click&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

debug 了 AOP+ 循环依赖

 zzzz

最新 Spring 全家桶 -2021 面试 - 重灾区:Spring/SpringCloud/SpringBoot/SpringMVC,肝完就跳槽

https://www.bilibili.com/video/BV1x64y1x71b?p=56

https://www.bilibili.com/video/BV1pY4y1A7Pv/?spm_id_from=333.337.search-card.all.click&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204![](https://raw.githubusercontent.com/TaylorLuo/typora-imgs/master/img20221216101945.png)

4.5. AOP 提前代理逻辑

4.5.1. 正常代理

对象初始化之后创建动态代理,这是 SpringBean 的正常生命周期
BeanPostProcessor.postProcessAfterInitialization(),具体实现类是AbstractAutoProxyCreator

image.png

4.5.2. 提前代理

4.5.2.1. getEarlyBeanReference

%%
▶32.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230323-1933%%
❕ ^qideqv

getEarlyBeanReference 方法就是为了生成提前代理对象而设计的。
如果有代理❕%%
▶9.🏡⭐️◼️项目中开启 AOP 的情况 ?🔜MSTM📝 项目中使用了@EnableAspectJAutoProxyCreator 或者@EnableTransactionManagement 注解,或者是 SpringBoot 项目 (自动开启 AOP),并且代码中使用了切面及通知方法◼️⭐️-point-20230303-1940%%
,那么在 6. registerBeanPostProcessors() 中会将提供 AOP 功能的 Bean(AnnotationAwareAspectJAutoProxyCreator) 注册完成,然后在后面的 getEarlyBeanReference(beanName, mbd, bean) 方法中就会走 AbstractAutoProxyCreator 的实现,返回 proxy。否则,如果没有 AOP 代理,那么会走 SmartInstantiationAwareBeanPostProcessor 的默认实现,返回正常 bean

image.png

4.5.2.2. 细节

防止提前代理后,正常代理再生成一次代理

https://blog.csdn.net/u012098021/article/details/107352463/

代理对象示例

4.5.2.3. 产生的影响⭐️🔴

%%
▶10.🏡⭐️◼️【如果开启了代理,并且当前 Bean 使用了切面或者事务就会注入动态代理】◼️⭐️-point-20230303-2023%%

@Autowired 自动注入的对象有可能会是代理对象,取决于 Bean 是否使用了 AOP 注解
spring 很多功能都是通过 aop 来实现,比如事务,缓存注解,异步、还有一些自定义的 aop 等等,而 aop 是通过动态代理来实现的,spring 主要用到的动态代理有 jdk 的动态代理和 cglib。

  • Spring 在没有使用 aop 的时候自动注入的时候是原始类型对象
  • 在发生 aop 的时候,若代理对象有实现接口,则默认会使用 jdk 动态代理
  • 在发生 aop 的时候,若代理对象没有实现接口,则默认会使用 cglib 动态代理
  • jdk 动态代理必须有实现接口
  • 可以强制使用 cglib 来做 spring 动态代理

示例代码: https://xie.infoq.cn/article/29ce37bd3f6f4c5b405549c06

4.5.3. 二级缓存的作用

  1. 如果调用 getEarlyBeanReference 生成动态代理对象立即返回,不使用二级缓存来缓存起来,就会出现多次生成同一个对象的动态代理的情况,比如 B、C 都与 A 循环依赖,那么不用缓存判断,直接返回的话,就会生成 2 次 A 的代理对象 ❕%%
    1519-🏡⭐️◼️二级缓存的作用是什么?🔜MSTM📝 除了缓存正常对象的半成品对象,最主要的功能是防止重复生成动态代理对象◼️⭐️-point-202301291519%%

    image.png

  2. 如果提前代理,后面需要从二级缓存中取出返回出去。

4.5.4. 测试程序

[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/spring-framework/springsource-test/src/main/java/com/atguigu/spring/config/MainConfig.java]]

image.png
https://www.bilibili.com/video/BV15b4y117RJ?p=188&spm_id_from=pageDriver&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

4.6. @Async 导致循环依赖报错⭐️🔴

4.6.1. 原因分析

%%
0836-🏡⭐️◼️一句话概括@Async 导致循环依赖报错的原因🔜MSTM📝 一句话:发生循环依赖时,没有像 AOP 一样做到提前代理:在实例化之后放入三级缓存,在属性填充时真正用到的时候调用 singletonFactory.getObject() 时根据是否用到 AOP 来决定填充一个代理对象。@EnableAsync 在属性填充时给了一个半成品对象,而在初始化完成之后又变成了动态代理对象,导致前后不一致。◼️⭐️-point-202302030836%%

@EnableAsync 开启时它会向容器内注入 AsyncAnnotationBeanPostProcessor,它是一个 BeanPostProcessor,实现了postProcessAfterInitialization方法。此处我们看代码,创建代理的动作在抽象父类 AbstractAdvisingBeanPostProcessor 上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// @since 3.2   注意:@EnableAsync在Spring3.1后出现
// 继承自ProxyProcessorSupport,所以具有动态代理相关属性~ 方便创建代理对象
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {

// 这里会缓存所有被处理的Bean~~~ eligible:合适的
private final Map<Class<?>, Boolean> eligibleBeans = new ConcurrentHashMap<>(256);

//postProcessBeforeInitialization方法什么不做~
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}

// 关键是这里。当Bean初始化完成后这里会执行,这里会决策看看要不要对此Bean创建代理对象再返回~~~
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (this.advisor == null || bean instanceof AopInfrastructureBean) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}

// 如果此Bean已经被代理了(比如已经被事务那边给代理了~~)
if (bean instanceof Advised) {
Advised advised = (Advised) bean;

// 此处拿的是AopUtils.getTargetClass(bean)目标对象,做最终的判断
// isEligible()是否合适的判断方法 是本文最重要的一个方法,下文解释~
// 此处还有个小细节:isFrozen为false也就是还没被冻结的时候,就只向里面添加一个切面接口 并不要自己再创建代理对象了 省事
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
// Add our local Advisor to the existing proxy's Advisor chain...
// beforeExistingAdvisors决定这该advisor最先执行还是最后执行
// 此处的advisor为:AsyncAnnotationAdvisor 它切入Class和Method标注有@Aysnc注解的地方~~~
if (this.beforeExistingAdvisors) {
advised.addAdvisor(0, this.advisor);
} else {
advised.addAdvisor(this.advisor);
}
return bean;
}
}

// 若不是代理对象,此处就要下手了~~~~isEligible() 这个方法特别重要
if (isEligible(bean, beanName)) {
// copy属性 proxyFactory.copyFrom(this); 生成一个新的ProxyFactory
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
// 如果没有强制采用CGLIB 去探测它的接口~
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
// 添加进此切面~~ 最终为它创建一个getProxy 代理对象
proxyFactory.addAdvisor(this.advisor);
//customize交给子类复写(实际子类目前都没有复写~)
customizeProxyFactory(proxyFactory);
return proxyFactory.getProxy(getProxyClassLoader());
}

// No proxy needed.
return bean;
}

// 我们发现BeanName最终其实是没有用到的~~~
// 但是子类AbstractBeanFactoryAwareAdvisingPostProcessor是用到了的 没有做什么 可以忽略~~~
protected boolean isEligible(Object bean, String beanName) {
return isEligible(bean.getClass());
}
protected boolean isEligible(Class<?> targetClass) {
// 首次进来eligible的值肯定为null~~~
Boolean eligible = this.eligibleBeans.get(targetClass);
if (eligible != null) {
return eligible;
}
// 如果根本就没有配置advisor 也就不用看了~
if (this.advisor == null) {
return false;
}

// 最关键的就是canApply这个方法,如果AsyncAnnotationAdvisor 能切进它 那这里就是true
// 本例中方法标注有@Aysnc注解,所以铁定是能被切入的 返回true继续上面方法体的内容
eligible = AopUtils.canApply(this.advisor, targetClass);
this.eligibleBeans.put(targetClass, eligible);
return eligible;
}
...
}

根本原理是只要能被切面 AsyncAnnotationAdvisor 切入(即只需要类/方法有标注 @Async 注解即可)的 Bean,在初始化完成之后,经过 BeanPostProcessor.postProcessAfterInitialization() 最终都会生成一个代理对象(若已经是代理对象里,只需要加入该切面即可了)赋值给上面的 exposedObject 作为返回最终 add 进 Spring 容器内。

4.6.2. 流程分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class A implements AInterface {
@Autowired
private BInterface b;
@Async
@Override
public void funA() {
}
}

@Service
public class B implements BInterface {
@Autowired
private AInterface a;
@Override
public void funB() {
a.funA();
}
}

  1. A->B: context.getBean(A) 开始创建 A,A 实例化完成后给 A 的依赖属性 b 开始赋值
  2. B->A: context.getBean(B) 开始创建 B,B 实例化完成后给 B 的依赖属性 a 开始赋值

重点:此时因为 A 支持循环依赖,而且没有动态代理所以会执行 A 的 getEarlyBeanReference 方法得到它的早期引用。而执行 getEarlyBeanReference() 的时候因为@Async 根本还没执行,所以最终返回的仍旧是原始对象的地址

  1. B 完成初始化、完成属性的赋值,此时属性 field 持有的是 Bean A原始类型的引用
  2. 继续 A 的创建流程:完成了 A 的属性的赋值(此时已持有 B 的实例的引用),继续执行初始化方法 initializeBean(…),在此处会解析@Aysnc 注解,从而生成一个代理对象,所以最终 exposedObject 是一个代理对象(而非原始对象)最终加入到容器里

%%
▶11.🏡⭐️◼️【原因是 ABA 时,A 实例化完成将原始类型引用放入三级缓存,属性填充 B 时触发 B 的创建流程,B 实例化完成后填充属性时从三级缓存中拿到的是原始类型的 A 的早期引用,B 创建完成后,继续 A 的创建流程,但最后初始化完成后 A 上的@Async 注解生效生成了动态代理,但是 B 中的引用还是原始类型的引用,造成前后不一致】◼️⭐️-point-20230303-2035%%
^vlhwms

上演尴尬的场面:B 引用的属性 A 是个原始对象,而此处准备 return 的实例 A 竟然是个代理对象,也就是说 B 引用的并非是最终对象(不是最终放进容器里的对象)
执行自检程序:由于 allowRawInjectionDespiteWrapping 默认值是 false,表示不允许上面不一致的情况发生,so 最终就抛错了

4.6.3. 解决方案

通过上面分析,知道了问题的根本原因,现总结出解决上述新问题的解决方案,可分为下面三种方案:

  1. 把 allowRawInjectionDespiteWrapping 设置为 true
  2. 使用@Lazy 或者@ComponentScan(lazyInit = true) 解决
  3. 不要让@Async 的 Bean 参与循环依赖

@Lazy 原理: Spring-9、@Lazy

4.6.4. 其他

本质上:@Async 的后置处理器,没有在 getEarlyRefrence 时,创建被@Async 修饰代理对象,而是在 initlizeBean 方法中创建了代理对象。作为对比,被@Aspect 修饰的 Aop 对象会在 getEarlyRefrence 阶段,提前创建代理对象,顾 aop 时不存在该问题

https://blog.csdn.net/f641385712/article/details/92797058

[[(210条消息) 使用@Async异步注解导致该Bean在循环依赖时启动报BeanCurrentlyInCreationException异常的根本原因分析,以及提供解决方案【享学Spring】_YourBatman的博客-CSDN博客]]

4.7. BFPP 导致自动配置失败

Bean 工厂后置处理器在容器刷新的第 5 步,远在第 11 步中的 9 大 Bean 后置处理器之上
https://www.bilibili.com/video/BV15b4y117RJ?p=169&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

image.png

image.png

  1. BFPP 尽量使用静态工厂方法
  2. 依赖注入使用局部变量

4.8. 多例的循环依赖

Spring 有没有解决多例 Bean 的循环依赖?
a. 多例不会使用缓存进行存储(多例 Bean 每次使用都需要重新创建)
b. 不缓存早期对象就无法解决循环

  关键: 一定要有一个缓存保存它的早期对象作为死循环的出口

4.9. 构造方法的循环依赖

%%
▶6.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230322-1813%%
❕ ^7cjsyr

4.9.1. @Lazy

image.png

image.png

使用@Lazy 注解,会创建 cglib 动态代理

https://www.yuanjava.cn/posts/spring-constructor-circular-dependencies/
https://blog.csdn.net/WX10301075WX/article/details/123904543

循环依赖

4.9.2. ObjectFactory

image.png

image.png

image.png

4.9.3. Provider

image.png

image.png
image.png

4.9.4. @Scope

image.png

4.10. 防止获取到不完整的 Bean⭐️🔴

%%
▶75.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230306-1455%%
❕ ^d4s7ab

%%
▶12.🏡⭐️◼️【创建对象时用同一把锁同时锁住 2 个地方,分别是 2 个重载的 getsingleton 方法。第一个锁住创建 Bean 的整个流程,包括实例化、属性填充和初始化,在此过程中,第二把锁在三级缓存前面,其他线程按次序到一级、二级缓存中拿都是空的,被阻塞在三级缓存前面。第一个线程创建完对象,放入 1 级缓存,并清空 23 级缓存,第二个线程通过双重检查锁机制,再次查看 1 级缓存就拿到了需要的 Bean】◼️⭐️-point-20230303-2218%%


https://www.bilibili.com/video/BV1t44y1C73F?p=29&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

上面这个图对应的版本为 5.0.X,后面版本 5.2.X 之后,Spring 只锁在三级缓存前面,也正因此一级、二级缓存用的是 ConcurrentHashMap%%
1741-🏡⭐️◼️Spring 防止不完整的 Bean 的源码中版本变化🔜MSTM📝 5.2.X 之后,只锁三级缓存,把二级缓存拿到了锁外面,没看出来什么特殊作用,也没有影响,因为线程 1 最终将完整的 bean 加入了一级缓存,并清空了二三级缓存,线程 2 获得锁之后,按一二三级缓存进行查询,先查到一级缓存中有就返回了◼️⭐️-point-202301291741%%

  1. 两个地方加了同一把锁,即锁住了一级缓存
  2. 先在创建 bean 的过程中加了锁,具体位置在 doGetBean 中的第二个 getSingleton,即入参为 beanNameObjectFactory<?>
  3. 于此同时,相当于也在 getSingleton(beanName, boolean) 中加了锁,因为是同一把对象锁%%
    1538-🏡⭐️◼️Spring 如何防止获取到不完整的 Bean 的🔜MSTM📝 分别在创建 bean(getSingleton(beanName,beanFactory)) 和获取 bean 的 (getSingleton(beanName)) 方法这 2 个地方加了同一个对象锁,使用了一级缓存对象。在创建过程中先进入 getSingleton(beanName),但是因为判断逻辑没有进入到加锁的地方,第一次加锁是在 getSingleton(beanName,beanFactory),然后对象锁就生效了,相当于同时在 getSingleton(beanName) 里也加了锁◼️⭐️-point-202301301538%%

只能说太精妙了,2 个锁都是在单例仓库 DefaultSingletonBeanRegistry 中,getSingleton 的 2 个重载的方法里:
image.png

image.png

5. 扩展点

^ywsrjj

Spring-7、扩展点

6. 设计模式

7. 实战经验

手动集成 Bean 到容器中
https://blog.csdn.net/yuan882696yan/article/details/99082252

1
2
3
4
5
6
7
8
9
10
11
12
// 装配外部bean(不在spring容器中的bean)
public class Main {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext("C:\\Users\\IdeaProjects\\springDemo\\src\\main\\java\\com\\beanfactory\\autowireCapableBeanFactory\\test.xml");
AutowireCapableBeanFactory autowireCapableBeanFactory = context.getAutowireCapableBeanFactory();
Object bean = autowireCapableBeanFactory.autowire(Foo.class, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, true);
// Foo没有在test.xml中注册,不受spring容器管制
// 通过autowireCapableBeanFactory的方式,依然可以获取到foo,并执行foo里的test方法
Foo foo = (Foo) bean;
foo.test();
}
}

8. 参考与感谢

8.1. 图灵徐庶

https://www.bilibili.com/video/BV1t44y1C73F/?p=24&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204 75p

[[Spring全家桶面试题—图灵徐庶.pdf]]

https://www.processon.com/view/link/5f5075c763768959e2d109df#map
https://www.processon.com/view/link/5f1fb2cf1e08533a628a7b4c
Bean 创建过程: https://www.processon.com/view/link/5f15341b07912906d9ae8642

8.2. 尚硅谷雷丰阳

8.2.1. 配套视频

https://www.bilibili.com/video/BV1gW411W7wy/?spm_id_from=333.337.search-card.all.click

8.2.2. 配套代码

spring 注解驱动开发 _spring 源码版 - 雷丰阳 - 尚硅谷
[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/Spring注解驱动开发-尚硅谷-雷丰阳/spring-annotation/src/main/resources/SpringSource.txt]]

8.2.3. 配套笔记

https://liayun.blog.csdn.net/article/details/111413608

8.3. 马士兵 -lianpengju

[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/lianpengju/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java]] ❕%%
0915-🏡⭐️◼️gradle 配置有问题,测试例子使用的是尚硅谷的 spring-framework:springsource-test◼️⭐️-point-202301290915%%

https://www.bilibili.com/video/BV11B4y1175J?p=6&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

8.4. 黑马

8.4.1. 视频

https://www.bilibili.com/video/BV15b4y117RJ?p=196&spm_id_from=pageDriver&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

8.4.2. 资料

1
/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/011-面试专题/黑马面试题

8.5. 测试 Demo

013-DemoCode/spring-framework/spring
[[AnnotationMainTest.java]]