深入浅出,从源码搞清Bean的加载过程

news2024/11/29 22:34:16

深入浅出,从源码搞清Bean的加载过程

前言

Bean的加载过程算是面试中的老生常谈了,今天我们就来从源码层面深入去了解一下Spring中是如何进行Bean的加载的

Spring

先看示例代码:

public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/spring/text.xml");
        System.out.println(context.getBean("stringutil"));
    }

我们就从这个getBean去探索Spring如何进行类的加载。首先我们先看看ClassPathXmlApplicationContex的继承关系:

而DefaultResourceLoader又是实现的ResourceLoader接口

我们通过不断去溯源new ClassPathXmlApplicationContext这个构造方法
 

我们先通过super一直溯源到最后

我们再来看看setParent的源码:
 

发现这里仅仅是给ApplicationContex设设置上下文。
所以真正的重点还是上面的refresh那里。

refresh

先看源码,源码有点长,但是我加了注解,请慢慢看,我会在里面进行一些分析的

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			
			prepareRefresh();

			
			
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			
			prepareBeanFactory(beanFactory);

			try {
				
				postProcessBeanFactory(beanFactory);

				
				invokeBeanFactoryPostProcessors(beanFactory);

				
				registerBeanPostProcessors(beanFactory);

				
				initMessageSource();

				
				initApplicationEventMulticaster();

				
				onRefresh();

				
				registerListeners();

				
				finishBeanFactoryInitialization(beanFactory);

				
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				
				destroyBeans();

				
				cancelRefresh(ex);

				
				throw ex;
			}

			finally {
				
				
				resetCommonCaches();
			}
		}
	}

简单总结一下就是刷新Spring应用的上下文,确保所有的Bean和相关组件都被正确初始化和配置。

BeanFactory

我们顺着getBean去看源码

BeanFacoty是用来加载Bean的重要接口,那么我们这里的实现类在哪呢?
让我们接着debug进去看看
 

为什么来到这里了呢?让我们看看AbstractApplicationContext的类关系
 

原来如此,我们最终还是要在AbstractApplicationContext里面去实现getBean的。
接着我们来看看这个BeanFactory是如何出现的,关注到上面的refresh源码中的这一行

以下是源码:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        this.refreshBeanFactory();
        return this.getBeanFactory();
}

看来重点在refreshBeanFactory()

    protected final void refreshBeanFactory() throws BeansException {
        
        if (this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }
		
        try {
	        
            DefaultListableBeanFactory beanFactory = this.createBeanFactory();
            beanFactory.setSerializationId(this.getId());
            beanFactory.setApplicationStartup(this.getApplicationStartup());
            this.customizeBeanFactory(beanFactory);
            
            this.loadBeanDefinitions(beanFactory);
            
            this.beanFactory = beanFactory;
        } catch (IOException var2) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var2);
        }
    }
    
protected DefaultListableBeanFactory createBeanFactory() {
	return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
}

而这个DefaultListableBeanFactory实际上也是BeanFactory的另一个实现
我们顺着loadBeanDefinitions去看,发现在我们的xml加载例子中,我们进入了这个类

让我研究研究这个源码

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	
       XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
       beanDefinitionReader.setEnvironment(this.getEnvironment());
	
       beanDefinitionReader.setResourceLoader(this);
       beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
       this.initBeanDefinitionReader(beanDefinitionReader);
       
       this.loadBeanDefinitions(beanDefinitionReader);
   }

对这个XmlBeanDefinitionReader,我们可以看见他是这样的继承关系

从右边可以看见,还有着Groovy和Properties两种实现,不过我们不在意他们的实现,接着我们关注到this.loadBeanDefinitions(beanDefinitionReader);这一行,我们debug进去

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
       Resource[] configResources = this.getConfigResources();
       if (configResources != null) {
           reader.loadBeanDefinitions(configResources);
       }

       String[] configLocations = this.getConfigLocations();
       if (configLocations != null) {
           reader.loadBeanDefinitions(configLocations);
       }

   }

接着让我们进入loadBeanDefinitons这个方法中

public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int count = 0;
        String[] var3 = locations;
        int var4 = locations.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String location = var3[var5];
            count += this.loadBeanDefinitions(location);
        }

        return count;
}

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {  
    return this.loadBeanDefinitions(location, (Set)null);  
}

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    
    ResourceLoader resourceLoader = this.getResourceLoader();
    
    if (resourceLoader == null) {
        
        throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
    } else {
        int count;
        
        if (resourceLoader instanceof ResourcePatternResolver) {
            ResourcePatternResolver resourcePatternResolver = (ResourcePatternResolver)resourceLoader;

            try {
                
                Resource[] resources = resourcePatternResolver.getResources(location);
                
                count = this.loadBeanDefinitions(resources);
                if (actualResources != null) {
                    
                    Collections.addAll(actualResources, resources);
                }

                
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                }

                return count;
            } catch (IOException var7) {
                
                throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var7);
            }
        } else {
            
            Resource resource = resourceLoader.getResource(location);
            
            count = this.loadBeanDefinitions((Resource)resource);
            if (actualResources != null) {
                
                actualResources.add(resource);
            }

            
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
            }

            return count;
        }
    }
}

