Java面试题:Spring IOC容器启动流程附源码

1. IOC容器概述

IOC和AOP是Spring框架的核心功能,而IOC又是AOP实现的基础,因而可以说IOC是整个Spring框架的基石。那么什么是IOC?IOC即控制反转,通俗的说就是让Spring框架来帮助我们完成对象的依赖管理和生命周期控制等等工作。从面向对象的角度来说,具有这种行为,完成这种工作的主体就可以形象的称之为IOC容器。从代码角度来看,IOC容器不过是Spring中定义的具有IOC基本功能的一些类的统称,这些类都遵循一些共同的接口规范,所以我们可以说实现某些接口的具体的实现类就是IOC容器。而IOC容器的启动流程,就是创建并初始化一个该实现类的实例的过程,在这个过程中要进行诸如配置文件的加载解析,核心组件的注册,bean 实例的创建等一系列繁琐复杂的操作,因而整个过程显得相对漫长,逻辑也相对复杂。

2. BeanFactory和ApplicationContext的区别

前面说到Spring中为容器类定义了一些接口规范,如下图所示


11.png

具体而言,Spring中的容器类可以分为两大类。

  • 一类是由BeanFactory接口定义的核心容器。BeanFactory位于整个容器类体系结构的顶端,其基本实现类为DefaultListableBeanFactory。之所以称其为核心容器,是因为该类容器实现IOC的核心功能:比如配置文件的加载解析,Bean依赖的注入以及生命周期的管理等。BeanFactory作为Spring框架的基础设施,面向Spring框架本身,一般不会被用户直接使用。
  • 另一类则是由ApplicationContext接口定义的容器,通常译为应用上下文,不过称其为应用容器可能更形象些。它在BeanFactory提供的核心IOC功能之上作了扩展。通常ApplicationContext的实现类内部都持有一个BeanFactory的实例,IOC容器的核心功能会交由它去完成。而ApplicationContext本身,则专注于在应用层对BeanFactory作扩展,比如提供对国际化的支持,支持框架级的事件监听机制以及增加了很多对应用环境的适配等。ApplicationContext面向的是使用Spring框架的开发者。开发中经常使用的ClassPathXmlApplicationContext就是典型的Spring的应用容器,也是要进行解读的IOC容器。

3. 解读IOC容器启动流程的意义

  • IOC容器在启动时会注册并初始化Spring框架的所有基础组件,这些组件不仅在IOC模块中被用到,也会被AOP等模块使用。因而熟悉IOC容器的启动流程不仅是掌握IOC模块的关键,也是理解整个Spring框架的前提。
  • Spring是个很灵活的框架,允许用户在原有功能上进行扩展或者进行满足业务需求的个性化设置,比如对容器和Bean的生命周期过程进行增强,进行事件监听等等。要更好的使用Spring的这些特性,必须了解其工作原理,而答案就在IOC容器的启动过程中。
  • Spring框架在实现时使用了大量的设计模式,体现了很多优秀的设计思想。其IOC容器的启动源码就是供开发者学习这种设计经验的绝佳样板。

4. 初探IOC容器启动源码

启动Spring容器,本质上是创建并初始化一个具体的容器类的过程,以常见的容器类ClassPathXmlApplicationContext为例,启动一个Spring容器可以用以下代码表示

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

尽管只有短短的一行代码,但已经创建并启动了一个Spring的IOC容器。为了后面更好的理解,先来看下ClassPathXmlApplicationContext的类继承结构


12.jpg

关键的几个类已经用红色箭头标注了出来。

  • AbstractApplicationContext
    ApplicationContext接口的抽象实现类,能够自动检测并注册各种后置处理器(PostProcessor)和事件监听器(Listener),以模板方法模式定义了一些容器的通用方法,比如启动容器的真正方法refresh()就是在该类中定义的。
  • AbstractRefreshableApplicationContext
    继承AbstractApplicationContext的抽象类。内部持有一个DefaultListableBeanFactory 的实例,使得继承AbstractRefreshableApplicationContext的Spring的应用容器内部默认有一个Spring的核心容器,那么Spring容器的一些核心功能就可以委托给内部的核心容器去完成。AbstractRefreshableApplicationContext在内部定义了创建,销毁以及刷新核心容器BeanFactory的方法。
  • ClassPathXmlApplicationContext
    最常用的Spring的应用容器之一。在启动时会加载类路径下的xml文件作为容器的配置信息。

下面就正式开始容器启动流程的源码阅读
进入ClassPathXmlApplicationContext的构造方法,首先调用了重载构造函数

