爱上源码,重学Spring IoC深入

news2024/11/18 6:01:06
回答:
我们为什么要学习源码?
1、知其然知其所以然
2、站在巨人的肩膀上,提高自己的编码水平
3、应付面试

1.1 Spring源码阅读小技巧

1、类层次藏得太深,不要一个类一个类的去看,遇到方法该进就大胆的进

2、更不要一行一行的去看,看核心点,有些方法并不重要,不要跟它纠缠

3、看不懂的先不看,根据语义和返回值能知道这个方法达到了啥目的即可

4、只看核心接口(下面标注了重点的地方)和核心代码,有些地方也许你使用spring以来都没触发过

5、debug跟步走,源码中给大家标注好了,见到 ”===>“ 就进去

​ 进去之前,下一行打个断点,方便快速回到岔路口

​ 进去之前,可以先点方法看源码,再debug跟进

6、广度优先,而非深度优先。先沿着主流程走,了解大概,再细化某些方法

7、认命。spring里多少万行的代码,一部书都写不完。只能学关键点

阅读源码目的

加深理解spring的bean加载过程

面试吹牛x

江湖传说,spring的类关系是这样的……

file

1.2 IoC初始化流程与继承关系

引言
在看源码之前需要掌握Spring的继承关系和初始化

1) IoC容器初始化流程

目标:

1、IoC容器初始化过程中到底都做了哪些事情(宏观目标)

2、IoC容器初始化是如何实例化Bean的(划重点,最终目标)

//没有Spring之前我们是这样的
User user=new User();
user.xxx();

//有了Spring之后我们是这样的
<bean id="userService" class="com.spring.test.impl.UserServiceImpl">
User user= context.getBean("xxx");
user.xxx();

IoC流程简化图:

tips:

下面的流转记不住没有关系

在剖析源码的整个过程中,我们一直会拿着这个图和源码对照

file

初始化:

1、容器环境的初始化

2、Bean工厂的初始化(IoC容器启动首先会销毁旧工厂、旧Bean、创建新的工厂)

读取与定义

读取:通过BeanDefinitonReader读取我们项目中的配置(application.xml)

定义:通过解析xml文件内容,将里面的Bean解析成BeanDefinition(未实例化、未初始化)

实例化与销毁

Bean实例化、初始化(注入)

销毁缓存等

扩展点

事件与多播、后置处理器

复杂的流程关键点:

file

重点总结:

1、工厂初始化过程

2、解析xml到BeanDefinition,放到map

3、调用后置处理器

4、从map取出进行实例化( ctor.newInstance)

5、实例化后放到一级缓存(工厂)

2) 容器与工厂继承关系

tips:

别紧张,下面的继承记不住没有关系

关注颜色标注的几个就可以

**目标:**简单理解ioC容器继承关系

file

继承关系理解:

1、ClassPathXmlApplicationContext最终还是到了 ApplicationContext 接口,同样的,我们也可以使用绿颜色的 FileSystemXmlApplicationContext 和 AnnotationConfigApplicationContext 这两个类完成容器初始化的工作

2、FileSystemXmlApplicationContext 的构造函数需要一个 xml 配置文件在系统中的路径,其他和 ClassPathXmlApplicationContext 基本上一样

3、AnnotationConfigApplicationContext 的构造函数扫描classpath中相关注解的类,主流程一样

课程中我们以最经典的 classpathXml 为例。

Bean工厂继承关系

目标:

ApplicationContext 和 BeanFactory 啥关系?

BeanFactory 和 FactoryBean呢?

file

总结:

别害怕,上面的继承关系不用刻意去记住它

其实接触到的就最下面这个!

1.3 开始搭建测试项目

四步:

1、新建测试module项目

首先我们在 Spring 源码项目中新增一个测试项目,点击 New -> Module… 创建一个 Gradle 的 Java 项目

file
2、详细信息

file
file

3、设置gradle

file
4、完善信息

file

在 build.gradle 中添加对 Spring 源码的依赖:

compile(project(':spring-context'))

file

spring-context 会自动将 spring-core、spring-beans、spring-aop、spring-expression 这几个基础 jar 包带进来。

接着,我们需要在项目中创建一个 bean 和配置文件(application.xml)及启动文件(Main.java)

接口如下:

package com.spring.test.service;

public interface UserService {
	public String getName();
}

实现类

package com.spring.test.impl;

import com.spring.test.service.UserService;

public class UserServiceImpl implements UserService {
	@Override
	public String getName() {
		return "Hello World";
	}
}

Main代码如下

public class Test {
	public static void main(String[] args) {
		ApplicationContext context =
				new ClassPathXmlApplicationContext("classpath*:application.xml");

		UserService userService = context.getBean(UserService.class);
		System.out.println(userService);
		// 这句将输出: hello world
		System.out.println(userService.getName());

	}
}

配置文件 application.xml(在 resources 中)配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean id="userService" class="com.spring.test.impl.UserServiceImpl"/>
</beans>

运行

输出如下
com.spring.test.impl.UserServiceImpl@2aa5fe93
Hello World

1.4 工厂的构建

引言:
接下来,我们就正式讲解Spring ioC容器的源码
我们的目的:看一下ioC如何帮我们生成对象的

