【源码解析】Spring源码解读-bean的加载

news2024/10/7 18:24:46

在这里插入图片描述
在这里插入图片描述

Spring的整体流程其实就是通过配置 xml、注解将自定义bean类信息进行配置,然后通过BeanDefinitionReader读取配置信息,由Dom转换成xml解析成Docment。在通过加载的配置信息进行初始化Bean对象,然后在对象的前后进行处理,也就是不同的处理器,AOP、自动装配、事务等其实都是这个原理。而我们的IOC容器本质其实就是一个Map对象。

spring-bean最核心的两个类:DefaultListableBeanFactory和XmlBeanDefinitionReader

DefaultListableBeanFactory

在这里插入图片描述
上面类图中各个类及接口的作用如下:

  • AliasRegistry:定义对alias的简单增删改等操作
  • SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现
  • SingletonBeanRegistry:定义对单例的注册及获取
  • BeanFactory:定义获取bean及bean的各种属性
  • DefaultSingletonBeanRegistry:默认对接口SingletonBeanRegistry各函数的实现
  • HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持
  • BeanDefinitionRegistry:定义对BeanDefinition的各种增删改操作
  • FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能
  • ConfigurableBeanFactory:提供配置Factory的各种方法
  • ListableBeanFactory:根据各种条件获取bean的配置清单
  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurationBeanFactory的功能
  • AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后处理器
  • AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现
  • ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等
  • DefaultListableBeanFactory:综合上面所有功能,主要是对Bean注册后的处理
    XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取Bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性。在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册

XmlBeanDefinitionReader

在这里插入图片描述
XML配置文件的读取是Spring中重要的功能,因为Spring的大部分功能都是以配置作为切入点的,可以从XmlBeanDefinitionReader中梳理一下资源文件读取、解析及注册的大致脉络,首先看看各个类的功能

ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource
BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能
EnvironmentCapable:定义获取Environment方法
DocumentLoader:定义从资源文件加载到转换为Document的功能
AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader类定义的功能进行实现
BeanDefinitionDocumentReader:定义读取Document并注册BeanDefinition功能
BeanDefinitionParserDelegate:定义解析Element的各种方法

ClassPathXmlApplicationContext

因为我们是使用XML进行加载启动的。

		ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
		ServiceA beanServiceA = classPathXmlApplicationContext.getBean(ServiceA.class);

启动的时候会刷新容器

	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh(); //刷新容器
		}
	}

而这个刷新方法其实是比较核心的方法

	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

			//准备上下文环境 Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			// 工厂创建:BeanFactory第一次开始创建的时候,有xml解析逻辑。
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			//给容器中注册了环境信息作为单实例Bean方便后续自动装配;放了一些后置处理器处理(监听、xxAware功能) Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				//留给子类的模板方法,允许子类继续对工厂执行一些处理; Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				//【大核心】工厂增强:执行所有的BeanFactory后置增强器;利用BeanFactory后置增强器对工厂进行修改或者增强,配置类会在这里进行解析。 Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				//【核心】注册所有的Bean的后置处理器 Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();

				//初始化国际化功能 Initialize message source for this context.
				initMessageSource();

				//初始化事件多播功能(事件派发) Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				//注册监听器,从容器中获取所有的ApplicationListener; Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				//【大核心】bean创建;完成 BeanFactory 初始化。(工厂里面所有的组件都好了)
				finishBeanFactoryInitialization(beanFactory);

				//发布事件 Last step: publish corresponding event.
				finishRefresh();
			}

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

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
				contextRefresh.end();
			}
		}
	}
	protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory(); //创建保存所有Bean定义信息的档案馆
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);
			this.beanFactory = beanFactory;
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}
	protected DefaultListableBeanFactory createBeanFactory() {
		return new DefaultListableBeanFactory(getInternalParentBeanFactory()); //创建档案馆
	}

在这里插入图片描述
因为是通过XML配置的,所以就使用XMLWebApplicationContext

	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		//XML读取器
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

解析以及注册BeanDefinitions

在这里插入图片描述

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

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}

		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); //这里是重点
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			Document doc = doLoadDocument(inputSource, resource); //利用dom解析工具把xml变成Document
			int count = registerBeanDefinitions(doc, resource); //根据document信息注册bean信息
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
}

这里主要就是验证XML文件 调用loadDocument根据xml文件获取Document实例,调用registerBeanDefinitions 注册Bean实例。具体的XML转换成Docuemnt流程这里不细说。

	protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}

解析及注册BeanDefinitions

上面是进行加载xml文件转换成Document对象后,就调用了registerBeanDefinitions方法。

	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;
	}

