当前位置: 首页 > article >正文

Spring源码解析(1)

SpringBean工厂的体系结构

从BeanFactory说起

其中,BeanFactory是整个bean工厂的顶级接口,里面定义了容器最基本的约束和规范,提供了管理Bean的基本功能。例如这里有根据名称获取Bean:Object getBean(String name),根据类型获取Bean:<T> T getBean(Class<T> requiredType),判断是否是单例Bean:boolean isSingleton(String name),判断是否是多例Bean:boolean isPrototype(String name),根据名称获取Bean的类型:Class<?> getType(String name)等。由于开始先关心整个Bean工厂的体系结构,对于某个工厂的细节先不过的的考虑。

HierarchicalBeanFactory提供了子父容器的管理。

什么是子父容器?例如:在SpringMVC中,@Controller是子容器,@Service和@Repository是父容器,子容器的Bean注入的话只能够注入父容器的Bean,反之父容器不能够注入子容器的Bean,这就解释了为什么Controller层要进行Service层进行注入,而不能Service层注入被Controller注入。

父子容器关系:父容器不知道子容器的存在,而子容器可以访问父容器中的所有bean。这意味着:

  • 子容器中的bean(如@Controller)可以直接依赖并注入父容器中的bean(如@Service, @Repository)。
  • 父容器中的bean不能直接引用或注入子容器中的bean。

        AutowireCapableBeanFactory:可以让bean具备依赖注入的能力,其中的void autowireBean(Object existing) throws BeansException起着重要作用。这个方法接收一个已经存在的对象实例作为参数,并根据Spring容器中现有的bean定义,自动为该对象注入其依赖项。这意味着,即使这个对象不是通过Spring容器直接管理的(例如,它可能是手动使用new关键字创建的),也可以通过调用这个方法来让Spring为其注入所需的依赖。

        ListableBeanFactory:可以根据名称判断bean的类型,以及判断是否包含一些bean的定义信息,获取bean的一些数量等。

        ConfigurableBeanFactory:其中最主要的是设置bean的作用域

void registerScope(String scopeName, Scope scope);

和后置处理bean,意思是允许在bean初始化前后对其进行操作。这对于AOP,事务管理常见非常有用,因为可以允许不修改代码的情况下增强或改变bean的行为

void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);

DefaultListableBeanFactory一个集成不同Bean工厂的一个复合体

 XmlBeanFactory:是基于XML的配置信息,完成bean对象的创建,如何创建的呢?

创建 Bean 的过程

  1. 加载 XML 配置文件:首先需要提供一个包含 bean 定义的 XML 文件路径。它描述了应用程序中各个 bean 的配置信息,包括它们的依赖关系、作用域等。

  2. 使用 XmlBeanDefinitionReader 读取配置

    • XmlBeanFactory 内部会使用 XmlBeanDefinitionReader 来解析提供的 XML 文件。
    • XmlBeanDefinitionReader 负责从 XML 文件中读取 bean 定义,并将这些定义注册到 BeanFactory 中。
  3. 注册 Bean 定义XmlBeanDefinitionReader 解析 XML 文件后,会将每个 <bean> 元素转换成对应的 BeanDefinition 对象,并将其注册到 BeanFactory 中。这意味着所有的 bean 都被准备好,但尚未实例化。

  4. 根据需要实例化 Bean:当调用 getBean() 方法请求某个特定的 bean 时,XmlBeanFactory会检查该 bean 是否已经被实例化。如果没有,则根据其 BeanDefinition 进行实例化,同时处理依赖注入、设置属性以及执行任何必要的初始化方法。

XmlBeanFactory加载配置文件的底层原理


public class TestUser {
    public static void main(String[] args) {
		BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
		User user = (User) beanFactory.getBean("user");
		System.out.println(user);
    }
}

