基本概念

BeanDefinitionReader ,该接口的作用就是加载 Bean。

在 Spring 中,Bean 一般来说都在配置文件中定义。而在配置的路径由在 web.xml 中定义。所以加载 Bean 的步骤大致就是:

  • 加载资源,通过配置文件的路径(Location)加载配置文件(Resource)

  • 解析资源,通过解析配置文件的内容得到 Bean。

下面来看它的接口定义:

public interface BeanDefinitionReader {

    BeanDefinitionRegistry getRegistry();

    ResourceLoader getResourceLoader();

    ClassLoader getBeanClassLoader();

    BeanNameGenerator getBeanNameGenerator();

    // 通过 Resource 加载 Bean 

    int loadBeanDefinitions(Resource resource)
throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource... resources)
throws BeanDefinitionStoreException; // 通过 location 加载资源 int loadBeanDefinitions(String location)
throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations)
throws BeanDefinitionStoreException;
}

具体的继承关系如下:


流程分析

首先来看 Spring Ioc 容器从启动开始到调用 BeanDefinitionReader 加载 Bean 的过程如下:

注意:由于这里采用 XML 文件作为 Spring 的配置文件,所以默认调用 XmlBeanDefinitionReader 来处理。

1.通过 BeanFactory 加载 Bean

在 Spring 容器(ApplicationContext)内部存在一个内部容器(BeanFactory)负责 Bean 的创建与管理。

在创建完 BeanFactory ,下一步就是要去加载 Bean。它由 loadBeanDefinitions 方法负责。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException { // 1.创建 BeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader =
new XmlBeanDefinitionReader(beanFactory); // 2.设置 BeanDefinitionReader 的相关属性 // 2.1.设置 Environment,即环境,与容器的环境一致
beanDefinitionReader.setEnvironment(getEnvironment()); // 2.2.设置 ResourceLoader,即资源加载器,因为容器实现了该接口,具体加载资源的功能
beanDefinitionReader.setResourceLoader(this); // 2.3.设置 EntityResolver,即实体解析器,这里用于解析资源加载器加载的资源内容
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 3.初始化 BeanDefinitionReader ,空方法
initBeanDefinitionReader(beanDefinitionReader); // 4.通过 BeanDefinitionReader 加载 Bean
loadBeanDefinitions(beanDefinitionReader);
} // 构造函数
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
// 内部 BeanFactory 被当作 Bean 注册器
super(registry);
}

观察代码,该方法的主要目的是创建了 BeanDefinitionReader ,并由它去加载 Bean。具体过程如下:

  • 创建 BeanDefinitionReader
  • 设置 BeanDefinitionReader 的相关属性
  • 初始化 BeanDefinitionReader
  • 通过 BeanDefinitionReader 加载 Bean

2.通过 BeanDefinitionReader 加载 Bean

在创建完 BeanDefinitionReader 后,则就需要通过它来 加载 Bean,过程如下:

// 通过 BeanDefinitionReader 加载 Bean
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader)
throws IOException {
// 取得 Spring 容器的所有配置文件
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
// 调用 BeanDefinitionReader 加载 Bean
reader.loadBeanDefinitions(configLocation);
}
}
}

3.通过 Location 加载 Bean

加载的 Bean 的责任被交给了 BeanDefinitionReader ,下面来看看该类的 loadBeanDefinitions 方法。

public int loadBeanDefinitions(String location)
throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
} public int loadBeanDefinitions(String location, Set<Resource> actualResources)
throws BeanDefinitionStoreException { // 1.取得资源加载器,即容器本身
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
// 抛出异常...
} // 判断资源加载器类型
if (resourceLoader instanceof ResourcePatternResolver) {
// 说明该 ResourceLoader 可以基于路径加载多个资源 try {
// 2.加载资源
Resource[] resources =
((ResourcePatternResolver) resourceLoader).getResources(location); // 3.通过 Resource 加载 Bean
int loadCount = loadBeanDefinitions(resources); if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
} // 省略代码... return loadCount; }catch (IOException ex) {
// 抛出异常...
}
}else {
// 表示 ResourceLoader 只能加载一个资源
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
} // 省略代码... return loadCount;
}
}