生命周期

1)ApplicationContext入口

参考 IocTest.java

测试代码:spring支持多种bean定义方式,为方便大家理解结构,以xml为案例,后面的解析流程一致

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:${xmlName}.xml");
		// (c)从容器中取出Bean的实例,call:AbstractApplicationContext.getBean(java.lang.Class<T>)
		//工厂模式(simple)
		UserService userService = (UserService) context.getBean("userServiceBeanId");
		// 这句将输出: hello world
		System.out.println(userService.getName());

进入到ClassPathXmlApplicationContext的有参构造器

org.springframework.context.support.ClassPathXmlApplicationContext#ClassPathXmlApplicationContext(java.lang.String[], boolean, org.springframework.context.ApplicationContext)

	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {
		//继承结构图
		//1、返回一个classloader
		//2、返回一个解析器
		super(parent);
		// 1、获取环境(系统环境、jvm环境)
		// 2、设置Placeholder占位符解析器
		// 2、将xml的路径解析完存储到数组
		setConfigLocations(configLocations);
		//默认为true
		if (refresh) {
			//核心方法(模板)
			refresh();
		}
	}

重点步骤解析(断点跟踪讲解)

super方法做了哪些事情
1、super方法:通过点查看父容器与子容器概念
2、super方法:调用到顶端,一共5层,每一层都要与讲义中的【ioC与Bean工厂类关系继承】进行对照
3、super方法:在什么地方初始化的类加载器和解析器
setConfigLocations方法做了哪些事情:
1、如何返回的系统环境和jvm环境
2、路径的解析
3、设置占位符解析器

进入核心方法refresh

2)预刷新

prepareRefresh()【准备刷新】

		// synchronized块锁(monitorenter --monitorexit),不然 refresh() 还没结束,又来个启动或销毁容器的操作
		synchronized (this.startupShutdownMonitor) {
			//1、【准备刷新】【Did four things】
			prepareRefresh();
			
			
	......。略

讲解重点(断点跟踪、类继承关系、架构图讲解)

prepareRefresh干了哪些事情

	//1、记录启动时间/设置开始标志
	//2、子类属性扩展(模板方法)
	//3、校验xml配置文件
	//4、初始化早期发布的应用程序事件对象(不重要,仅仅是创建setg对象)

3)创建bean工厂【重点】

【获得新的bean工厂】obtainFreshBeanFactory()

最终目的就是解析xml,注册bean定义

			关键步骤
			//1、关闭旧的 BeanFactory
			//2、创建新的 BeanFactory(DefaluListbaleBeanFactory)
			//3、解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(未初始化)
			//4、返回全新的工厂
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

4)bean工厂前置操作

【准备bean工厂】prepareBeanFactory(beanFactory);

	//1、设置 BeanFactory 的类加载器
	//2、设置 BeanFactory 的表达式解析器
	//3、设置 BeanFactory 的属性编辑器
	//4、智能注册

tips

当前代码逻辑简单、且非核心

5)bean工厂后置操作

【后置处理器Bean工厂】postProcessBeanFactory(beanFactory) 空方法

tips:子类实现

空方法,跳过

6)工厂后置处理器【重点】

【调用bean工厂后置处理器】invokeBeanFactoryPostProcessors(beanFactory);

	//调用顺序一:bean定义注册后置处理器
	//调用顺序二:bean工厂后置处理器

	PostProcessorRegistrationDelegate 类里有详细注解

tips

invoke方法近200行

关注两类后置处理器的方法执行步骤和顺序

7)bean后置处理器

【注册bean后置处理器】registerBeanPostProcessors(beanFactory)

//6、【注册bean后置处理器】只是注册,但是不会反射调用
//功能:找出所有实现BeanPostProcessor接口的类,分类、排序、注册
registerBeanPostProcessors(beanFactory);
//	核心:查看重要的3步;最终目的都是实现bean后置处理器的注册
// 第一步: implement PriorityOrdered
// 第二步: implement Ordered.
// 第三步: Register all internal BeanPostProcessors.




8)国际化

【初始化消息源】国际化问题i18n initMessageSource();

tips:

就加了个bean进去,非核心步骤,跳过

9)初始化事件广播器

【初始化应用程序事件多路广播】initApplicationEventMulticaster();

tips:

需要讲解观察者设计模式

重点:就放了个bean进去, 到下面的 listener再联调。

10)刷新

【刷新】 onRefresh();

空的,交给子类实现:默认情况下不执行任何操作

// 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
onRefresh();

不重要,跳过

11)注册监听器【重点】

**【注册所有监听器】**registerListeners();

测试代码参考:MulticastTest

	//获取所有实现了ApplicationListener,然后进行注册
	//1、集合applicationListeners查找
	//2、bean工厂找到实现ApplicationListener接口的bean
	//3、this.earlyApplicationEvents;

tips:

需要讲解观察者设计模式

重点:演示多播和容器发布

12)完成bean工厂【重点】

【完成bean工厂初始化操作】finishBeanFactoryInitialization(beanFactory);

//【完成bean工厂初始化操作】负责初始化所有的 singleton beans
//此处开始调用Bean的前置处理器和后置处理器
finishBeanFactoryInitialization(beanFactory);

