随遇而安

关注TA

额,假装这里有签名...

  • 某地区
  • 工程师

可爱的留言者

Spring IOC 容器源码分析(转载)

2018年01月09 10:02 226 0

原文出处:JavaDoop


Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器。既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文并不能让你成为 Spring 专家,不过一定有助于大家理解 Spring 的很多概念,帮助大家排查应用中和 Spring 相关的一些问题。

阅读建议:读者至少需要知道怎么配置 Spring,了解 Spring 中的各种概念,少部分内容我还假设读者使用过 SpringMVC。本文要说的 IOC 总体来说有两处地方最重要,一个是创建 Bean 容器,一个是初始化 Bean,如果读者觉得一次性看完本文压力有点大,那么可以按这个思路分两次消化。读者不一定对 Spring 容器的源码感兴趣,也许附录部分介绍的知识对读者有些许作用。

我采用的源码版本是 4.3.11.RELEASE,算是 5.0.x 前比较新的版本了。为了降低难度,本文所说的所有的内容都是基于 xml 的配置的方式,实际使用已经很少人这么做了,至少不是纯 xml 配置,不过从理解源码的角度来看用这种方式来说无疑是最合适的。如果读者对注解方式的源码感兴趣,也许等我有时间的时候可以写篇文章介绍介绍。

我希望能将此文写成一篇 Spring IOC 源码分析的好文章,希望通过本文可以让读者不惧怕阅读 Spring 源码。

为了保持文章的严谨性,如果读者发现我哪里说错了请一定不吝指出,非常希望可以听到读者的声音。

引言

先看下最基本的启动 Spring 容器的例子:

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

以上代码就可以利用配置文件来启动一个 Spring 容器了,请使用 maven 的小伙伴直接在 dependencies 中加上以下依赖即可,我比较反对那些不知道要添加什么依赖,然后把 Spring 的所有相关的东西都加进来的方式。

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.11.RELEASE</version>
</dependency>

spring-context 会自动将 spring-core、spring-beans、spring-aop、spring-expression 这几个基础 jar 包带进来。 多说一句,很多开发者入门就直接接触的 SpringMVC,对 Spring 其实不是很了解,Spring 是渐进式的工具,并不具有很强的侵入性,它的模块也划分得很合理,即使你的应用不是 web 应用,或者之前完全没有使用到 Spring,而你就想用 Spring 的依赖注入这个功能,其实完全是可以的,它的引入不会对其他的组件产生冲突。

废话说完,我们继续。ApplicationContext context = new ClassPathXmlApplicationContext(…) 其实很好理解,从名字上就可以猜出一二,就是在 ClassPath 中寻找 xml 配置文件,根据 xml 文件内容来构建 ApplicationContext。当然,除了 ClassPathXmlApplicationContext 以外,我们也还有其他构建 ApplicationContext 的方案可供选择,我们先来看看大体的继承结构是怎么样的:

读者可以大致看一下类名,源码分析的时候不至于找不着看哪个类,因为 Spring 为了适应各种使用场景,提供的各个接口都可能有很多的实现类。对于我们来说,就是揪着一个完整的分支看完。当然,读本文的时候读者也不必太担心,每个代码块分析的时候,我都会告诉读者我们在说哪个类第几行。 我们可以看到,ClassPathXmlApplicationContext 兜兜转转了好久才到 ApplicationContext 接口,同样的,我们也可以使用绿颜色的 FileSystemXmlApplicationContext 和 AnnotationConfigApplicationContext 这两个类。

FileSystemXmlApplicationContext 的构造函数需要一个 xml 配置文件在系统中的路径,其他和 ClassPathXmlApplicationContext 基本上一样。

AnnotationConfigApplicationContext 是基于注解来使用的,它不需要配置文件,采用 java 配置类和各种注解来配置,是比较简单的方式,也是大势所趋吧。

不过本文旨在帮助大家理解整个构建流程,所以决定使用 ClassPathXmlApplicationContext 进行分析。

我们先来一个简单的例子来看看怎么实例化 ApplicationContext。

首先,定义一个接口:

public interface MessageService {
    String getMessage();
}

定义接口实现类:

