首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring IoC 容器初始化

Spring IoC 容器初始化

作者头像
WriteOnRead
发布2021-02-05 09:44:53
2400
发布2021-02-05 09:44:53
举报
文章被收录于专栏:WriteOnReadWriteOnRead

概述

上篇文章「Spring 中的 IoC 容器」从整体介绍了 Spring IoC 容器的相关概念和大致实现流程,本文要进入源码来一探究竟了。

这里仍以前文的代码为例进行分析,测试代码如下:

public class IocTests {
    @Test
    public void test01() {
        ApplicationContext context = new ClassPathXmlApplicationContext("application-ioc.xml");
        System.out.println(context.getBean("person"));
        System.out.println(context.getBean("dog"));
    }
}

/*
 * 输出结果:
 *  Person{id=12, name='Jack-12'}
 *  Dog{age=1}
 */

PS: 此处 Spring Framework 版本为 5.2.12.RELEASE,其他版本可能略有不同。

代码分析

从 ClassPathXmlApplicationContext 的构造器进入它的代码。

ClassPathXmlApplicationContext 有很多重载的构造器,不过多数都会调用到下面这个:

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

 // 调用父类构造器,保存传入的父容器
 super(parent);
    
 // 保存配置文件信息
 setConfigLocations(configLocations);
    
 // 刷新 IoC 容器
 if (refresh) {
  refresh();
 }
}

该构造器主要做了三件事:

  1. 调用父类的构造器,保存传入的父容器
  2. 保存配置信息,在本例中就是 application-ioc.xml
  3. 刷新 IoC 容器

其中最核心的就是第三步,也是最复杂的。

由于 ClassPathXmlApplicationContext 的整体继承结构比较复杂,为了便于分析其核心实现,这里先暂时忽略它实现的接口,只看它的类继承结构:

前面两个步骤的代码不再深入分析,这里直接进入第三步,也就是 refresh 方法(该方法是在 AbstractApplicationContext 类中实现的):

public abstract class AbstractApplicationContext extends DefaultResourceLoader
  implements ConfigurableApplicationContext {
 // ...

 @Override
 public void refresh() throws BeansException, IllegalStateException {
  synchronized (this.startupShutdownMonitor) {
   // Prepare this context for refreshing.
   prepareRefresh();

   // 获取 BeanFactory
   // Tell the subclass to refresh the internal bean factory.
   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 ...
   // finally ...
  }
 }

 // ...

 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
  refreshBeanFactory();
  return getBeanFactory();
 }    
}

refresh 方法里面封装了很多方法,每个方法里面又是一大堆代码……

刚开始看到这里可能会被吓到(我当初就是这样被劝退的)。

其实不必,代码虽然很多,但读起来还是有迹可循的:就是要先找到一条主线。就像一棵树,先找到树干,其它的都是细枝末节。先主后次,要不很容易陷进去、读起来一团乱麻。

PS: 之前写过一篇如何阅读 JDK 源码的文章「我是如何阅读JDK源码的?」,有兴趣可以参考一下。 但二者又有些不同:JDK 源码相对独立,一般关联性不大,而 Spring 的代码前后关联太多。

这里的主线是什么呢?

就是 obtainFreshBeanFactory 方法(它的实现在 AbstractRefreshableApplicationContext 类中),如下:

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
 // ...

 @Override
 protected final void refreshBeanFactory() throws BeansException {
  // 如果容器已经存在,则销毁容器中的对象、关闭容器
  if (hasBeanFactory()) {
   destroyBeans();
   closeBeanFactory();
  }
  try {
   // 创建 BeanFactory
   DefaultListableBeanFactory beanFactory = createBeanFactory();
   beanFactory.setSerializationId(getId());
   customizeBeanFactory(beanFactory);
   // 加载 BeanDefinition
   loadBeanDefinitions(beanFactory);
   this.beanFactory = beanFactory;
  }
  catch (IOException ex) {
   throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
  }
 }

 // ...
    
 protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
   throws BeansException, IOException;    
}

refreshBeanFactory 看名字可以推测是刷新 IoC 容器,它主要做了三件事:

  1. 如果 IoC 容器已经存在,则销毁容器中的对象、关闭容器(正如其名,refresh,是一个全新的,就要先把旧的干掉)。
  2. 创建 BeanFactory,即 DefaultListableBeanFactory,它就是 Spring IoC 容器的默认实现。
  3. 加载 BeanDefinition,也就是从配置文件(application-ioc.xml)中加载我们定义的 bean 信息,这一步也是最复杂的。