讲解重点(断点跟踪、类继承关系、架构图讲解)

  //1、设置辅助器:例如:解析器、转换器、类装载器
	//2、实例化
	//3、填充
	//4、调用前置、后置处理器
//核心代码在  getBean() , 下面单独讲解

13)完成刷新

【完成刷新】

	protected void finishRefresh() {
		// 1、清除上下文级资源缓存
		clearResourceCaches();

		// 2、LifecycleProcessor接口初始化
		// ps:当ApplicationContext启动或停止时,它会通过LifecycleProcessor来与所有声明的bean的周期做状态更新
		// 而在LifecycleProcessor的使用前首先需要初始化
		initLifecycleProcessor();

		// 3、启动所有实现了LifecycleProcessor接口的bean
		//DefaultLifecycleProcessor,默认实现
		getLifecycleProcessor().onRefresh();

		// 4、发布上下文刷新完毕事件到相应的监听器
		//ps:当完成容器初始化的时候,
		// 要通过Spring中的事件发布机制来发出ContextRefreshedEvent事件,以保证对应的监听器可以做进一步的逻辑处理
		publishEvent(new ContextRefreshedEvent(this));

		// 5、把当前容器注册到到MBeanServer,用于jmx使用
		LiveBeansView.registerApplicationContext(this);
	}

tips:

非核心步骤

2 singleton bean 创建【重点】

下面拎出来,重点讲 getBean方法。

参考代码:

先看没有循环依赖的情况,普通单例bean的初始化 SinigleTest.java

后面再讲循环依赖

1)调用入口

大家都知道是getBean()方法,但是这个方法要注意,有很多调用时机

如果你把断点打在了这里,再点进去getBean,你将会直接从singleton集合中拿到一个实例化好的bean

无法看到它的实例化过程。

可以debug试一下。会发现直接从getSingleTon返回了bean,这不是我们想要的模样……

file

思考一下,为什么呢?

回顾 1.4中的第 12 小节,在bean工厂完成后,会对singleton的bean完成初始化,那么真正的初始化应该发生在那里!

那就需要找到:DefaultListableBeanFactory的第 809 行,那里的getBean

也可以从 1.4的第12小节的入口跟进去。断点打在这里试试:

file

这也是我们在上面留下的尾巴。

本小节我们从这里继续……

2)主流程

小tip:先搞清除3级缓存的事

关于bean的三级缓存:DefaultSingletonBeanRegistry代码

	/**
	 * 一级缓存:单例(对象)池,这里面的对象都是确保初始化完成,可以被正常使用的
	 * 它可能来自3级,或者2级
	 */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/**
	 * 三级缓存:单例工厂池,这里面不是bean本身,是它的一个工厂,未来调getObject来获取真正的bean
	 * 一旦获取,就从这里删掉,进入2级(发生闭环的话)或1级(没有闭环)
	 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/**
	 * 二级缓存:早期(对象)单例池,这里面都是半成品,只是有人用它提前从3级get出来,把引用暴露出去
	 * 它里面的属性可能是null,所以叫早期对象,early!半成品
	 * 未来在getBean付完属性后,会调addSingleton清掉2级,正式进入1级
	 */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

传统叫三级缓存里拿bean,其实就是仨map

严格意义上,只有single一级缓存,其他俩根本算不上是缓存

他们只是在生成bean的过程中,暂存过bean的半成品。

就那么称呼,不必较真

主流程图很重要!后面的debug会带着这张图走

file

getBean   :
入口

doGetBean   :
调getSingleton查一下缓存看看有没有,有就返回,没有给singleton一个lambda表达式,函数式编程里调下面的createBean拿到新的bean,然后清除3级缓存,放入1级缓存

createBean  :
调这里。一堆检查后,进入下面

doCreateBean  :
真正创建bean的地方: 调构造函数初始化 - 放入3级缓存 - 解析属性赋值 - bean后置处理器

3)getSingleton

在DefaultSingletonBeanRegistry里,有三个,作用完全不一样

//啥也没干,调下面传了个true
public Object getSingleton(String beanName) 
  
//从1级缓存拿,1级没有再看情况
//后面的参数如果true,就使用3级升2级返回,否则直接返回null 
protected Object getSingleton(String beanName, boolean allowEarlyReference)

//1级没有,通过给的factory创建并放入1级里,清除2、3
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)

4)bean实例化

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance

5)放入三级缓存

循环依赖和aop

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#addSingletonFactory

4)注入属性

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
真正给bean设置属性的地方!

7)bean前后置

还记得上面我们自定义的 Bean后置处理器吗

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean
前、后置的调用,在这里

详细见下图第3步,很多,了解即可,需要时查一下在相关地方扩展

file

8)小结

伪代码,无循环依赖时,生成bean流程一览

getBean("A"){
	doGetBean("A"){
    a = getSingleton("A"){
      a = singletonObjects(); //查1级缓存,null
      if("创建过3级缓存"){  //不成立
        //忽略
      }
      return a;
    }; // null
    
    if(a == null){
      a = getSingleton("A" , ObjectFactory of){
        
        
        a = of.getObject() -> { //lambda表达式
          createBean("A"){
            doCreateBean("A"){
              createBeanInstance("A"); // A 实例化
              addSingletonFactory("A"); // A 放入3级缓存
              populateBean("A"); // A 注入属性
              initializeBean("A"); // A 后置处理器
            } //end doCreateBean("A")
          } //end crateBean("A")
      	} // end lambda A
        
        addSingleton("A" , a) // 清除2、3级,放入1级
      } // end getSingleton("A",factory)
  
    } // end if(a == null)
    
    return a;
      
  } //end doGetBean("A")
}//end getBean("A")

