BeanDefinitionReader接口,Spring加载Bean的过程(非常流畅和容易理解)(Spring源码分析1)

本文主要是介绍BeanDefinitionReader接口,Spring加载Bean的过程(非常流畅和容易理解)(Spring源码分析1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、前言

        前言部分,介绍Spring框架的工作和大致原理,有基础的小伙伴可以跳过

        我们现在最常使用的开发框架SSM,分别是Spring、Spring MVC和Mybatis,其功能已经超出原生Spring非常多,所以想学习Spring原理,首先要走到底层。

        第一,Spring框架,最关键的功能是bean的管理。

        bean,可以理解成一个实例化的实体对象,使用过Spring的都知道,我们配置XML文件或者注解开发时,需要配置一个bean属性【在注解中,则是使用@Component注解】

        这个配置的目的,就是告诉Spring某些信息。

        最重要也是最基础的两个,一个是bean指向的类,另一个是bean的名称。

        当然,还有单例或原型、初始化方法、销毁方法等等。

        不过所有的这些信息,都指向BeanDefinition

        BeanDefinition,就是Spring对bean的定义,我们要使用bean,绕不开这个接口。

        第二,Spring的核心设计,在于如何用配置文件,管理Bean

        学习过设计模式的同学,应该对“工厂模式”有所了解,在此不介绍基础原理。

        我们只需记住,Spring中,工厂是用BeanFactory接口体现的

        不过我们常用的工厂方法,很明显有一个缺点:对于接口Data,它每增加一个实现类,就要配置相关信息。

        这也是Spring的问题所在,所以我们一旦增加一个类,就要用@Component注解该类。

        我们考虑一个问题:明明只有配置文件,为何Spring框架却能将XML文件中的Bean定义,实例化出来呢?

        记住下面的流程

        第一步,必须把Bean信息读取出来,也就是说,要有读取XML文件的能力。

        第二步,Spring框架必须能实例化这个Bean,并且要有至少一个容器【数据结构】,来存储这些实例对象。

        第三步,Spring至少对外有一个接口,使外界可以得到这个Bean。

二、从代码入手,学习底层的Spring使用方法【下面会统一说明这些名词】     

        我们考虑下面这行代码,现在已经很少有人这么写了,不过为了学习Spring的原理,我们必须先把Spring的Context部分放在一旁,专注于beans这个核心部分。

    public static void main(String[] args) throws Exception{// 建造Spring工厂DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// 初始化读取器。XmlBeanDefinitionReader beanDefinitionReader =new XmlBeanDefinitionReader(beanFactory);// 调用 加载 方法。【注:虽然是叫“加载”方法,但事实上完成了加载、注册和实例化功能】beanDefinitionReader.loadBeanDefinitions("Spring.xml");MyService myService = (MyService) beanFactory.getBean("myService");myService.serve(); // 只有一个打印方法}

        这段代码做了什么事呢?

        按流程来说,请保持疑问。

        第一,new一个BeanFactory工厂

                疑问1:为什么使用Default Listable Bean Factory,而不是使用BeanFactory作为声明?

                疑问2:为什么这个对象的new,不需要参数?那把它new出来有什么用处?

        第二,new一个BeanDefinitionReader读取器

                疑问1:Xml BeanDefinitionReader和我说的BeanDefinitionReader有什么关系?

                疑问2:将这个BeanFactory传递进去,会发生什么?为什么需要这个Factory?

        第三,调用读取器reader的load方法

                疑问1:这个方法做了什么?为什么一下子就能使用Bean了?

三、记住几个重点名词

        首先,我要说一下几个重点名词,以及它的含义。

        记住:loadBeanDefinitions方法、Resource接口、resourceLoader属性。

        按照类来解释。

        BeanDefinition

                Bean的定义,这个我想大家都了解,不过请记住一点:BeanDefinition是Spring的唯一数据类,这个类定义了所有数据,其它类定义的数据,都是为了辅助这个类的使用。

        BeanFactory

                工厂类,但不负责加载、初始化Bean,这个接口的唯一作用,其实是管理Bean【比如在外界需要时,传递一个bean对象】

                这个接口的下层实现接口非常多,比如ListableBeanFactory,在此不介绍。

                最重要的一个实现类,即DefaultListableBeanFactory,这个类同时实现了另一条支线上的BeanDefinitionRegistry接口。

                这个实现类,有一个HashMap的数据结构beanDefinitionMap,存储了所有的bean,无论是bean的注册还是初始化,都要使用到它。

                这就相当于生出了一个同时拥有二者能力的儿子。

        BeanDefinitionRegistry

                一个拥有注册Bean能力的接口,并非本文重点。

                不过在Bean加载结束后,注册就要依靠它。

        BeanDefinitionReader

                这是本文的重点之一,这个接口的类继承结构如下图所示。

                我们忽略Groovy、Properties,因为它们不是很重要。

                Reader接口只定义了一些方法,在此不赘叙。

                在Abstract中,实现了通用的方法,其中最重要的就是loadBeanDefinitions方法,这个方法的重载很多,实际的加载过程,甚至放在子类中实现。

                不过不影响它的重要性,因为它将资源文件,全部转化为Resource资源,以便可以统一地访问数据。

                至此,介绍完毕。

四、加载Bean,Spring的初始工作

        有了前文的基础,这一步就轻松很多,让我们来看看源码。

        我必须解释一下,BeanDefinitionReader读取器,它的new需要一个factory的原因,是它在加载方法中,调用了Bean注册、初始化的方法。

        如果单纯的“加载”资源,使其成为可读的Document格式,不需要一个factory。【当然,实际上这个factory在reader眼里,是一个rigistry注册器】

        第一步,走进Abstract抽象类loadBeanDefinitions方法里【参数:String location】

                为了使代码结构更清晰,我把异常处理、日志记录都删除了,以下是我的分析。【忽略如何拿到加载器、以及如何加载资源】

                我再次强调一下,Resource类,是Spring对底层资源的统一访问接口,所以拿到Resource对象是第一步。

                首先,拿到资源加载器。

                然后,根据加载器的类型【一次读取n个资源,或者1个资源】,拿到资源。

                最后,把资源放入集合,等待处理。

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources){ResourceLoader resourceLoader = this.getResourceLoader();  int count;// 加载一个或多个资源if (resourceLoader instanceof ResourcePatternResolver) {// 多资源加载器,一次加载多个资源Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);count = this.loadBeanDefinitions(resources);if (actualResources != null) {// 将资源类Resource,放进集合Collections.addAll(actualResources, resources);}return count;    }} else {// 单资源加载器,一次加载一个资源Resource resource = resourceLoader.getResource(location);count = this.loadBeanDefinitions((Resource)resource);if (actualResources != null) {actualResources.add(resource);}return count;}}}

        第二步,走进Xml子类实现的loadBeanDefinitions方法中【参数:Resource resource】

                这一步骤里,我依旧删除了异常处理、日志和一些语句,保留了比较重要的方法。

                EncodedResource相当于Resource的编码结果,与Resource差别不大,在此忽略。

                主要做了两件事

                第一,将资源转化为InputSource。

                第二,调用doLoadBeanDefinitions方法。【真正的加载bean方法】

public int loadBeanDefinitions(EncodedResource encodedResource){Assert.notNull(encodedResource, "EncodedResource must not be null");Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();int var6;InputStream inputStream = encodedResource.getResource().getInputStream();Throwable var4 = null;// 将资源转化为InputSourceInputSource inputSource = new InputSource(inputStream);if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());}// 最重要的一步var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());return var6;
}

        第三步,走进doLoadBeanDefinitions方法

                这一步的主要逻辑,就是得到Document文件,然后调用registerBeanDefinitions方法。

Document doc = this.doLoadDocument(inputSource, resource);
int count = this.registerBeanDefinitions(doc, resource);return count;

        至此,Bean的加载已经完毕,Document类,已经可以支持Spring注册bean和实例化bean,从底层XML资源,转化为可读的资源,已经完成。

        至于Resource、Document是什么,ResourceLoader的原理又是什么,这部分我在下次再写。

五、结语

以上就是我分享的对Spring框架的理解。

         我是蚊子码农,如有补充或者疑问,欢迎在评论区留言。个人的知识体系可能没有那么完善,希望各位多多指正,谢谢大家。

这篇关于BeanDefinitionReader接口,Spring加载Bean的过程(非常流畅和容易理解)(Spring源码分析1)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1031834

相关文章

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

MyBatis延迟加载与多级缓存全解析

《MyBatis延迟加载与多级缓存全解析》文章介绍MyBatis的延迟加载与多级缓存机制,延迟加载按需加载关联数据提升性能,一级缓存会话级默认开启,二级缓存工厂级支持跨会话共享,增删改操作会清空对应缓... 目录MyBATis延迟加载策略一对多示例一对多示例MyBatis框架的缓存一级缓存二级缓存MyBat

Java中的.close()举例详解

《Java中的.close()举例详解》.close()方法只适用于通过window.open()打开的弹出窗口,对于浏览器的主窗口,如果没有得到用户允许是不能关闭的,:本文主要介绍Java中的.... 目录当你遇到以下三种情况时,一定要记得使用 .close():用法作用举例如何判断代码中的 input

深入理解Mysql OnlineDDL的算法

《深入理解MysqlOnlineDDL的算法》本文主要介绍了讲解MysqlOnlineDDL的算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小... 目录一、Online DDL 是什么?二、Online DDL 的三种主要算法2.1COPY(复制法)

Redis中Hash从使用过程到原理说明

《Redis中Hash从使用过程到原理说明》RedisHash结构用于存储字段-值对,适合对象数据,支持HSET、HGET等命令,采用ziplist或hashtable编码,通过渐进式rehash优化... 目录一、开篇:Hash就像超市的货架二、Hash的基本使用1. 常用命令示例2. Java操作示例三