首先,先抛出一个疑问,Spring是如何读取XML的配置文件呢?从ClassPathResource可知,ClassPathResource实现了Resource的接口,并且使用类加载器加载资源。

 Resource接口是干什么的呢?从它的源码中可知,它继承了InputStreamSource接口。

总的图形如下。

 InputStreamSource里面有一个方法,getInputStream,它的返回值是一个输入流,ClassPathResource也重写了getInputStream这个方法。

	@Override
	public InputStream getInputStream() throws IOException {
		InputStream is;
		if (this.clazz != null) {
			is = this.clazz.getResourceAsStream(this.path);
		}
		else if (this.classLoader != null) {
			is = this.classLoader.getResourceAsStream(this.path);
		}
		else {
			is = ClassLoader.getSystemResourceAsStream(this.path);
		}
		if (is == null) {
			throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
		}
		return is;
	}

可以通过类加载器获取输入流,也可以通过字节码对象调用getResourceAsSream获取输入流。因此可以对Resouce这样理解,Resource接口用于读取资源,既可以读取普通的文件 xml porperties配置文件 txt文件,网络中的资源等
读取资源的方式也是不一样的:

  • FileSystemResource 基于文件的绝对路径进行资源的读取操作
  • ClassPathResource 基于文件的类路径进行资源的读取操作
  • ByteArrayResource 基于二级制流进行资源的读取
  • ......

到了这里,可以明白它是通过类加载器去进行读取资源的。

        配置文件读取是一个IO操作,如果每次使用时都需要进行读取的话效率较低,因此我们想让他只读取一次,读取之后把它以Java对象的方式存放在Java虚拟机中。具体来说,通过Resouce的getInputStream的方法以流的方式读到虚拟机中,以BeanDedinition对象方式来保存的。

        现在知道是如何读取xml文件了,那么它是如何进行解析的呢?通过SAX进行解析,这个后面再说。

        还要其他的问题获取到配置文件的信息之后,spring是如何将这些信息封装成Java对象的?根据BeanDefinition对象,又是如何创建Bean对象的?Bean对象的生命周期是什么?这些问题,都会在后文进行解答。第二篇文章已解答。

     如何封装成Java对象?

         前面提到,Resource接口的实现类ClassPathResource对配置信息读取,获取输入流,为了减少IO操作,需要将这些配置信息以BeanDefinition对象的方式进行存储,那么,它是如何将这些信息转化为对象呢?谁又去做这件事,是BeanDefinitionReader去做的。BeanDefinitionReader有这样一句话,Simple interface for bean definition readers,我们想想,为什么要设计成接口,因为对于spring配置文件有不同的配置形式,可以是xml,可以是纯注解的形式进行配置。

到这里,我们的关注点到了,Spring底层是如何通过XmlBeanDefinitionReader将xml信息封装成BeanDefinition对象的? XmlBeanDefinitionReader有一个loadBeanDefinitions方法,它是对配置的输入流进行解析。对吗?点开这个类,发现在package org.xml.sax;到下面的return doLoadBeanDefinitions,它是真正去干活的。那么这个loadBeanDefinitions到底是干什么的呢?从这个方法的前面的可以看出,它是用来检验循环依赖的。 
        如果循环检查,当两个或更多的配置文件相互引用时,会导致无限递归地尝试加载相同的资源,最终耗尽系统资源或导致程序崩溃。

  1. this.resourcesCurrentlyBeingLoaded.get(); 获取一个 ThreadLocal<Set<EncodedResource>> 变量,该变量保存了当前线程中正在加载的所有资源。
  2. 如果当前没有正在加载的资源,则初始化一个新的 HashSet 来存储这些资源。
  3. 使用 currentResources.add(encodedResource) 尝试将新资源添加到集合中。
  4. 如果返回 false,意味着该资源已经存在于集合中,表明发生了循环依赖加载,此时会抛出 BeanDefinitionStoreException 异常,并提示用户检查他们的导入定义。

doLoadBeanDefinitions 方法