观察代码,该方法的工作流程可分为四个步骤:

  • 取得资源加载器

  • 加载资源,通过 location 利用 ResourceLoader 加载

  • 通过 Resource 加载 Bean


4.通过 Resource 加载 Bean

在得到资源(Resource)后,该方法对它进行了封装,使其变成一个 EncodedResource 对象。

public int loadBeanDefinitions(Resource resource)
throws BeanDefinitionStoreException { // EncodedResource 表示编码类型的资源
return loadBeanDefinitions(new EncodedResource(resource));
} // 构造函数
public EncodedResource(Resource resource, String encoding) {
// 封装资源,并带上编码类型
this(resource, encoding, null);
}

5.通过 EncodedResource 加载 Bean

public int loadBeanDefinitions(EncodedResource encodedResource)
throws BeanDefinitionStoreException { // 省略代码... // 1.取得[已加载的资源]的集合,用于存在已加载的资源。空,则创建。
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
} // 2.将当前资源加入集合
if (!currentResources.add(encodedResource)) {
// 抛出异常...
} try {
// 3.将资源转换成流
InputStream inputStream = encodedResource.getResource().getInputStream(); try {
//5.通过流创建 InputSource ,并设置编码
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
} //6.通过 InputSource 加载 Bean
return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); }finally {
inputStream.close();
}
}catch (IOException ex) {
// 抛出异常...
}finally { // 移除已完成解析的资源
currentResources.remove(encodedResource); // 集合为空,则一并删除
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
  • 取得已加载的资源集合
  • 将当前资源添加到集合
  • 将资源转换成流
  • 通过流创建 InputSource ,并设置编码
  • 通过 InputSource 加载 Bean

6.通过 InputSource 加载 Bean

之所以把 Resource 转换成 InputSource ,就是为了 SAX 解析作准备。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try { // 1.解析 XML 文件
Document doc = doLoadDocument(inputSource, resource); // 2.注册 Bean
return registerBeanDefinitions(doc, resource); }catch (BeanDefinitionStoreException ex) {
// 抛出异常...
}catch (SAXParseException ex) {
// 抛出异常...
}catch (SAXException ex) {
// 抛出异常...
}catch (ParserConfigurationException ex) {
// 抛出异常...
}catch (IOException ex) {
// 抛出异常...
}catch (Throwable ex) {
// 抛出异常...
}
} // 解析 XML 文件,并返回 Document 对象。
protected Document doLoadDocument(InputSource inputSource, Resource resource)
throws Exception {
return this.documentLoader.loadDocument(
inputSource,
getEntityResolver(),
this.errorHandler,
getValidationModeForResource(resource),
isNamespaceAware());
}

3.Bean 注册

上一步中 XmlBeanDefinitionReader 已经取得了 XML 的 Document 对象,完成了资源的解析过程。

下一步就是 Bean 的注册过程。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {

    // 利用 documentReader 对配置文件的内容进行解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 取得已经注册 BeanDefinition
int countBefore = getRegistry().getBeanDefinitionCount(); // 关键 -> 注册 BeanDefinition (包含解析过程)
documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore;
}

总结

分析完 BeanDefinitionReader 具体工作流程,最后通过一个图简单阐述:

07.Spring Bean 加载 - BeanDefinitionReader的更多相关文章

  1. Spring bean加载2--FactoryBean情况处理

    Spring bean加载2--FactoryBean情况处理 在Spring bean加载过程中,每次bean实例在返回前都会调用getObjectForBeanInstance来处理Factory ...

  2. spring bean加载顺序指定方式之一

    在某些情况下,我们在容器启动的时候做一些事情,举个例子,加载缓存等.. 此时我们会希望某个bean先被加载并执行其中的afterpropertiesset方法. 因为spring默认是通过contex ...

  3. Spring bean加载多个配置文件

    除了写很简单的加载一个xml,加载多个的情况一直没用到,在公司里也不会由自己处理这个问题,现在需要用到了,就研究验证一下. 使用的案例还是上面的例子. 只有,将原来的beans.xml分成两个部分. ...

  4. spring bean 加载过程(spring)

    以classpathXmlApplication为例 入口方法包含3个部分, public ClassPathXmlApplicationContext(String[] configLocation ...

  5. Spring bean加载之1:BeanFactory和FactoryBean

    BeanFactory BeanFactory:以Factory结尾,表示它是一个工厂类(接口),用于管理Bean的一个工厂.在Spring中,BeanFactory是IOC容器的核心接口,它的职责包 ...

  6. Spring源码分析:Bean加载流程概览及配置文件读取

    很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ...

  7. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  8. 【Spring源码分析】Bean加载流程概览(转)

    转载自:https://www.cnblogs.com/xrq730/p/6285358.html 代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. ...

  9. 工厂模式模拟Spring的bean加载过程

    一.前言    在日常的开发过程,经常使用或碰到的设计模式有代理.工厂.单例.反射模式等等.下面就对工厂模式模拟spring的bean加载过程进行解析,如果对工厂模式不熟悉的,具体可以先去学习一下工厂 ...

随机推荐

  1. [转]Google开源Leak Finder—用于检测内存泄漏的JavaScript工具-----可惜,暂时打不开google的网站,下载不了

    近日,Google开源了Leak Finder,这款工具可以查看JavaScript应用的堆,进而发现内存泄漏. 作为一门垃圾收集语言,JavaScript并不会出现常见的内存泄露情况,特别是像C++ ...

  2. jquery--find与children方法的区别

      children方法: find方法: 通过以上的解释,可以总结如下: 1:children及find方法都用是用来获得element的子elements的,两者都不会返回 text node,就 ...

  3. 【转】 Pro Android学习笔记(三九):Fragment(4):基础小例子-续

    目录(?)[-] Step 3实现简介显示类DetailFragment 创建实例 编写所需的生命周期代码 Step 4实现showDetailint index如何管理fragment fragme ...

  4. 多对多 hibernate映射

    数据库: create table EMPLOYEE ( EMPID NUMBER(6) not null, EMPNAME VARCHAR2(32) ) alter table EMPLOYEE a ...

  5. C++之queue模板类

    1.queue常用成员函数 ①size() 返回队列中元素的个数 ②empty() 如果队列空则返回真  ③back() 返回最后一个元素引用即队尾. ④front() 返回第一个元素引用即队首. ⑤ ...

  6. OpenGL 使用GLFW创建全屏窗口

    OpenGL 使用GLFW创建全屏窗口 GLFW库里面的glfwCreateWindow()函数是用来创建窗口的函数. 这样函数的原型是: GLFWwindow* glfwCreateWindow(i ...

  7. Pig Flatten 解包操作,解元组

    Flatten Operator The FLATTEN operator looks like a UDF syntactically, but it is actually an operator ...

  8. rlwrap:让控制台程序使用input.rc里的设置

    习惯了vi模式,在bash里配置input.rc可以使平时命令行操作都使用vi习惯,翻页和跳动光标都很方便 但是当运行一些交互式程序时,比如redis-cli,lua等,它们却不按input.rc的配 ...

  9. 判断Java对象死亡的两种常用算法

    当对象不馁引用的时候,这个对象就是死亡的,等待GC进行回收. 1.引用计数法 概念: 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就增加1:当应用失效时,计数器值就减1:任何时刻计数器 ...

  10. html css将图片或div置于顶层

    在做这个功能时,图片被挡住了.. 解决办法 在这个图片的css里加上z-index:数字:(数字可以为正也可以为负数) z-index:1肯定在z-index:-1的上面 用这个属性来给div分层 是 ...