/**
 * Create a new ClassPathXmlApplicationContext, loading the definitions
 * from the given XML file and automatically refreshing the context.
 * @param configLocation resource location
 * @throws BeansException if context creation failed
 */
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
   this(new String[] {configLocation}, true, null);
}

这里有两点需要注意下:

  • 创建ClassPathXmlApplicationContext时需要指定xml文件的路径作为参数,尽管我们在创建时只指定了一个,但其实可以同时指定多个。
  • Spring容器有父子容器的概念,通过HierarchicalBeanFactory接口定义了具有层级关系的容器体系。而在抽象实现类AbstractApplicationContext类的内部,有一个表示父容器的成员变量。
/** Parent context */
private ApplicationContext parent;

重载函数的第三个参数即表示要创建的ClassPathXmlApplicationContext的父容器,不过这里只需要设置为null。关于Spring的父子容器,还有一些独特的访问规则,子容器可以访问父容器中的Bean,父容器不可以访问子容器中的Bean。不知道这个规则在使用Spring做web开发时可能会碰到一些匪夷所思的问题。

继续跟进源码

//设置父容器
super(parent);
//设置xml文件的路径参数
setConfigLocations(configLocations);
if (refresh) { //默认为true
    //启动Spring容器
    refresh();
}

设置完父容器和xml文件的路径信息后,终于看到了refresh()方法,正如前面提到的,这是真正启动Spring容器的方法,想要知道Spring IOC容器的启动流程,就要知道该方法内部都做了什么。

4.1 容器启动流程的不同阶段

为了更好的进行讲解,可以将容器启动的整个流程划分为以下五个阶段


33.png

4.2 前期准备

主程序

 public static void main(String[] args) {   
        System.out.println("现在开始初始化容器");
        ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
        System.out.println("容器初始化成功");
       Person person = (Person)ac.getBean("person");
        System.out.println(person);
 }

xml

    
        
        
        
    

结果运行

容器初始化成功
Person{name='尚硅谷', address='武汉', age=22, beanFactory=org.springframework.beans.factory.support.DefaultListableBeanFactory@694e1548: defining beans [person]; root of factory hierarchy, beanName='person'}

(1) 配置的是怎么转换成Person对象的呢?

(2) 配置的value值是怎样设置到person中的呢?

(3) spring容器怎样管理该对象的呢?

请带着这些问题继续往下看

4.3 基础组件

  • BeanFactory

spring底层容器,定义了最基本的容器功能,注意区分FactoryBean


191.jpg
  • ApplicationContext

扩展于BeanFactory,拥有更丰富的功能。例如:添加事件发布机制、父子级容器,一般都是直接使用ApplicationContext。


12.jpg
  • Resource

bean配置文件,一般为xml文件。可以理解为保存bean信息的文件。


13.jpg
  • BeanDefinition

beandifinition定义了bean的基本信息,根据它来创造bean


14.jpg

4.4 容器启动过程

(1)资源定位:找到配置文件Resource

(2)BeanDefinition载入和解析: 将配置文件解析成BeanDefinition

(3)BeanDefinition注册:将BeanDefinition向Map中注册 Map

(4)bean的实例化和依赖注入

 此过程由getBean()方法触发
15.jpg
16.jpg

创造 bean


17.jpg

实现依赖注入


18.jpg
19.jpg

此过程根据上述的BeanDefition,

(1)通过反射或者Cglib的方式创造bean

(2)根据配置的依赖将所需要的bean注入进来,此过程会递归调用getBean()方法。

(3)根据bean的scope决定是否缓存该Bean,一般情况为单例。容器会缓存该对象。

这个过程大概可以理解为

将原材料进行加工,创造可以直接利用的产品。

到此,spring容器就可以对外提供服务了。

5. 总结

容器启动的过程可以分为2大步:

(1)获取、解析、注册配置信息,将配置的文件信息转换Map

(2)根据上述的Map去实例化bean,并完成以来注入


44.jpg

以上是根据传统的xml形式配置Bean,现在很少用,现在用的比较多的是注解和javaConfig的形式配置,但换汤不换药,只是容器获取Map的过程变了而已。这也是容器容器初始化步骤细化的一个好处。易于扩展。

spring容器的启动过程由spring框架封装好了,并不需要我们手动编程,但理解其启动原理,更有利于我们对spring的使用和扩展。

你可能感兴趣的:(Java面试题:Spring IOC容器启动流程附源码)