3 Spring的循环依赖

引言
在上面,我们剖析了bean实例化的整个过程
也就是我们的Bean他是单独存在的,和其他Bean没有交集和引用
而我们在业务开发中,肯定会有多个Bean相互引用的情况
也就是所谓的循环依赖

3.1 什么是循环依赖

简单回顾下

通俗的讲就是N个Bean互相引用对方,最终形成闭环

file
项目代码介绍如下(测试类入口: CircleTest.java)

配置文件

	<!--循环依赖BeanA依赖BeanB -->
	<bean id="userServiceImplA" class="com.spring.test.impl.UserServiceImplA">
		<property name="userServiceImplB" ref="userServiceImplB"/>
	</bean>

	<!--循环依赖BeanB依赖BeanA -->
	<bean id="userServiceImplB" class="com.spring.test.impl.UserServiceImplB">
		<property name="userServiceImplA" ref="userServiceImplA"/>
	</bean>

userServiceImplA代码如下

public class UserServiceImplA implements UserService {
	private UserServiceImplB userServiceImplB;
	public void setUserServiceImplB(UserServiceImplB userServiceImplB) {
		this.userServiceImplB = userServiceImplB;
	}

	@Override
	public String getName() {

		return "在UserServiceImplA的Bean中" +
				"userServiceImplB注入成功>>>>>>>>>"+userServiceImplB;

	}

}

userServiceImplB代码如下

//实现类
public class UserServiceImplB implements UserService {
	private UserServiceImplA userServiceImplA;

	public void setUserServiceImplA(UserServiceImplA userServiceImplA) {
		this.userServiceImplA = userServiceImplA;
	}

	@Override
	public String getName() {

		return "在UserServiceImplB的Bean中" +
				"userServiceImplA注入成功>>>>>>>>>"+userServiceImplA;

	}

入口Main

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml");
		
	UserService userService = context.getBean("userServiceImplA",UserService.class);
	System.out.println(userService.getName());

输出如下

file

3.2 Spring如何解决循环依赖

假如无法解决循环依赖

1、Bean无法成功注入,导致业务无法进行

2、产生死循环(一种假设情景)

1)三级缓存变化过程

目标:

只有明白三级缓存变化过程,才能知道是如何解决循环依赖的

略去其他步骤,只看缓存变化

file

变化过程3-1:如下图:

file

步骤:

A :… - 走到doCreateBean: 初始化 - 进3级缓存 - 注入属性,发现需要B

B :… - 走到doCreateBean: 初始化 - 进3级缓存

1、BeanA经历gdcd四个方法,走到doCreatebean里在实例化后、注入前放到三级缓存

2、放到三级缓存后;BeanA在正式的注入的时候,发现有循环依赖,重复上【1】的步骤

3、最终:BeanA和BeanB都放到了三级缓存

变化过程3-2:如下图:

file

步骤:

1、BeanB放到三级缓存后,这个时候BeanB要开始注入了;

于是,BeanB找到了循环依赖BeanA后,再从头执行A的getBean和doGetBean方法;

此处在getSingleton里面(这货第一次是必经的,但第二次来行为不一样了)将BeanA设置到了二级缓存,并且把BeanA从三级缓存移除走了

2、BeanB如愿以偿的拿到了A,注入,此时,完成了注入过程;一直到DefaultSingletonBeanRegistry#addSingleton方法后;BeanB从三级缓存直接进入一级缓存,完成它的使命

3、目前,一级缓存有BeanB(里面的BeanA属性还是空)、二级缓存有BeanA 三级缓存为空

效果如下

走到这一步,B里面有A,它已完成。

但是很不幸,A里面的B还是null,我们第三步会继续完成这个设置

file

思考一下:

如果不用三级,我们直接用2级也能实现,但是3级我们说它是一个Factory,里面可以在创建的前后嵌入我们的代码,和前后置处理器,Aop之类的操作就发生在这里

而2级存放的是bean实例,没这么多扩展的可能性,如果仅仅用于bean循环创建,倒是可以

总结:

1、如果不调用后置,返回的bean和三级缓存一样

2、如果调用后置,返回的就是代理对象

3、这就是三级缓存设计的巧妙之处!!!!Map<String, ObjectFactory<?>>

变化过程3-3:如下图:

file

步骤:

此时, BeanB里面已经注入了BeanA,它自己完成并进入了一级缓存

要注意,它的完成是被动的结果,也就是A需要它,临时先腾出时间创建了它

接下来,BeanA 还要继续自己的流程,然后populateBean方法将BeanB注入到自己里

最后,BeanA 进一级缓存,删除之前的二级

整个流程完成!

大功告成:双方相互持有对方效果如下:

file

2)三级缓存解决方案总结

简化版

file

序列图

file
三级缓存解决循环依赖过程(回顾)

