Spring源码分析一:Spring初级容器初始化

Spring初级容器初始化

简单的demo

创建一个bean对象Student:

public class Student {
    private String name = "xiaofei";
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

在resources下创建applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.xiaofei.bean.Student"/>

</beans>

创建一个BeanFactoryDemo:

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class BeanFactoryDemo {
    public static void main(String[] args) {
        XmlBeanFactory beanFactory =
                new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        Student student = (Student) beanFactory.getBean("student");
        System.out.println(student.getName());

    }
}

XmlBeanFactory原理分析

上面的demo简单流程如下:

  • 首先,通过ClassPathResource将applicationContext.xml配置文件封装起来,我们可以知道的是,
    ClassPathResource肯定会从resources目录下解析配置文件,从配置文件中解析bean标签,并获取bean标签上的id属性和class属性的值。
  • 通过class属性的值即类全限定名称,就可以通过反射创建bean,也就是创建了一个Student对象出来,然后再将Student对象放到Spring容器当中,Student对象在容器中的名称为属性id的值,Spring容器的初始化简单来说也就是干这些事。
  • 然后,当我们调用getBean方法时就会从Spring容器中加载bean了,Spring会根据给定bean的名称到Spring容器中获取bean,比如,demo中就是通过student这个名称,从Spring容器中获取Student对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pgEujhZi-1690957915685)(Spring_sound_code.assets/image-20230801160419889.png)]

Resource接口

Resource接口

什么是Resource接口?

  • Spring统一把所有使用到的资源抽象成了Resource不同来源的资源对应着不同的Resource实现类

Resource接口的方法:
Resource接口的方法

  • 定义了对资源状态判断的方法:
    • exists方法判断资源是否存在
    • isFile方法判断资源是否是文件类型的
    • isOpen方法判断资源是否处于打开的状态
    • isReadable方法判断资源是否是可读状态的
  • 提供了资源到File、URI以及URL的转换方法:
    • getFile方法可以将资源转换为File
    • getURI方法可以将资源转换为URI
    • getURL方法可以将资源转换为URL
  • 提供了获取文件名称的getFilename方法
  • 提供了获取资源的描述信息的getDescription方法
    • getDescription方法一般它可以用于日志信息的打印。

InputStreamSource

package org.springframework.core.io;

import java.io.IOException;
import java.io.InputStream;

public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}

​ 可以看到,InputStreamSource接口中只有一个方法getInputStream,而且方法返回的就是一个输入流lnputStream。

​ 从前面的类继承图中我们也看到了,Resource是继承了InputStreamSource接口的,所以,这也就意味着所有资源只要封装成了Resource,就可以通过调用InputStreamSource中的getinpustream方法获取资源对应的输入流InputStream了。

而资源的来源又是多种多样的:

  • ClassPathResource就是用来加载classpath路径下的资源文件的。
  • UrlResource加载URL
  • FileSystemResource加载文件
  • ByteArrayResource加载Byte数组
  • InputStreamResource加载InputStream

XmlBeanFactory构造方法分析

在上面通过Resource接口的实现类获取到资源后,接下来就是对XmlBeanFactory进行初始化操作:

可以看到先执行了super父类构造器,其实执行到了AbstractAutowireCapableBeanFactory:

ignoreDependencyInterface方法

ignoreDependencyInterface方法很简单,就是将这三个类(BeanNameAware、BeanFactoryAware、BeanClassLoaderAware)都放到了ignoredDependencyInterfaces中,而ignoredDependencyInterfaces其实就是一个Set集合:

BeanNameAware、BeanFactoryAware、BeanClassLoaderAware:

  • 这三个接口中的方法名称,和相应类的名称极度的相似,其实,Aware接口也称为感知接口,当bean实现了这些感知接口时,Spring在实例化这些bean的时候,就会调用感知接口中的方法注入相应的数据。
ignoreDependencyInterface方法作用:

ignoreDependencyInterface方法的作用,是为了让那些实现了感知接口的bean属性只能由Spring容器赋值,而不是可以人为的从外部随意的注入进来。

​ 如果一个bean实现了BeanName、BeanFactoryAware或BeanClassLoaderAware接口的话,那这个bean中的属性如果想要通过Spring进行自动装配赋值的话,这个属性对应的setter方法,就不能和感知接口中的方法相同

​ 如果相同的话,Spring就不会为该属性自动装配赋值,而是让Spring内部调用这些感知接口的方法,来为这些属性设置值。

​ 这也是这些感知接口存在的意义,毕竟, bean都实现了这些感知接口了,而感知接口恰好已经通过方法
ignoreDependencyInterface添加到忽略感知接口的集合中了,这就相当于这些属性的赋值权利交给Spring内部来决定了。