doLoadBeanDefinitions 方法的主要任务是从给定的 InputSourceResource 中读取 XML 内容,并将其转换为一个 Document 对象。然后,它会解析该文档来提取所有的 bean 定义,并将它们注册到 BeanFactory 中。

主要步骤:
  1. 将输入源转换为 Document 对象
    • 使用 DocumentLoader 将 XML 输入流转换成 Document 对象。这是通过调用 DocumentBuilderFactory 来创建 DocumentBuilder 实例,并使用它来解析 XML 数据。
  2. 解析 Document 对象
    • 一旦有了 Document 对象,下一步就是遍历这个文档,查找所有 <bean> 标签以及其它可能包含 bean 定义的标签(如 <import> 标签)。
  3. 注册 Bean 定义
    • 对于每一个找到的 bean 定义,都会生成一个 BeanDefinition 对象,并通过 registerBeanDefinition 方法将其注册到 BeanFactory 中。

registerBeanDefinition 方法的作用

  • BeanDefinition 对象添加到 BeanFactory:这意味着将每个 bean 的定义信息存储在一个合适的数据结构中,以便后续可以根据名称或其他标识符检索和实例化这些 bean。

  • 处理覆盖逻辑:如果存在同名的 bean 定义,根据配置的不同,可能会选择覆盖旧的定义或抛出异常。

为什么要先转化为 Document 对象?

  1. 便于解析和处理:转换为 Document 对象后,可以方便地使用标准的 DOM API 来遍历和查询 XML 文档。这使得提取特定元素(如 <bean> 标签)变得简单直接。
  2. 支持复杂的 XML 结构:XML 配置文件可能包含嵌套的元素、属性和其他复杂结构。将整个文档加载为 Document 对象后,可以更容易地处理这些复杂性,确保所有必要的信息都被正确解析。
  3. 提高效率:一次性将整个文档加载为 Document 对象,可以在内存中对文档进行多次遍历和查询,而不需要反复从磁盘读取数据或重新解析 XML。

ParseBeanDefinitions是如何进行解析的

        其中的parseDefaultElement是解析基本的标签,例如bean标签,import标签alias标签,delegate是解析自定义标签,自定义标签的格式是<标签头:标签名>。对于bean标签来说,交给parseDefaultElement来进行解析,之后交给processBeanDefinition,先把标签解析为BeanDefinitionHold对象,这里面包含beanName,aliases别名,有多个,因此把他作为数组,得到对象后调用BeanDefinitionReaderUtils工具类进行bean信息的注册,注册时是保存到map集合中,key是beanid,value是beanDefinition注册完成后进行事件的发送。那么,解析成BeanDedinitionHold对象时,它是如何封装的呢?请看下一篇。

总的来说: 

  1. 资源加载:首先,使用 ClassPathResource 或其他实现 Resource 接口的类来加载 XML 配置文件。这一步将 XML 文件转换为一个 Resource 对象
  2. 创建 XmlBeanFactory 实例:创建 XmlBeanFactory 实例时传入上面创建的 Resource 对象。实际上,XmlBeanFactory 内部会使用 XmlBeanDefinitionReader 来读取和解析 XML 文件
  3. 资源包装与循环依赖检查:在 XmlBeanDefinitionReader 中,当掉用 loadBeanDefinitions(Resource resource) 方法时,首先会对资源进行包装成 EncodedResource,这是为了支持编码设置等功能。然后,它会检查当前是否已经在加载相同的资源,以防止循环依赖问题。
  4. 真正加载 Bean 定义

    调用 doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法来进行实际的加载工作。这个方法首先将 XML 文件内容转换为 W3C DOM Document 对象。
  5. 解析 Document 并注册 Bean 定义

    使用 registerBeanDefinitions(Document doc, Resource resource) 方法处理 Document 对象。这个方法内部会调用 BeanDefinitionDocumentReader 的 registerBeanDefinitions 方法。BeanDefinitionDocumentReader 会遍历文档中的 <bean> 标签和其他相关的标签(如 <import><alias>),并将每个找到的 bean 定义转化为 BeanDefinition 对象。
  6. 具体解析 Bean 定义

    parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 方法负责遍历 XML 文档的根元素及其子元素。对于每个 <bean> 元素,调用 processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) 方法进一步解析属性,并生成 BeanDefinitionHolder 对象。最终通过 registerBeanDefinition 方法将 BeanDefinition 注册到 BeanFactory 中。

        在之前提到的XmlBeanFactory这个工厂,现在Spring已经不提倡使用它了,取代它的是DefaultListableBeanFactory。它们之间的异同点