1、BeanA经过gdcd方法、放入到3级缓存、如果有循环依赖BeanB,重复执行gdcd方法

2、直到发现了它也需要A,而A前面经历了一次get操作,将3级缓存的BeanA放到2级缓存

3、然后2级缓存的A注入进BeanB, BeanB完事进一级缓存,此时BeanB持有BeanA

3、接下来,继续完成BeanA剩下的操作,取BeanB填充进BeanA,将BeanA放到一级缓存,完成!

伪代码,循环依赖流程一览,都是关键步骤,不能再简化了

建议粘贴到vscode等编辑器里查看,因为……它层级太tmd深了!

getBean("A"){
	doGetBean("A"){
    a = getSingleton("A"){
      a = singletonObjects(); //查1级缓存,null
      if("创建过3级缓存"){  //不成立
        //忽略
      }
      return a;
    }; // A第一次,null
    
    if(a == null){
      a = getSingleton("A" , ObjectFactory of){
        
        
        a = of.getObject() -> { //lambda表达式
          createBean("A"){
            doCreateBean("A"){
              createBeanInstance("A"); // A 实例化
              addSingletonFactory("A"); // A 放入3级缓存
              populateBean("A"){
                //A 需要B,进入B的getBean
                b = getBean("B"){
                  doGetBean("B"){
                    b = getSingleton("B"); // B第一次,null

                    if(b == null){
                      b = getSingleton("B", ObjectFactory of){

                        b = of.getObject() -> {
                          createBean("B"){
                            doCreateBean("B"){
                              createBeanInstance("B"); // B 实例化
                              addSingletonFactory("B"); // B 放入3级缓存
                              populateBean("B"){
                                //B 需要A,2次进入A的getBean
                                a = getBean("A"){
                                  doGetBean("A"){
                                    a = getSingleton("A"){
                                      a = singletonObjects(); //查1级缓存,null
                                      if("创建过3级缓存"){  //成立!
                                        a = singletonFactory.getObject("A"); //取3级缓存,生成a
                                        earlySingletonObjects.put("A", a); //放入2级缓存
                                        singletonFactories.remove("A"); //移除3级缓存
                                        return a;
                                      }
                                    }; // A第二次,不是null,但是半成品,还待在2级缓存里
                                  } // end doGetBean("A")
                                } // end getBean("A")
                              }  // end populate B
                              initializeBean("B",b); // B后置处理器
                            } // end doCreateBean B
                          } // end createBean B
                        } // end lambda B

                        // B 创建完成,并且是完整的,虽然它里面的A还是半成品,但不影响它进入1级               
                        addSingleton("B",b) ; // 清除3级缓存,进入1级
                      ); // end getSingleton("B",factory)                 


                    } // end if(b==null);

                    return b;

                  } // end doGetBean("B")
                } // end getBean("B")
              } // end populateBean("A")

              initializeBean("A"); // A 后置处理器
            } //end doCreateBean("A")
          } //end crateBean("A")
      	} // end lambda A
        
        addSingleton("A" , a) // 清除2、3级,放入1级
      } // end getSingleton("A",factory)
  
    } // end if(a == null)
    
    return a;
      
  } //end doGetBean("A")
}//end getBean("A")


总结

可以发现,通过spring的三级缓存完美解决了循环依赖

Spring处理机制很聪明;它先扫描一遍Bean,先放到一个容器(3级缓存待命)

此时也不知道是否存在循环依赖,先放到三级缓存再说

等到设置属性的时候,取对应的属性bean去(此时才发现有了循环依赖) ,在放到第二个容器(2级缓存,半成品)

继续,然后从二级缓存拿出进行填充(注入)

填充完毕,将自己放到一级缓存(这个bean是被动创建出来的,因为别人需要它,结果它先完成了)

然后不断循环外层,处理最原始要创建的那个bean

为什么设计三级?二级缓存能否解决循环依赖?

可以解决。别说2级,1级都行

虽然二级缓存能解决循环依赖,但是aop时会可能会引发问题,三级是一个factory,在里面配备了对应的后置处理器,其中就有我们的aop (后面会讲到),如果有人要用它,会在调用factory的getObject时生效,生成代理bean而不是原始bean。

如果不这么做,直接创建原始对象注入,可能引发aop失效。

所以spring的3级各有意义:

1级:最终成品

2级:半成品

3级:工厂,备用

在上面的方法getEarlyBeanReference(提前暴露的引用)

回顾下

AbstractAutowireCapableBeanFactory.getEarlyBeanReference

		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			//循环所有Bean后置处理器
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				  //重点:开始创建AOP代理
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}

总结下:

1、如果不调用后置处理器,返回的Bean和三级缓存一样,都是实例化、普通的Bean

2、如果调用后置,返回的就是代理对象,不是普通的Bean了

其实;这就是三级缓存设计的巧妙之处

那为什么要2级呢? 不能直接放入1级吗?

不能!

A-B-A中,第二次A的时候,A还是个半成品,不能放入1级

以上面为例,A在进入2级缓存的时候,它里面的B还是个null !

如果放入1级,被其他使用的地方取走,会引发问题,比如空指针

4 IoC用到的那些设计模式

引言:

Spring中使用了大量的设计模式(面试)