public class MessageServiceImpl implements MessageService {
 
    public String getMessage() {
        return "hello world";
    }
}

接下来,我们在 resources 目录新建一个配置文件,文件名随意,通常叫 application.xml 或 application-xxx.xml就可以了:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName">
 
    <bean id="messageService" class="com.javadoop.example.MessageServiceImpl"/>
</beans>

这样,我们就可以跑起来了:

public class App {
    public static void main(String[] args) {
        // 用我们的配置文件来启动一个 ApplicationContext
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
 
        System.out.println("context 启动成功");
 
        // 从 context 中取出我们的 Bean,而不是用 new MessageServiceImpl() 这种方式
        MessageService messageService = context.getBean(MessageService.class);
        // 这句将输出: hello world
        System.out.println(messageService.getMessage());
    }
}

以上例子很简单,不过也够引出本文的主题了,就是怎么样通过配置文件来启动 Spring 的 ApplicationContext?也就是我们今天要分析的 IOC 的核心了。ApplicationContext 启动过程中,会负责创建实例 Bean,往各个 Bean 中注入依赖等。

BeanFactory 简介

初学者可别以为我之前说那么多和 BeanFactory 无关,前面说的 ApplicationContext 其实就是一个 BeanFactory。我们来看下和 BeanFactory 接口相关的主要的继承结构:

我想,大家看完这个图以后,可能就不是很开心了。ApplicationContext 往下的继承结构前面一张图说过了,这里就不重复了。这张图呢,背下来肯定是不需要的,有几个重点和大家说明下就好。

  1. ApplicationContext 继承了 ListableBeanFactory,这个 Listable 的意思就是,通过这个接口,我们可以获取多个 Bean,最顶层 BeanFactory 接口的方法都是获取单个 Bean 的。
  2. ApplicationContext 继承了 HierarchicalBeanFactory,Hierarchical 单词本身已经能说明问题了,也就是说我们可以在应用中起多个 BeanFactory,然后可以将各个 BeanFactory 设置为父子关系。
  3. AutowireCapableBeanFactory 这个名字中的 Autowire 大家都非常熟悉,它就是用来自动装配 Bean 用的,但是仔细看上图,ApplicationContext 并没有继承它,不过不用担心,不使用继承,不代表不可以使用组合,如果你看到 ApplicationContext 接口定义中的最后一个方法 getAutowireCapableBeanFactory() 就知道了。
  4. ConfigurableListableBeanFactory 也是一个特殊的接口,看图,特殊之处在于它继承了第二层所有的三个接口,而 ApplicationContext 没有。这点之后会用到。
  5. 请先不用花时间在其他的接口和类上,先理解我说的这几点就可以了。

然后,请读者打开编辑器,翻一下 BeanFactory、ListableBeanFactory、HierarchicalBeanFactory、AutowireCapableBeanFactory、ApplicationContext 这几个接口的代码,大概看一下各个接口中的方法,大家心里要有底,限于篇幅,我就不贴代码介绍了。

启动过程分析

下面将会是冗长的代码分析,请读者先喝个水。记住,一定要在电脑中打开源码,不然纯看是很累的。

第一步,我们肯定要从 ClassPathXmlApplicationContext 的构造方法说起。

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
  // 将配置文件作为资源都存放到这个数组中
  private Resource[] configResources;
 
    // 如果已经有 ApplicationContext 并需要配置成父子关系,那么调用这个构造方法
  public ClassPathXmlApplicationContext(ApplicationContext parent) {
    super(parent);
  }
  ...
  public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
      throws BeansException {
 
    super(parent);
        // 解析配置文件列表,放置到上面说的那个 configResources 数组中
    setConfigLocations(configLocations);
    if (refresh) {
      refresh(); // 核心方法
    }
  }
    ...
}

接下来,就是 refresh(),这里简单说下为什么是 refresh(),而不是 init() 这种名字的方法。因为 ApplicationContext 建立起来以后,其实我们是可以通过调用 refresh() 这个方法重建的,这样会将原来的 ApplicationContext 销毁,然后再重新执行一次初始化操作。