​ Spring这样的设计其实也有一定的合理性,比如你实现了BeanNameAware接口,对应的beanName属性的值,当然就是当前这个bean在Spring容器中的名称

​ 此时,如果你从外部的xml或者注解中注入了一个其它的名称,Spring理所应当就会忽略掉这个外来值的自动装配了,确保bean名称的唯—性

比如:

  • 执行结果:

可以看到输出的值并不是我们所设置的xiaofeiBeanName,而是beanNameAwareImpl

XmlBeanDefinitionReader

执行完了super父类构造器后,下面执行reader.loadBeanDefinitions(resource)

继续看loadBeanDefinitions方法

XmlBeanDefinitionReader首先会将Resource封装为EncodedResource,EncodedResource相比于我们传进来的Resource只不过多了一些字符集和编码相关的设置,然后通过Resource中的输入流创建了InputSource,接下来进入到真正加载资源的方法doLoadBeanDefinitions中。

可以看到,doLoadBeanDefinitions方法中大体来说就干了两件事,一是通过我们传进来的参数inputSource和resource创建xml文件的Document对象(jdk里的),另外就是解析Document对象将解析到的bean注入到Spring的容器中

其实Spring就是通过DOM来解析xml文件的,但是解析xml也是先需要对xml进行校验

XML的校验方式:DTD和XSD
  • XSD:XML Schemas Definition,也就是XML模式的定义,通过XSD的声明文件可以约束你在xml文件中不能随便乱写,以保证xml文件格式的正确性。

    • 比如spring-beans.xsd,而xml文件的组成不光是xml文件本身,而且还包括它的约束语言,比如spring-beans.xsd就是xml约束语言XSD所规定的声明文件。

  • DTD:Document Type Definition,也就是文档类型的定义

  • 可以看到,DTD在xml文件中是通过额外的一段配置来体现,当然,我们也可以注意到在DTD模式下也会配置相应的网址,通过这样网址,Spring会去下载对应的DTD的声明文件spring-beans.dtd,以便来约束我们按照一定的格式来配置xml文件。

  • 如果我们要运行一个基于Spring开发的应用程序,但是,程序在运行的时候一旦断网了,就会导致Spring没法从网上下载相应的DTD或XSD声明文件,那xml文件是否符合我们预期的格式也就无从得知了,另外,如果网速不好的话也会影响我们程序的稳定性,这是无法接受的。

  • 所以,Spring的研发团队也考虑到这个问题了,索性就把相应的DTD和XSD文件都放到Spring对应的jar包中,这样的话当Spring程序运行的时候,只需要通过相应的代码程序,从jar包中获取相应的DTD或XSD声明文件就可以了,就不需要那么费力的从网上下载了。

首先,我们将applicationContext.xml封装为资源ClassPathResource,然后再通过EncodedResource和InputSource的进一步封装之后,交给DOM的APl进行解析并获取xml对应的Document。并且,Spring在解析xml文件前还需要对xml文件进行校验

标签的解析

  • 默认标签解析的入口在DefaultBeanDefinitionDocumentReader#parseBeanDefinitions方法;

    • 调用链路是

      processBeanDefinition:306, DefaultBeanDefinitionDocumentReader 
      parseDefaultElement:197, DefaultBeanDefinitionDocumentReader 
      parseBeanDefinitions:176, DefaultBeanDefinitionDocumentReader 
      doRegisterBeanDefinitions:149, DefaultBeanDefinitionDocumentReader 
      registerBeanDefinitions:96, DefaultBeanDefinitionDocumentReader 
      registerBeanDefinitions:511, XmlBeanDefinitionReader 
      doLoadBeanDefinitions:391, XmlBeanDefinitionReader 
      loadBeanDefinitions:338, XmlBeanDefinitionReader 
      loadBeanDefinitions:310, XmlBeanDefinitionReader 
      <init>:79, XmlBeanFactory 
      <init>:67, XmlBeanFactory 
      main:10, BeanFactoryDemo 
      
  • 到方法parseDefaultElement中,看下Spring是如何解析默认标签的:

  • 重点还是最核心的bean标签的解析,直接到processBeanDefinition方法中看下 :

    • 步骤一:解析bean标签元素,在parseBeanDefinitionElement里:
      • 最后返回BeanDefinitionHolder对象, parseBeanDefinitionElement方法其实就是将beanDefinition,连带着解析得到的别名aliasesArray一起封装到了BeanDefinitionHolder中,BeanDefinitionHolder我们可以理解为是持有BeanDefinition的一个对象而已

    • 步骤二:将解析到的bean注册到Spring容器中
      • 进入到 registerBeanDefinition:

      • beanDefinitionMap就是一个ConcurrentHashMap类型的Map,beanDefinitionMap就是Spring容器,Spring容器从本质上来说就是一个Map

Spring初级容器初始化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值