相同:

  • Bean管理:两者都是用于管理 Spring 容器中的 bean 实例的核心组件。
  • Bean定义的抽象:两者都依赖于 BeanDefinition 来描述 bean 的元数据。
  • 生命周期回调:两者都支持对 bean 生命周期的管理,包括初始化方法(如 init-method 或 @PostConstruct 注解)和销毁方法(如 destroy-method 或 @PreDestroy 注解)。
  • 资源共享:两者都可以与 Spring 的资源加载机制(如 ResourceLoaderResource 接口)结合使用,允许从不同的源(如文件系统、类路径等)加载配置信息。

不同:

  • 限制性:主要用于基于 XML 的配置,并且不支持很多现代 Spring 特性,例如注解配置、国际化消息资源等。缺乏对事件发布机制的支持,这使得它不适合需要事件驱动的应用场景。
  • 生命周期管理:仅提供基础的 bean 生命周期管理功能,缺少对更多高级特性的支持,比如对 @PostConstruct 和 @PreDestroy 注解的支持有限。
  • 扩展性:不如 DefaultListableBeanFactory 灵活,难以扩展以支持更多的功能。
  • 更强大的功能集:作为 ConfigurableListableBeanFactory 接口的一个实现,提供了比 XmlBeanFactory 更丰富的功能集合。支持多种类型的配置源,包括 XML 文件、注解、Java 配置类等。提供了对 @PostConstruct 和 @PreDestroy 注解的支持,以及对 @Autowired 和其他依赖注入注解的支持。
  • 事件发布机制:内置了对事件监听器的支持,允许应用程序通过事件驱动的方式进行组件间的通信。
  • 灵活的扩展点:可以通过添加 BeanPostProcessor、自定义作用域、属性编辑器等方式轻松扩展其功能。
  • 集成与兼容性:更好地与其他 Spring 模块(如 Spring MVC, Spring AOP 等)集成,提供全面的上下文环境支持。

http://www.kler.cn/a/563527.html

相关文章:

  • 面试葵花宝典之React(持续更新中)
  • 设计模式-行为型-责任链模式
  • ShardingCore:EF Core实战教程
  • 2025年能源工程与电气技术国际学术会议(EEET2025)
  • Rust 并发编程:使用消息传递进行线程间数据共享
  • IDEA关闭SpringBoot程序后仍然占用端口的排查与解决
  • SpringBoot项目注入 traceId 来追踪整个请求的日志链路
  • 数据结构---定长顺序表
  • 强化学习概览
  • c++进阶之----二叉搜索树
  • 基于Matlab实现倒立摆仿真程序
  • HTML——前端基础1
  • 深度学习(3)-TensorFlow入门(常数张量和变量)
  • P10108 [GESP202312 六级] 闯关游戏
  • 湘潭大学计算机复试详细攻略(调剂)
  • 物联网串口综述
  • 【HarmonyOS Next】鸿蒙TaskPool和Worker详解 (一)
  • 内网穿透-端口转发
  • 瑞芯微RK安卓Android主板GPIO按键配置方法,触觉智能嵌入式开发
  • Apache Commons Chain 与 Spring Boot 整合:构建用户注册处理链