这里的 loadBeanDefinitions 是一个抽象方法?! 是的。这就是设计模式中的「模板模式(Template Pattern)」,这个模式不难理解,不了解也不影响,有空可以再看。 JDK 中的 AbstractQueuedSynchronizer(AQS) 也使用了模板模式,前文「JDK源码分析-AbstractQueuedSynchronizer(2)」可以参考。

loadBeanDefinitions 方法其实就是把实现代码放在子类中了。

What?它的子类有那么多,哪个才是真·儿子呢?

还记得上面那个类的继承结构图吗?

所以,这里它的子类实现指的就是 AbstractXmlApplicationContext#loadBeanDefinitions 方法:

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {
 // ...

 @Override
 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
  // 创建 BeanDefinitionReader,用于读取 BeanDefinition
  // Create a new XmlBeanDefinitionReader for the given BeanFactory.
  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.
  initBeanDefinitionReader(beanDefinitionReader);
  loadBeanDefinitions(beanDefinitionReader);
 }

 // ...
    
 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
  // 读取 Resource 类型的配置
  Resource[] configResources = getConfigResources();
  if (configResources != null) {
   reader.loadBeanDefinitions(configResources);
  }
  // 读取 String 类型的配置
  String[] configLocations = getConfigLocations();
  if (configLocations != null) {
   reader.loadBeanDefinitions(configLocations);
  }
 }    
}

它来了,XmlBeanDefinitionReader,是用来读取 BeanDefinition 的。

这里 BeanDefinition 的类型有两种:Resource 和 String。其实本质上还是一种,即 Resource,String 最终还是要转为 Resource。

这两个 loadBeanDefinitions 最终都会调用 XmlBeanDefinitionReader#loadBeanDefinitions 方法:

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
 // ...

 @Override
 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
  return loadBeanDefinitions(new EncodedResource(resource));
 }

 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
  Assert.notNull(encodedResource, "EncodedResource must not be null");
  if (logger.isTraceEnabled()) {
   logger.trace("Loading XML bean definitions from " + encodedResource);
  }

  Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

  if (!currentResources.add(encodedResource)) {
   throw new BeanDefinitionStoreException(
     "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
  }

  // 读取配置文件
  try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
   InputSource inputSource = new InputSource(inputStream);
   if (encodedResource.getEncoding() != null) {
    inputSource.setEncoding(encodedResource.getEncoding());
   }
   // 实际加载 BeanDefinition
   return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
  }
  // catch ...
 }

 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
   throws BeanDefinitionStoreException {

  try {
   // 将 Resource 解析为 Document 对象
   Document doc = doLoadDocument(inputSource, resource);
   // 从 Document 解析和注册 BeanDefinition
   int count = registerBeanDefinitions(doc, resource);
   if (logger.isDebugEnabled()) {
    logger.debug("Loaded " + count + " bean definitions from " + resource);
   }
   return count;
  }
  // catch ...
 }    

 // ...
}

loadBeanDefinitions 做了层封装,主要工作是在 doLoadBeanDefinitions 实现的。doLoadBeanDefinitions 方法主要做了两件事:

  1. 将 Resource 解析为 Document 对象。
  2. 从 Document 解析和注册 BeanDefinition。

第一步只是工具人,不必深究。

关键在于第二步,也比较复杂,后文再分析吧,毕竟源码读起来还是有点累……本文就先到这了。

Spring 源码中的关键部分通常是有点小规律的,比如:

  1. 以 try...catch 包围
  2. 以 do 开头的方法才是实际做事的,前面都是 caller,比如 doLoadBeanDefinitions、doGetBean 等

其他规律小伙伴们可以继续补充。

小结

本文开始进入 Spring IoC 容器的源码了,主要找到了一条主线,为便于对整体有个了解,这里简单总结了一个思维导图(还有后面的预告):

小伙伴们读的时候也可以用思维导图工具画一下主要流程,起到提纲挈领的作用,也便于以后回顾。

源码读起来还是需要一点耐心的,毕竟它就是这么朴实无华……且枯燥?

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WriteOnRead 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 代码分析
  • 小结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档