解码Spring XML初始化流程:逐步分析容器启动的原理与实践

XmlBeanFactory

XmlBeanFactory 是 Spring Framework 中用于读取 XML 配置文件并初始化 Bean 的一种方式。在源码学习的时候可以跟踪 XmlBeanFactory 的运行流程,因为它继承了 DefaultListableBeanFactory 这个高级工厂,使用方法如下:

public class XmlBeanFactoryTest {
   
   

    @Test
    public void testXmlBeanFactory() {
   
   
        // 指定配置文件,创建 Spring 工厂
        BeanFactory factory = new XmlBeanFactory(new ClassPathResource("/applicationContext.xml"));
        System.out.println(factory.getBean("user"));
    }
}

它的初始化过程可以简单概括如下:

  1. 加载配置文件:获取配置文件的输入流,读取配置文件时使用 ClassPathResource 类,读取类路径下相关资源文件的内容(applicationContext.xml)。
  2. DOM 解析:读取 XML 的配置内容,使用 XmlBeanDefinitionReader 类来实现 XML 的 DOM 解析(底层基于 SAX 实现)。
  3. Bean的创建与注册:通过解析出来的 DOM 对象来进一步进行 Bean 的解析,将 XML 中的一个个的 <bean> 标签封装为 Java 的对象 BeanDefinition,之后以 bean 的 id 为键,BeanDefinition 为值,注册到工厂(DefaultListableBeanFactory)的 beanDefinitionMap 中。

通过查看源码可以看到,这个类其实非常的简单,只是在本类中维护了一个 XmlBeanDefinitionReader,这个类负责具体的 XML 解析,其他所有的核心工厂方法都是来自父类的 DefaultListableBeanFactory,而且这个红色框住的内容也显示,这个 XmlBeanFactory 类已经废弃了,可以直接搭配 DefaultListableBeanFactory 和 XmlBeanDefinitionReader 进行使用即可,使用方法如下:

public class XmlBeanFactoryTest {
   
   

    @Test
    public void testDefaultListableBeanFactory() {
   
   
        // 创建工厂
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        // 创建 XML 阅读器,并指定关联的工厂
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        // 开始解析加载 Bean
        reader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
        System.out.println(factory.getBean("user"));
    }
}

XmlBeanDefinitionReader 解析

在 Spring 框架中,XML 文件的解析由 org.springframework.beans.factory.xml.XmlBeanDefinitionReader 类负责。这个类是 Spring 中负责读取 XML 配置文件并将其转换为相应的 BeanDefinition 对象的关键组件之一。

通过观察上面的代码可以发现,XmlBeanDefinitionReader 构造的时候就需要传入一个工厂作为注册器,之后解析 XMl 创建的所有的 BeanDefinition 都要注册到里面。而开始解析的过程是从 reader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml")); 这句开始的。

接下来我们来大概看一下源码:

Spring 的源码非常的麻烦,很多方法的重载(do 开头的方法才是真正干活的),类也是一层套一层,看起来非常的乱…

包装资源 EncodedResource

XmlBeanDefinitionReader#loadBeanDefinitions

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   
   
    return loadBeanDefinitions(new EncodedResource(resource));
}
public class EncodedResource implements InputStreamSource {
   
   
    private final Resource resource;
    @Nullable
    private final String encoding;
    @Nullable
    private final Charset charset;

    public EncodedResource(Resource resource) {
   
   
        this(resource, (String)null, (Charset)null);
    }
    ...

这就是一个简单的重载,把配置文件的资源使用 EncodedResource 包装一下,但是可以发现,其实和没包装也差不多,包装后 encoding 和 charset 也都还是空的。

准备解析 loadBeanDefinitions

XmlBeanDefinitionReader#loadBeanDefinitions

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   
   
    // ↓ 直接省略一堆校验逻辑...
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
   
   
        currentResources = new HashSet<>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    if (!currentResources.add(encodedResource)) {
   
   
        throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    // ↑ 直接省略一堆校验逻辑...
    try {
   
   
        // 从资源对象获取输入流
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
   
   
            // 构建输入源,注意这个 InputSource 就是 SAX 提供的,也就印证了 Spring XML 解析是由 SAX 处理的!
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
   
   
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            // 这里又转了一层,开始加载 BeanDefinition
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        finally {
   
   
            inputStream.close();
        }
    }
    catch (IOException ex) {
   
   
        throw new BeanDefinitionStoreException(
            "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
   
   
        currentResources.remove(encodedResource);
        if (currentResources.isEmpty()) {
   
   
            this.resourcesCurrentlyBeingLoaded.remove();
        }
    }
}

这里先是做了一大堆的校验,然后从上一步包装的 EncodedResource 获取真正的输入流,之后构建 SAX 提供的 InputSource 对象,接下来开始真正的解析工作…

开始解析 doLoadBeanDefinitions

XmlBeanDefinitionReader#doLoadBeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
   
   
    try {
   
   
        // 使用 SAX 进行 DOM 解析
        Document doc = doLoadDocument(inputSource, resource);
        // 处理 DOM 解析的结果,创建并注册 BeanDefinition
        int count = registerBeanDefinitions(doc, resource);
        return count;
    } catch (BeanDefinitionStoreException ex) {
   
   
        throw ex;
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值