我们接着追踪loadBeanDefinitions,最终在XmlBeanDefinitionReader中找到答案:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return this.loadBeanDefinitions(new EncodedResource(resource));
}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Loading XML bean definitions from " + encodedResource);
        }
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}

	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		
		try {
			
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			
		}
	}


protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
		}
		catch (BeanDefinitionStoreException ex) {
			
		}
	}

	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

如果接着往下走就会发现,将输入流中的xml标签解析然后注册BeanDefiniton工厂中
至此我们成功把xml解析成功了,接下来我们回到这里

让我们关注这个函数

我们关注到这个函数,然后我们关注getBean

我们第一次进来这里会式null,所以会走下面的逻辑,等后续我们实例代码中使用getBean的时候这里就不为空了

然后下面的else的内容就是使用BeanFactory去创建一个Bean,代码很长,有兴趣可以自己看,我们要关注的是这一段之后的内容

{
			
		Object sharedInstance = getSingleton(beanName);
		
		final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
		
		if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
						@Override
						public Object getObject() throws BeansException {
							try {
							 	
								return createBean(beanName, mbd, args);
							}
							catch (BeansException ex) {
								destroySingleton(beanName);
								throw ex;
							}
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
	
	
	if (newSingleton) {
		addSingleton(beanName, singletonObject);
	}    
	
}

在这之后经过一系列的Bean的初始化,然后放入缓存中,就算是大功告成了,最后返回示例代码中的getBean即可从单例缓存中拿到bean !!!

总结

Spring加载Bean的过程为:

  1. 创建Spring容器
  2. 读取配置(或者注解),解析内容,封装到BeanDefinition的实现类中(通过registerBeanDefiniton方法来注册到ConcurrentHashMap中,然后将beanName放到list中方便取出)
  3. 实例化Bean放到Spring容器中
    至于一些实例化之类的主要就是BeanPostProcessor了,可以从我的手写rpc项目的文章中找到

Bean的生命周期

  1. 创建Bean的实例:Bean容器会先找到Bean的定义,然后通过Java反射API来创建Bean的实例
  2. Bean属性赋值/填充:为Bean设置相关属性和依赖,例如填入@Autowired等注解注入的对象,setter方法和构造函数
  3. Bean初始化:

    Bean的初始化

  4. 销毁Bean:把Bean的销毁方法记录下来,将爱需要销毁Bean或者销毁容器时,调用这些方法去释放Bean所持有的资源
    • 如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
    • 如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的 Bean 销毁方法。或者,也可以直接通过@PreDestroy 注解标记 Bean 销毁之前执行的方法。
      实例化和初始化的区别:
      初始化会调用PostConstruct来调用Bean的初始化方法

循环依赖

循环依赖的详细介绍可以去我的另一篇文章中找到,这里就带大家找到三级缓存的实际位置
 

解决流程如下:

SpringBoot

SpringBoot实际上就是通过自定义注解去标记,然后启动的时候去扫描这些注解来创建Bean,从而替代Spring的xml配置方式,所以这里先略过,后续会单独出一片文章深入探讨SpringBoot,大体介绍一下流程:

  1. 启动类: 启动类上使用 @SpringBootApplication 注解,包含了 @ComponentScan 和 @EnableAutoConfiguration。
  2. 自动配置: 通过 @EnableAutoConfiguration 注解加载自动配置类,这些类通常使用 @Conditional 注解来控制 Bean 的创建。
  3. 组件扫描: 扫描启动类所在包及其子包中的组件(如 @Component, @Service, @Repository, @Controller 等)。
  4. 条件注解: 根据条件注解的配置,有选择地注册 Bean

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

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

相关文章

微服务之间的相互调用的几种常见实现方式对比

目录 微服务之间的相互调用的几种实现方式 一、HTTP HTTP/RESTful API调用工作原理 二、RPC 设计理念与实现方式 协议与传输层 RPC远程调用工作原理 应用场景与性能考量 特点 三、Feign 设计理念与实现方式 协议与传输层 Feign调用的基本流程 Feign调用的工作原理…

算法训练营打卡Day19

目录 1.二叉搜索树的最近公共祖先 2.二叉树中的插入操作 3.删除二叉搜索树中的节点 题目1、二叉搜索树的最近公共祖先 力扣题目链接(opens new window) 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有…

温度转换-C语言

1.问题&#xff1a; 输入一个华氏温度&#xff0c;要求输出摄氏温度。公式为 c5(F-32)/9&#xff0c;取位2小数。 2.解答&#xff1a; scanf("%lf",&f);或者scanf("%f",&f);如果你前面定义的f是用double类型的话&#xff0c;就应该用%lf格式&…

deploy thingsboard

ThingsBoard部署 平台&#xff1a;windows10&#xff0c;idea2022&#xff0c;postgres15 maven仓库 进入thingsboard源码下载目录: 主要执行以下两个命令&#xff1a; mvn编译&#xff1a; mvn clean install -Dmaven.test.skiptrue编译报错时&#xff1a; 清除java进程 t…

计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

UART通信—基于江科大源码基础进行的改进和解析

我就不讲理论了&#xff0c;CSDN上大佬属实多&#xff0c;我就只讲代码了&#xff0c;串口的基本理论&#xff0c;大家去看其他大佬写的吧 一、源文件的组成 1、包含的头文件 stm32f10x.h 是STM32F10x系列微控制器的标准外设库&#xff08;Standard Peripheral Library&…

C语言基础(7)之操作符(1)(详解)

目录 1. 各种操作符介绍 1.1 操作符汇总表 2. 移位操作符 2.1 移位操作符知识拓展 —— 原码、反码、补码 2.2 移位操作符讲解 2.2.1 右移操作符 ( >> ) 2.2.2 左移操作符 ( << ) 3. 位操作符 3.1 & (按位与) 3.2 | (按位或) 3.3 ^ (按位异或) 3.4…

【AI学习】Mamba学习(二):线性注意力

上一篇《Mamba学习&#xff08;一&#xff09;&#xff1a;总体架构》提到&#xff0c;Transformer 模型的主要缺点是&#xff1a;自注意力机制的计算量会随着上下文长度的增加呈平方级增长。所以&#xff0c;许多次二次时间架构&#xff08;指一个函数或算法的增长速度小于二次…

C++ 多态:重塑编程效率与灵活性

目录 多态的概念 多态的定义及实现 多态的构成条件 虚函数 虚函数的重写 虚函数重写的两个例外&#xff1a; 1. 协变(基类与派生类虚函数返回值类型不同) 2. 析构函数的重写(基类与派生类析构函数的名字不同&#xff09; 析构函数要不要定义成虚函数&#xff1f;&…

绝对值得收藏!分享7款ai写作论文免费一键生成网站

在当前的学术研究和写作过程中&#xff0c;AI写作工具已经成为了许多研究者和学生的重要助手。这些工具不仅能够提高写作效率&#xff0c;还能帮助生成高质量的论文内容。以下是七款免费的AI写作论文生成器&#xff0c;其中特别推荐千笔-AIPassPaper。 1.千笔-AIPassPaper 千…

信号处理: Block Pending Handler 与 SIGKILL/SIGSTOP 实验

1. 信号处理机制的 “三张表” kill -l &#xff1a;前 31 个信号为系统标准信号。 block pending handler 三张表保存在每个进程的进程控制块 —— pcb 中&#xff0c;它们分别对应了某一信号的阻塞状态、待处理状态以及处理方式。 block &#xff1a;通过 sigset_t 类型实现&…

YOLO11改进 | 检测头 | 融合渐进特征金字塔的检测头【AFPN3】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 本文介绍了一个渐进特征金字塔网络&…

关于 S7 - 1200 通过存储卡进行程序更新

西门子S7-1200系列PLC可以通过存储卡进行程序的更新&#xff0c;固件版本的升级以及程序数据的存储多项功能。本例进行程序更新的操作。 存储卡的订货号以及存储容量 一&#xff1b;如何插入存储卡 在CPU断电下&#xff0c;将CPU上挡板向下掀开&#xff0c;可以看到右上角有一…

ai写作论文会被检测吗?分享市面上7款自动写论文网站

近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;AI写作工具在学术界引起了广泛关注。然而&#xff0c;这些工具的使用也引发了关于学术诚信和检测机制的讨论。根据多所高校的声明&#xff0c;为了应对AI代写论文的现象&#xff0c;许多高校已经开始引入论文检测工具…

Python入门:深入了解__init__.py 文件(如何实现动态导入子模块)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 `__init__.py` 的作用示例:📝 如何编写 `__init__.py`1. 空的 `__init__.py`2. 导入子模块3. 初始化代码4. 动态导入子模块📝 编写 `__init__.py` 的技巧和注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 在…

01:(寄存器开发)点亮一个LED灯

寄存器开发 1、单片机的简介1.1、什么是单片机1.2、F1系列内核和芯片的系统架构1.3、存储器映像1.4、什么是寄存器 2、寄存器开发模板工程3、使用寄存器点亮一个LED4、代码改进15、代码改进2 本教程使用的是STM32F103C8T6最小系统板&#xff0c;教程来源B站up“嵌入式那些事”。…

前缀和(6)_和可被k整除的子数组_蓝桥杯

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 前缀和(6)_和可被k整除的子数组 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 …

kubeadm部署k8s

1.1 安装Docker [rootk8s-all ~]# wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.huaweicloud.com/docker-ce/linux/centos/docker-ce.repo [rootk8s-all ~]# sed -i sdownload.docker.commirrors.huaweicloud.com/docker-ce /etc/yum.repos.d/docker-ce.repo [ro…

基于Keras的U-Net模型在图像分割与计数中的应用

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色&a…