BeanDefinitionDocumentReader是一个接口,具体实现其实是DefaultBeanDefinitionDocumentReader,createBeanDefinitionDocumentReader是利用工具实现 DefaultBeanDefinitionDocumentReader,而这里显然使用的是接口表达。也就是面向接口编程。

	protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
		return BeanUtils.instantiateClass(this.documentReaderClass);
	}
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		doRegisterBeanDefinitions(doc.getDocumentElement());
	}

解析逻辑的核心方法doRegisterBeanDefinitions

	protected void doRegisterBeanDefinitions(Element root) {
		// Any nested <beans> elements will cause recursion in this method. In
		// order to propagate and preserve <beans> default-* attributes correctly,
		// keep track of the current (parent) delegate, which may be null. Create
		// the new (child) delegate with a reference to the parent for fallback purposes,
		// then ultimately reset this.delegate back to its original (parent) reference.
		// this behavior emulates a stack of delegates without actually necessitating one.
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				// We cannot use Profiles.of(...) since profile expressions are not supported
				// in XML config. See SPR-12458 for details.
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}

		preProcessXml(root);
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);

		this.delegate = parent;
	}

最终解析动作落地在两个方法处:parseDefaultElement(ele, delegate) 和 delegate.parseCustomElement(root)。我们知道在 Spring 有两种 Bean 声明方式:

配置文件式声明:
自定义注解方式:tx:annotation-driven

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					if (delegate.isDefaultNamespace(ele)) { //遍历文档中的所有节点
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

至此,整个bean加载的过程就完毕了,但是后续其实还有对bean的初始化、实例化以及AOP、事务等流程。

小结

本篇其实主要就是从bean是如何加载xml文件生成 解析及注册BeanDefinitions过程。后续我们会接着介绍Bean的生命周期、初始化、AOP、MVC以及tomcat源码。

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

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

相关文章

2023-10-22

一、总线通信协议简介 总线是计算机系统中负责连接各个硬件的通信线路&#xff0c;它可以传输数据、地址和控制信号。通信协议是指双方实体完成通信所遵循的规则。总线通信协议是一种规定总线设备之间数据通信方式和方法的规则&#xff0c;它包括数据的通信方式、速率、格式、…

python爬虫之js逆向入门:常用加密算法的逆向和实践

一、强大的Chrome DevTools Chrome DevTools是一组内置于Google Chrome浏览器中的开发者工具&#xff0c;用于帮助开发人员调试、分析和优化Web应用程序。它提供了一系列功能强大的工具&#xff0c;用于检查和编辑HTML、CSS和JavaScript代码&#xff0c;监视网络请求、性能分析…

redis怎么设计一个高性能hash表

问题 redis 怎么解决的hash冲突问题 &#xff1f;redis 对于扩容rehash有什么优秀的设计&#xff1f; hash 目标是解决hash冲突&#xff0c;那什么是hash冲突呢&#xff1f; 实际上&#xff0c;一个最简单的 Hash 表就是一个数组&#xff0c;数组里的每个元素是一个哈希桶&…

ida81输入密码验证算法分析以及破解思路

本文分析了ida81对输入密码的验证流程&#xff0c;分别对输入密码到生成解密密钥、密码素材的生成过程以及文件数据的加密过程这三个流程进行分析&#xff0c;并尝试找一些可利用的破绽。很遗憾&#xff0c;由于水平有限&#xff0c;目前也只是有个思路未能完全实现&#xff0c…

查看当前cmake版本支持哪些版本的Visual Studio

不同版本的的cmake对Visual Studio的版本支持不同&#xff0c;以下图示展示了如何查看当前安装的cmake支持哪些版本的Visual Studio。 1.打开cmake-gui 2.查看cmake支持哪些版本的Visual Studio

28. 使用 k8e 玩转 kube-vip with Cilium‘s Egress Gateway 特性

因为在私有云环境下,我们需要保障集群服务 APIServer地址的高可用,所以提供的方案就是使用一个 VIP 让 API Server 的流量可以负载均衡的流入集群。另外,kube-vip 还支持 Service LB,方便SVC 服务的负载均衡,结合 cilium Egress Gateway 特性可以做到集群内的容器对外访问…

canvas绘制刮涂层抽奖效果

实现的效果&#xff1a;主要用到画布设置图层覆盖效果globalCompositeOperation属性 实现的源代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"…

canvas常用的几种重叠绘图设置globalCompositeOperation

globalCompositeOperation描述了2个图形交叉的时候是什么样子&#xff0c;它的值有很多&#xff0c;这里就盗一张很经典的图&#xff1a; 我们来看一个source-in的例子吧&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset&q…

论文-分布式-并发控制-Lamport逻辑时钟

目录 前言 逻辑时钟讲解 算法类比为面包店内取号 Lamport算法的时间戳原理 Lamport算法的5个原则 举例说明 算法实现 参考文献 前言 在并发系统中&#xff0c;同步与互斥是实现资源共享的关键Lamport面包店算法作为一种经典的解决并发问题的算法&#xff0c;它的实现原…

VTM/VVC 编译与测试-- YUV与RGB空间转换

环境配置:ubuntu 18.04 一、VVC测试 软件下载: 官网下载 VVC:http://jvet.hhi.fraunhofer.de/ 这里可以选择版本。 编译工具: 在开始编译前,需要电脑的环境中中有gcc、g++、cmake、make这四个工具。 sudo apt-get install gcc g++ sudo apt-get install cmake sudo …

Android微信逆向--实现发朋友圈动态

Android微信逆向--实现发朋友圈动态 0x0 前言# 最近一直在研究Windows逆向的东西&#xff0c;想着快要把Android给遗忘了。所以就想利用工作之余来研究Android相关的技术&#xff0c;来保持对Android热情。调用微信代码来发送朋友圈动态一直是自己想实现的东西&#xff0c;研…

Internet Download Manager 逆向分析

写在前面 文章仅供学习&#xff0c;切勿用于商业用途&#xff0c;出于版权原因&#xff0c;文章不提供资源下载。 论坛上较早之前已经有前辈对IDM的序列号算法进行过逆向分析 以及最近有师傅尝试对本篇文章对象相同的版本进行了逆向分析&#xff0c;但是比较遗憾的是该文章并…

瑞萨e2studio(26)----SPI驱动TFT-LCD屏

瑞萨e2studio.26--SPI驱动TFT-LCD屏 概述视频教学csdn课程样品申请完整代码下载屏幕接口接线方式新建工程工程模板保存工程路径芯片配置工程模板选择时钟配置开始SPI配置SPI属性配置IO配置头文件定义回调函数lcd_init.clcd.c设置区域颜色显示字符串显示汉字显示图片结果演示 概…

点击查看详情 | 网页版微信客户管理系统如何操作试用?

微信作为我们日常生活中最常用的社交应用之一&#xff0c;早已成为我们与朋友、家人和同事保持联系的重要工具&#xff0c;也是营销引流的重要平台。 通过微信营销&#xff0c;可以比较精准定向亲近用户。而微信的功能并没有很能满足做微信营销的人群&#xff0c;所以我们需要借…

S5PV210裸机(五):定时器

本文主要探讨210定时器相关知识&#xff0c;210定时器主要包含PWN定时器&#xff0c;系统定时器&#xff0c;看门狗&#xff0c;RTC。 PWM定时器 210有5个PWM定时器,timer0、1、2、3通过对应PIO产生PWM波形信号并输出,timer4没有GPIO只产生内部定时器中断 PWM…

Python 安装CSF(布料模拟滤波)的环境配置

一、环境配置 1.1 下载源码: Github下载CSF库源码 1.2 解压文件如下: 二、安装CSF库 2.1在解压文件中找到python文件夹所在目录 2.2 输入cmd并回车,来打开终端窗口 2.3激活虚拟环境 通过: activate +你的虚拟环境名称。来激活安装CSF库的虚拟环境。【不执行此

k8s镜像加devops

展示 1.配套资料2.devops 3.elk日志收集 4.grafana监控 5.dashboard![在这里插入图片描述](https://img-blog.csdnimg.cn/bf294f9fd98e4c038858a6bf5c34dbdc.png 目的 学习k8s来来回回折腾很久了&#xff0c;光搭个环境就能折腾几天。这次工作需要终于静下心来好好学习了一…

【USMA】N1CTF2022-praymoon

前言 本题主要利用 USMA 解题&#xff0c;当然还有其他做法&#xff0c;暂时不表 程序分析 启动脚本就不看了&#xff0c;该开的保护都开了。看下文件系统初始化脚本&#xff1a; #!/bin/shmkdir /tmp mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpf…

codeforces (C++ Haunted House)

题目&#xff1a; 翻译&#xff1a; 思路&#xff1a; 1、由题目可知&#xff0c;他想让我们判断交换相邻字符位置后将二进制转为十进制后&#xff0c;能否整除2的次方。能整除即输出需要交换的次数&#xff0c;不能则输出-1。&#xff08;例&#xff1a;输入3和010这组数据就…

二分查找:如何快速定位IP对应的省份地址?

文章来源于极客时间前google工程师−王争专栏。 通过IP地址查找IP归属地功能&#xff1a; 这个功能是通过维护一个很大的IP地址库来实现。地址库中包含IP地址范围和归属地的对应关系。 当我们查询202.201.133.13这个IP地址归属地时&#xff0c;在地址库中搜索&#xff0c;这个…