4.1 工厂

**工厂模式(Factory Pattern)**提供了一种创建对象的最佳方式。

工厂模式(Factory Pattern)分为三种

1、简单工厂

2、工厂方法

3、抽象工厂

1. 简单工厂模式

ApplicationContext context =
	new ClassPathXmlApplicationContext("classpath*:application.xml");\
UserService userService = context.getBean(UserService.class);

简单工厂模式对对象创建管理方式最为简单,因为其仅仅简单的对不同类对象的创建进行了一层简单的封装

定义接口IPhone

public interface Phone {
	void make();
}

实现类

public class IPhone implements Phone {
	public IPhone() {
		this.make();
	}

	@Override
	public void make() {
		// TODO Auto-generated method stub
		System.out.println("生产苹果手机!");
	}
}

实现类

public class MiPhone implements Phone {
	public MiPhone() {
		this.make();
	}
	@Override
	public void make() {
		// TODO Auto-generated method stub
		System.out.println("生产小米手机!");
	}
}

定义工厂类并且测试

public class PhoneFactory {
	public Phone makePhone(String phoneType) {
		if (phoneType.equalsIgnoreCase("MiPhone")) {
			return new MiPhone();
		} else if (phoneType.equalsIgnoreCase("iPhone")) {
			return new IPhone();
		}
		return null;
	}

	//测试简单工厂
	public static void main(String[] arg) {
		PhoneFactory factory = new PhoneFactory();
		Phone miPhone = factory.makePhone("MiPhone");             
		IPhone iPhone = (IPhone) factory.makePhone("iPhone");     
	}
}

file

4.2 模板

**模板模式(Template Pattern)😗*基于抽象类的,核心是封装算法

Spring核心方法refresh就是典型的模板方法
org.springframework.context.support.AbstractApplicationContext#refresh

模板设计模式—

模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供具体实现

//模板模式
public abstract class TemplatePattern {
	protected abstract void step1();

	protected abstract void step2();

	protected abstract void step3();

	protected abstract void step4();

	//模板方法
	public final void refresh() {
	//此处也可加入当前类的一个方法实现,例如init()
		step1();
		step2();
		step3();
		step4();
	}


}

定义子类

//模板模式
public class SubTemplatePattern extends TemplatePattern {
	@Override
	public void step1() {
		System.out.println(">>>>>>>>>>>>>>1");
	}

	@Override
	public void step2() {
		System.out.println(">>>>>>>>>>>>>>2");

	}

	@Override
	public void step3() {
		System.out.println(">>>>>>>>>>>>>>3");

	}

	@Override
	public void step4() {
		System.out.println(">>>>>>>>>>>>>>4");
	}
    
    //测试
	public static void main(String[] args) {
		TemplatePattern tp = new SubTemplatePattern();
		tp.refresh();
	}
}

输出

file

4.3 观察者

什么是观察者模式

观察者模式(Observer Pattern):当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。

Spring 的事件机制就是具体的观察者模式的实现

spring中的多播与事件
AbstractApplicationContext#initApplicationEventMulticaster
    
AbstractApplicationContext#registerListeners

观察者模式有哪些角色?

事件 ApplicationEvent 是所有事件对象的父类,继承JDK的EventObject

事件监听 ApplicationListener,也就是观察者对象,继承自 JDK 的 EventListener,可以监听到事件;该类中只有一个方法 onApplicationEvent。当监听的事件发生后该方法会被执行。

事件发布ApplicationContext, 实现事件的发布。

(发布事件)

or=========

Spring中的多播

事件发布 ApplicationEventMulticaster,用于事件监听器的注册和事件的广播。

file

自定义一个事件MessageSourceEvent并且实现ApplicationEvent接口

//在Spring 中使用事件监听机制(事件、监听、发布)
//定义事件
//执行顺序
//1、进入到事件源的有参数构造器
//2、发布事件
//3、进入到监听器类---one
//4、进入到事件源的方法
//5、进入到监听器类---two
//6、进入到事件源的方法
public class MessageSourceEvent extends ApplicationEvent {
	public MessageSourceEvent(Object source) {
		super(source);
		System.out.println("进入到事件源的有参数构造器");
	}

	public void print() {
		System.out.println("进入到事件源的方法");
	}
}

有了事件之后还需要自定义一个监听用来接收监听到事件,自定义ApplicationContextListener监听 需要交给Spring容器管理, 实现ApplicationListener接口并且重写onApplicationEvent方法,

监听一

//在Spring 中使用事件监听机制(事件、监听、发布)
//监听类,在spring配置文件中,注册事件类和监听类
public class ApplicationContextListener implements ApplicationListener {
	@Override
	public void onApplicationEvent(ApplicationEvent event) {

		if (event instanceof MessageSourceEvent) {
			System.out.println("进入到监听器类---one");
			MessageSourceEvent myEvent = (MessageSourceEvent) event;
			myEvent.print();
		}

	}
}

监听二

//在Spring 中使用事件监听机制(事件、监听、发布)
//监听类,在spring配置文件中,注册事件类和监听类
public class ApplicationContextListenerTwo implements ApplicationListener  {