往下看,refresh() 方法里面调用了那么多方法,就知道肯定不简单了,请读者先看个大概,细节之后会详细说。

@Override
public void refresh() throws BeansException, IllegalStateException {
   // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛
   synchronized (this.startupShutdownMonitor) {
 
      // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
      prepareRefresh();
 
      // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
      // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
      // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 
      // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
      // 这块待会会展开说
      prepareBeanFactory(beanFactory);
 
      try {
         // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口,
         // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】
 
         // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
         // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
         postProcessBeanFactory(beanFactory);
         // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
         invokeBeanFactoryPostProcessors(beanFactory);
 
         // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别
         // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
         // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化
         registerBeanPostProcessors(beanFactory);
 
         // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了
         initMessageSource();
 
         // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了
         initApplicationEventMulticaster();
 
         // 从方法名就可以知道,典型的模板方法(钩子方法),
         // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
         onRefresh();
 
         // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过
         registerListeners();
 
         // 重点,重点,重点
         // 初始化所有的 singleton beans
         //(lazy-init 的除外)
         finishBeanFactoryInitialization(beanFactory);
 
         // 最后,广播事件,ApplicationContext 初始化完成
         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.
         // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
         destroyBeans();
 
         // Reset 'active' flag.
         cancelRefresh(ex);
 
         // 把异常往外抛
         throw ex;
      }
 
      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

下面,我们开始一步步来肢解这个 refresh() 方法。

创建 Bean 容器前的准备工作

这个比较简单,直接看代码中的几个注释即可。

protected void prepareRefresh() {
   // 记录启动时间,
   // 将 active 属性设置为 true,closed 属性设置为 false,它们都是 AtomicBoolean 类型
   this.startupDate = System.currentTimeMillis();
   this.closed.set(false);
   this.active.set(true);
 
   if (logger.isInfoEnabled()) {
      logger.info("Refreshing " + this);
   }
 
   // Initialize any placeholder property sources in the context environment
   initPropertySources();
 
   // 校验 xml 配置文件
   getEnvironment().validateRequiredProperties();
 
   this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}

创建 Bean 容器,加载并注册 Bean

注意,这个方法是全文最重要的部分之一,这里将会初始化 BeanFactory、加载 Bean、注册 Bean 等等。

当然,这步结束后,Bean 并没有完成初始化。

// AbstractApplicationContext.java

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
   // 关闭旧的 BeanFactory (如果有),创建新的 BeanFactory,加载 Bean 定义、注册 Bean 等等
   refreshBeanFactory();
 
   // 返回刚刚创建的 BeanFactory
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   if (logger.isDebugEnabled()) {
      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
   }
   return beanFactory;
}

// AbstractRefreshableApplicationContext.java 120


看到这里的时候,我觉得读者就应该站在高处看 ApplicationContext 了,ApplicationContext 继承自 BeanFactory,但是它不应该被理解为 BeanFactory 的实现类,而是说其内部持有一个实例化的 BeanFactory(DefaultListableBeanFactory)。以后所有的 BeanFactory 相关的操作其实是给这个实例来处理的。 我们说说为什么选择实例化 DefaultListableBeanFactory ?前面我们说了有个很重要的接口 ConfigurableListableBeanFactory,它实现了 BeanFactory 下面一层的所有三个接口,我把之前的继承图再拿过来大家再仔细看一下:

我们可以看到 ConfigurableListableBeanFactory 只有一个实现类 DefaultListableBeanFactory,而且实现类 DefaultListableBeanFactory 还通过实现右边的 AbstractAutowireCapableBeanFactory 通吃了右路。所以结论就是,最底下这个家伙 DefaultListableBeanFactory 基本上是最牛的 BeanFactory 了,这也是为什么这边会使用这个类来实例化的原因。

在继续往下之前,我们需要先了解 BeanDefinition。我们说 BeanFactory 是 Bean 容器,那么 Bean 又是什么呢?

这里的 BeanDefinition 就是我们所说的 Spring 的 Bean,我们自己定义的各个 Bean 其实会转换成一个个 BeanDefinition 存在于 Spring 的 BeanFactory 中。

所以,如果有人问你 Bean 是什么的时候,你要知道 Bean 在代码层面上是 BeanDefinition 的实例。

BeanDefinition 中保存了我们的 Bean 信息,比如这个 Bean 指向的是哪个类、是否是单例的、是否懒加载、这个 Bean 依赖了哪些 Bean 等等。

BeanDefinition 接口定义

我们来看下 BeanDefinition 的接口定义:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
 
   // 我们可以看到,默认只提供 sington 和 prototype 两种,
   // 很多读者都知道还有 request, session, globalSession, application, websocket 这几种,
   // 不过,它们属于基于 web 的扩展。
   String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
   String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
 
   // 比较不重要,直接跳过吧
   int ROLE_APPLICATION = 0;
   int ROLE_SUPPORT = 1;
   int ROLE_INFRASTRUCTURE = 2;
 
   // 设置父 Bean,这里涉及到 bean 继承,不是 java 继承。请参见附录介绍
   void setParentName(String parentName);
 
   // 获取父 Bean
   String getParentName();
 
   // 设置 Bean 的类名称
   void setBeanClassName(String beanClassName);
 
   // 获取 Bean 的类名称
   String getBeanClassName();
 
   // 设置 bean 的 scope
   void setScope(String scope);
 
   String getScope();
 
   // 设置是否懒加载
   void setLazyInit(boolean lazyInit);
 
   boolean isLazyInit();
 
   // 设置该 Bean 依赖的所有的 Bean,注意,这里的依赖不是指属性依赖(如 @Autowire 标记的),
   // 是 depends-on="" 属性设置的值。
   void setDependsOn(String... dependsOn);
 
   // 返回该 Bean 的所有依赖
   String[] getDependsOn();
 
   // 设置该 Bean 是否可以注入到其他 Bean 中,只对根据类型注入有效,
   // 如果根据名称注入,即使这边设置了 false,也是可以的
   void setAutowireCandidate(boolean autowireCandidate);
 
   // 该 Bean 是否可以注入到其他 Bean 中
   boolean isAutowireCandidate();
 
   // 主要的。同一接口的多个实现,如果不指定名字的话,Spring 会优先选择设置 primary 为 true 的 bean
   void setPrimary(boolean primary);
 
   // 是否是 primary 的
   boolean isPrimary();
 
   // 如果该 Bean 采用工厂方法生成,指定工厂名称。对工厂不熟悉的读者,请参加附录
   void setFactoryBeanName(String factoryBeanName);
   // 获取工厂名称
   String getFactoryBeanName();
   // 指定工厂类中的 工厂方法名称
   void setFactoryMethodName(String factoryMethodName);
   // 获取工厂类中的 工厂方法名称
   String getFactoryMethodName();
 
   // 构造器参数
   ConstructorArgumentValues getConstructorArgumentValues();
 
   // Bean 中的属性值,后面给 bean 注入属性值的时候会说到
   MutablePropertyValues getPropertyValues();
 
   // 是否 singleton
   boolean isSingleton();
 
   // 是否 prototype
   boolean isPrototype();
 
   // 如果这个 Bean 原生是抽象类,那么不能实例化
   boolean isAbstract();
 
   int getRole();
   String getDescription();
   String getResourceDescription();
   BeanDefinition getOriginatingBeanDefinition();
}

这个 BeanDefinition 其实已经包含很多的信息了,暂时不清楚所有的方法对应什么东西没关系,希望看完本文后读者可以彻底搞清楚里面的所有东西。

这里接口虽然那么多,但是没有类似 getInstance() 这种方法来获取我们定义的类的实例,真正的我们定义的类生成的实例到哪里去了呢?别着急,这个要很后面才能讲到。 有了 BeanDefinition 的概念以后,我们再往下看 refreshBeanFactory() 方法中的剩余部分:

customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);

虽然只有两个方法,但路还很长啊。。。

customizeBeanFactory(beanFactory)

customizeBeanFactory(beanFactory) 比较简单,就是配置是否允许 BeanDefinition 覆盖、是否允许循环引用。

protected void customizeBeanFactory(DefaultListableBeanFanullnullnullnullnullnull
                    
点赞(0)
本文标签java spring ioc 容器
点了个评