Spring源码分析 之 Spring IOC容器初始化过程(一)资源定位过程

Spring IOC容器初始化过程(一)资源定位过程

最近复习了一遍Spring IOC容器的初始化过程,结合书籍《Spring源码深度解析》总结了一下,IOC容器的初始化过程,大概分为以下三点:

  • 定位资源

    定位相关的配置文件,扫描相关注解

  • 加载资源

    将配置信息加载到内存中

  • 注册

    根据载入的配置信息,初始化对象,并将其装载至容器中

image-20210325225650908

整体加载的时序图

image-20210330004112183

工程目录

image-20210329003952934

POM文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>springTest</artifactId>
    <version>1.0-SNAPSHOT</version>

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>

</dependencies>
</project>

测试代码

public class ServiceB {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        Object serviceA = context.getBean("serviceA");
        System.out.println(serviceA);
    }
}

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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/cache
       http://www.springframework.org/schema/cache/spring-cache.xsd">

    <context:component-scan base-package="com.donkeys.spring"/>
    <bean id="serviceA" class="com.donkeys.spring.service.ServiceA"></bean>
</beans>

资源定位过程解析

代码中,使用的是ClassPathXmlApplicationContext类去加载Sping的配置文件,所以先给出该类类图

image-20210326015001046

ClassPathXmlApplicationContext构造方法

	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);
		//根据传入的配置文件名称,调用父类的setConfigLocations方法,解析配置文件路径,
		setConfigLocations(configLocations);
		//refresh = true
		//refresh() 方法会重启整个容器
		if (refresh) {
			refresh();
		}
	}

构造方法中一共做了2件事,首先是设置配置文件的路径,然后对整个容器进行刷新。

这里我们重点关注refresh()方法;进入refresh()方法

AbstractApplicationContext类的refresh()方法

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      //为刷新前做准备
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      //获取IOC容器,这里就是处理资源定位以配置文件加载/注册的方法
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         onRefresh();

         // Check for listener beans and register them.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

这里主要关注**obtainFreshBeanFactory()**方法

/**
 * Tell the subclass to refresh the internal bean factory.
 * @return the fresh BeanFactory instance
 * @see #refreshBeanFactory()
 * @see #getBeanFactory()
 */
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
   //刷新IOC容器
   //这里使用了委派设计模式,父类定义了抽象的refreshBeanFactory方法,具体调用实现调用子类的refreshBeanFactory方法
   refreshBeanFactory();
   //获取一个新的容器
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   if (logger.isDebugEnabled()) {
      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
   }
   return beanFactory;
}

**obtainFreshBeanFactory()**方法总共干了2件事,

  • 重置容器,refreshBeanFactory()方法中会设置相关标志,清除旧的容器,同时为Spring上下文生成一个新的容器
  • 获取一个新的容器

AbstractRefreshableApplicationContext的refreshBeanFactory()方法

下面我们进入**refreshBeanFactory()**方法

/**
 * This implementation performs an actual refresh of this context's underlying
 * bean factory, shutting down the previous bean factory (if any) and
 * initializing a fresh bean factory for the next phase of the context's lifecycle.
 * 该方法会将之前的bean工厂全部关闭,并初始化一个全新的bean 工厂类 用于Spring 上下文的生命周期
 * bean工厂就是IOC容器
 */
@Override
protected final void refreshBeanFactory() throws BeansException {
   //判断是否之前也有容器
   //如果有就销毁掉
   if (hasBeanFactory()) {
      destroyBeans();
      closeBeanFactory();
   }
   try {
      //创建一个新的工厂
      DefaultListableBeanFactory beanFactory = createBeanFactory();
      beanFactory.setSerializationId(getId());
      customizeBeanFactory(beanFactory);
      //读取Bean对象的定义
      //这里也是使用的委派设计模式
      loadBeanDefinitions(beanFactory);
      synchronized (this.beanFactoryMonitor) {
         this.beanFactory = beanFactory;
      }
   }
   catch (IOException ex) {
      throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
   }
}

这里创建了新的容器工厂,同时将新的工厂传入了loadBeanDefinitions()方法中,下面来看一下在loadBeanDefinitions方法中具体做了什么操作。

AbstractXmlApplicationContext的 loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法

AbstractRefreshableApplicationContext类的refreshBeanFactory方法中,调用了loadBeanDefinitions方法,但是这个方法它的一个抽象方法,具体实现应由子类去实现,我们在程序启动时,使用的ClassPathXmlApplicationContext类。根据文章开头的类图可以知道,这里会调用子类AbstractXmlApplicationContextloadBeanDefinitions方法去完成本次加载

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
   // Create a new XmlBeanDefinitionReader for the given BeanFactory.
   //使用默认的beanFactory去创建 XmlBeanDefinitionReader
   XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

   // Configure the bean definition reader with this context's
   // resource loading environment.
   //设置资源的加载环境
   beanDefinitionReader.setEnvironment(this.getEnvironment());
    //设置资源读取器
   beanDefinitionReader.setResourceLoader(this);
    //设置实体解析器
   beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

   // Allow a subclass to provide custom initialization of the reader,
   // then proceed with actually loading the bean definitions.
    //初始化 bean对象定义读取器
   initBeanDefinitionReader(beanDefinitionReader);
	//使用初始化完成的读取器,调用loadBeanDefinitions方法
   loadBeanDefinitions(beanDefinitionReader);
}

总的来说,这里只干了一件事,那就是初始化配置

//设置资源读取器
beanDefinitionReader.setResourceLoader(this);
//初始化 bean对象定义读取器
initBeanDefinitionReader(beanDefinitionReader);
  • 设置资源读取器,这里设置的资源读取器就是当前这个对象本身

    通过类图我们可以发现我们这个类的顶级父类ApplicationContext,继承自DefaultResourceLoader这个类,该类实现了ResourceLoader接口,说明这个类的实例化对象本身是具有资源读取器的功能的

  • 初始化bean对象定义读取器,这里设置xml文件的校验方式

下面我们继续看**loadBeanDefinitions(XmlBeanDefinitionReader reader)**方法

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
   //从子类对象中获取到资源定位
   Resource[] configResources = getConfigResources();
   if (configResources != null) {
      //XmlBeanDefinitionReader 读取器调用其父类的
      reader.loadBeanDefinitions(configResources);
   }
   String[] configLocations = getConfigLocations();
   if (configLocations != null) {
      reader.loadBeanDefinitions(configLocations);
   }
}

先看这一行

//这行代码的具体实现是由子类完成的,通过类图可以知道AbstractXmlApplicationContext的子类为ClassPathXmlApplicationContext
//这里主要将我们最开始在构造方法中设置好的配置文件进行返回
Resource[] configResources = getConfigResources();

再看这一行

//如果返回的配置文件不为空,就将返回的已经封装好的资源文件进行读取,   
if (configResources != null) {
      //XmlBeanDefinitionReader 读取器调用其父类的
      reader.loadBeanDefinitions(configResources);
   }

至此。资源文件定位过程已经加载完成。后续就是读取和注册。整个IOC容器加载过程中最重要的是读取过程,我们可以从刚刚的定位过程来看,虽然叫定位过程,但是其实就是一个配置文件读取器的初始化过程,这个过程会设置相关的解析策略以及校验策略。最终读取器生成后,就可以将我们早早设置好的配置文件载入然后进行读取。

  • 26
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值