	@Override
	public void onApplicationEvent(ApplicationEvent event) {

		if(event instanceof MessageSourceEvent){
			System.out.println("进入到监听器类---two");
			MessageSourceEvent myEvent=(MessageSourceEvent)event;
			myEvent.print();
		}


	}
}

发布事件

//在Spring 中使用事件监听机制(事件、监听、发布)
//该类实现ApplicationContextAware接口,得到ApplicationContext对象
// 使用该对象的publishEvent方法发布事件
public class ApplicationContextListenerPubisher implements ApplicationContextAware {

	private ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	public void publishEvent(ApplicationEvent event) {
		System.out.println("发布事件");
		applicationContext.publishEvent(event);
	}

}

配置文件

	<!--  Spirng中的事件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
	<!--<bean id="messageSourceEvent" class="com.spring.test.pattern.observer.MessageSourceEvent"  />-->
	<bean id="applicationContextListener" class="com.spring.test.pattern.observer.ApplicationContextListener"/>
	<bean id="applicationContextListenerTwo" class="com.spring.test.pattern.observer.ApplicationContextListenerTwo"/>

	<bean id="applicationContextListenerPubisher" class="com.spring.test.pattern.observer.ApplicationContextListenerPubisher"/>
	<!--  Spirng中的事件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->

测试

//总结 :使用bean工厂发布和使用多播器效果是一样的
public class Test {

	public static void main(String[] args) {
		ApplicationContext context =
				new ClassPathXmlApplicationContext("classpath*:application.xml");
		//***************使用spring的多播器发布**********************
		ApplicationEventMulticaster applicationEventMulticaster = (ApplicationEventMulticaster) context.getBean("applicationEventMulticaster");
		applicationEventMulticaster.multicastEvent(new MessageSourceEvent("测试..."));
//***************使用BeanFactory的publishEvent发布*********************
		//	ApplicationContextListenerPubisher myPubisher = (ApplicationContextListenerPubisher)
		//context.getBean("applicationContextListenerPubisher");
		//myPubisher.publishEvent(new MessageSourceEvent("测试..."));
	}

}

多播发布

file
工厂发布

file

总结:

​ 1、spring的事件驱动模型使用的是 观察者模式

2、通过ApplicationEvent抽象类和ApplicationListener接口,可以实现事件处理

3、ApplicationEventMulticaster事件广播器实现了监听器的注册,一般不需要我们实现,只需要显示的调用 applicationcontext.publisherEvent方法即可

​ 4、使用bean工厂发布和使用多播器效果是一样的

本文由传智教育博学谷教研团队发布。

如果本文对您有帮助,欢迎关注点赞;如果您有任何建议也可留言评论私信,您的支持是我坚持创作的动力。

转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1033.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【3D目标检测】SECOND: Sparsely Embedded Convolutional Detection

目录概述细节网络结构稀疏卷积方向分类损失函数概述 首先&#xff0c;本文是基于点云&#xff0c;并且将点云处理成体素的3D目标检测网络&#xff0c;提出的SECOND可以看做是VoxelNet的升级版。 提出动机与贡献 VoxelNet计算量比较大&#xff0c;速度比较慢&#xff08;训练和…

第二节:数据类型与变量【java】

目录 &#x1f4c3;前言 &#x1f4d7;1.数据类型 &#x1f4d5;2. 变量 2.1 变量概念 2.2 语法格式 &#x1f4d9;3.整型变量 3.1 整型变量 3.2 长整型变量 3.3 短整型变量 3.4 字节型变量 &#x1f4d8;4.浮点型变量 4.1 双精度浮点型 4.2 单精度浮点型 &#…

[SpringBoot] AOP-AspectJ 切面技术

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

python的编译器与解释器

作者介绍&#xff1a; &#x1f425;作者&#xff1a;小刘在C站 &#x1f446;每天分享课堂笔记&#xff0c;一起努力&#xff0c;共赴美好人生 &#x1f341;夕阳下&#xff0c;是最美的绽放 目录 一.为什么会有编译器和解释器 二.编译器和解释器的区别 三.python解释器种类…

RK3399应用开发 | 移植libdrm到rk3399开发板(2.4.113)

一、下载源码 下载地址:https://dri.freedesktop.org/libdrm/。 这里我下载最新的2.4.113版本: wget https://dri.freedesktop.org/libdrm/libdrm-2.4.113.tar.xz解压: xz -d libdrm-2.4.113.tar.xz tar -xf libdrm-2.4.113.tar二、编译环境安装 1. 更新python ubuntu安…

CalBioreagents 艾美捷重组BCOADC-E2蛋白说明书

艾美捷CalBioreagents 重组BCOADC-E2蛋白英文说明&#xff1a; PRODUCT DESCRIPTION: Branched Chain 2-Oxo-Acid Dehydrogenase Complex E2 protein, recombinant. CLINICAL INDICATION: Primary biliary cirrhosis CATALOG NUMBER: A268 SOURCE: Recombinant protein ex…

《CTF攻防世界web题》之茶壶我爱你(2)

前言 &#x1f340;作者简介&#xff1a;被吉师散养、喜欢前端、学过后端、练过CTF、玩过DOS、不喜欢java的不知名学生。 &#x1f341;个人主页&#xff1a;被吉师散养的职业混子 &#x1fad2;文章目的&#xff1a;记录唯几我能做上的题 &#x1f342;相应专栏&#xff1a;CT…

Pytorch调用GPU时显示CUDA版本过低的解决方案

在调用torch.cuda.is_available时&#xff0c;有如下报错&#xff1a; cuda initialization: The Nvidia driver on your system is too old. 事情的发展是这样的&#xff1a; 1. 服务器的CUDA版本是10.1&#xff0c;仅支持pytorch版本最高1.7&#xff1b;前几天跑项目需要用…

Oracle 中常用的字符串函数总结

一、substr 函数 —— 字符串截取 格式1&#xff1a; substr(string,start_pos,length) 格式2&#xff1a; substr(string,start_pos) 说明&#xff1a; 从指定位置start_pos截取字符串string的length位&#xff0c;如果不指定length&#xff08;格式2&#xff09;则从指…

【数据挖掘】2022数据挖掘之Pandas库相关使用

数据挖掘之Pandas库相关使用一、概念1、介绍2、Pandas的优点3、软件推荐&#xff08;Jupyter Notebook&#xff09;4、软件下载网址以及参考文档二、基础知识1、DataFrame属性和方法1.1 结构&#xff08;1&#xff09;行索引&#xff08;2&#xff09;列索引1.2 常用属性&#…

配置非法AP设备检测和反制

1、业务需求 某企业分支机构为了保证工作人员可以随时随地访问公司网络资源&#xff0c;部署WLAN基本业务实现移动办公。且在覆盖区域内移动发生漫游时&#xff0c;不影响用户的业务使用。 分支机构位于开放式场所&#xff0c;容易受到网络入侵&#xff0c;例如攻击者在WLAN网络…

摒弃“短板”——数据中心基础设施运维管理建议书

数据中心是数字基础设施的重要组成部分&#xff0c;同时也是一整套复杂的设施。它不仅仅包括计算机系统和其它与之配套的设备&#xff08;例如通信和存储系统&#xff09;&#xff0c;还包含冗余的数据通信连接、环境控制系统、监控系统以及各种安全系统。运维管理又是数据中心…

基于智能优化算法实现自动泊车的路径动态规划(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 作为一种方便、快捷的交通工具&#xff0c;汽车已成为人们生活和工作的重要组成部分。随着汽车数量的逐年增加&#xff0c;有限…

在ubuntu上用QT写一个简单的C++小游戏(附源码)

最近老师让用Qt写一个可视化界面&#xff0c;然后就给了一个小视频&#xff0c;好奇的不得了&#xff0c;就照着做了一下 视频链接如下&#xff1a;C案例教学–一个类游戏小程序的设计与实现全过程–用到QT-简单的STL容器 创建项目 1、打开QT 如果不知道怎么下载的话&#xff…

【牛客网刷题】VL8-VL10 generate for语句、比较数大小、function的使用

&#x1f449; 写在前面 &#x1f449; 本系列博客记录牛客网刷题记录 &#x1f449; 日拱一卒&#xff0c;功不唐捐&#xff01; 目录 VL8 使用generate for语句简化代码 题目描述 输入描述 输出描述 RTL 设计 testbench 设计 仿真测试 VL9 使用子模块实现三输入数的大…

【C++ 程序设计入门基础】- Chapter One

目录 一、什么是 C&#xff1f; 1、概念 2、标准库 二、第一个 C 程序 1、下载 C 开发工具 2、开始下载好之后&#xff0c;我们先设置一下编码&#xff0c;解决中文注释不显示的问题。 3、下面我们就可以新建一个源代码 4、 编写完成后&#xff0c;我们就可以运行查看结果…

MyBatis:缓存机制详解

本篇内容包括&#xff1a;MyBatis 缓存机制概述、一级缓存与二级缓存的介绍、配置和具体流程。 一、MyBatis 缓存机制概述 在我们常见的 OLTP&#xff08;on-line transaction processing&#xff0c;联机事务处理&#xff09;类型的 Web 应用中&#xff0c;性能的瓶颈往往来源…

【Transformer 相关理论深入理解】注意力机制、自注意力机制、多头注意力机制、位置编码

目录前言一、注意力机制&#xff1a;Attention二、自注意力机制&#xff1a;Self-Attention三、多头注意力机制&#xff1a;Multi-Head Self-Attention四、位置编码&#xff1a;Positional EncodingReference前言 最近在学DETR&#xff0c;看源码的时候&#xff0c;发现自己对…

【码上掘金编程挑战赛】- 前端可冲【简历加分项】

【码上掘金编程挑战赛】1.比赛报名2. 比赛介绍赛题一&#xff1a;码上游戏赛题二&#xff1a;码上创意赛题三&#xff1a;码上文言文赛题四&#xff1a;码上10243.报名4.提交作品1.比赛报名 比赛报名链接 https://juejin.cn/challenge/1?utm_source3169 2. 比赛介绍 赛题一…

Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】

Spring5入门到实战------12、使用JdbcTemplate操作数据库&#xff08;增删改查&#xff09;。具体代码讲解 【上篇】 Spring5入门到实战------13、使用JdbcTemplate操作数据库&#xff08;批量增删改&#xff09;。具体代码讲解 【下篇】 以上两篇采用的是注解开发形式xml配置…