Spring容器ClassPathXmlApplicationContext启动过程解析
Spring是企业级Java开发中应用最广泛的编程框架。在EJB日渐式微的情况下,Spring已成为企业级Java开发的事实标准。经过十多年的发展,不仅传统的配置方式仍然在广泛使用,同时也诞生了Spring Boot,Spring Cloud等基于Spring的新的框架,在方兴未艾的微服务领域继续引领着潮流。
在使用Spring的过程中,相信我们都很容易感受到Spring给我们带来的便利,这不禁就会引发我们对Spring本身的设计和实现的兴趣。通过阅读文档和代码,我们会发现Spring堪称Java项目设计与实现的典范,它的设计非常地优雅,是我们学习Java开发,乃至面向对象设计与开放的很好的教材。
在Spring的体系中,依赖注入(Dependency Injection,简称DI)是一个基础设施级的功能。一般说使用Spring,默认都会认为必然会使用DI的功能。所以学习Spring的源代码,一般也会从DI入手。
Spring的依赖注入主要是靠应用上下文(ApplicationContext)来实现的。顾名思义,应用上下文就是持有了应用启动必须的各种信息的对象。在多种ApplicationContext中,ClassPathXmlApplicationContext是比较简单的一个,从它的名字可以看出,它是基于Java的Classpath的,同时是基于Xml配置的。
下面先分析一下ClassPathXmlApplicationContext的类关系
我们首先关注一下ClassPathXmlApplicationContext到ApplicationContext的继承和实现关系。从顶向下各个接口和实现类的功能如下:
ApplicationContext:这个接口是提供了应用程序配置的核心接口,当程序运行时它是只读的,不能修改状态,但是可以重新加载(reload),只要具体的实现类支持。
ConfigurableApplicationContext:这是一个支持SPI加载的接口,绝大多数应用上下文都实现了它。它内部定义了一些默认的基础常量,同时提供了ApplicationContext之外的配置应用程序的方法。
AbstractApplicationContext:应用上下文的抽象实现类。这个类可以说是应用上下文的骨架实现类。它不关心用到的配置的存储方式;实现了通用的应用上下文功能;采用了模板设计模式,具体的实现类需要实现它定义的抽象方法。
Base class for {@link org.springframework.context.ApplicationContext}
- implementations which are supposed to support multiple calls to {@link #refresh()},
- creating a new internal bean factory instance every time.
- Typically (but not necessarily), such a context will be driven by
- a set of config locations to load bean definitions from.
AbstractRefreshableApplicationContext:这个类支持多次刷新上下文。每次刷新时,它会创建一个新的内部Bean Factory(Bean工厂,通过它实际持有创建的bean)。
AbstractRefreshableConfigApplicationContext:提供了对某种形式的存储配置文件路径的支持,包括类路径(ClassPath),文件系统等。
AbstractXmlApplicationContext:这个类提供了从XML文件中提取bean定义的功能(通过XmlBeanDefinitionReader实现)
ClassPathXmlApplicationContext:这个类从Class path获取Context配置文件。
下面就以dubbo源代码中提供的demo来跟踪一下ClassPathXmlApplicationContext这个应用上下文的启动过程。它主要通过下面这一行代码来启动:
|
|
这行代码很简单,就是调用了构造方法,参数是一个字符串数组,只有一个元素,指出了配置文件的路径。
进入这个构造方法:
它又调用了自己的另一个构造函数
这个构造函数先调用了超类的构造函数,之后判断传入的是否刷新的布尔值,如果为true,则调用refresh方法。
超类的构造方法基本什么也没做,除了每个超类定义的在构造函数之前就需要初始化的field的初始化之外,只是在AbstractApplicationContext的构造函数中设置了一下parent context(Spring支持有层级的应用上下文,但本例中不涉及)。
由此可见,所有的启动和刷新上下文的功能都是refresh这个方法完成的。这个方法是在ConfigurableApplicationContext中定义,在AbstractApplicationContext中定义的,子类没有覆盖它,这也说明Spring不同的上下文启动和刷新的流程是通用的。
|
|
prepareFresh方法主要做了一些准备工作,如设置启动时间,设置关闭状态为false,活动状态为true,初始化属性源等。
obtainFreshBeanFactory方法内部通过AbstractRefreshableApplicationContext中的refreshBeanFactory方法刷新bean工厂,它先判断内部的bean factory是否已存在,若存在则销毁它们保存的bean,并关闭之。之后这个方法的核心工作是调用了createBeanFactory方法创建内部的bean factory。
|
|
可见是新建了一个DefaultListableBeanFactory。这个类的类关系如下图:
关注一下BeanFactory这个接口,它是访问Spring bean容器的根接口,提供了访问bean容器的基本功能。
Extension of the {@link BeanFactory} interface to be implemented by bean factories
- that can enumerate all their bean instances, rather than attempting bean lookup
- by name one by one as requested by clients. BeanFactory implementations that
- preload all their bean definitions (such as XML-based factories) may implement
- this interface.
ListableBeanFactory提供了枚举bean实例的功能,它会预加载bean的定义
BeanDefinitionRegistry:持有bean定义,例如root bean definition和child bean definition实例通常被bean factory实现。
DefaultListableBeanFactory:ListableBeanFactory和BeanDefinitionRegistry的默认实现。常用于在访问bean之前,保存所有bean的definition。
|
|
通过loadBeanDefinition加载定义到beanFactory中。这个方法是通过XmlBeanDefinitionReader去加载配置文件中的bean定义。具体过程比较繁琐,这里就不展开了,后续有时间再专门介绍。加载完之后,会将beanDefinition保存在DefaultListableBeanFactory的一个field中:
|
|
至此,Spring容器启动过程中的第一大步骤就算基本完成了,就是将bean定义从配置文件中读取出来,并解析为BeanDefinition保存在应用上下文的内置bean factory的内部的一个map钟,key为配置文件中定义的bean的name。
之后回到refresh方法,下面是prepareBeanFactory方法,这个方法就是对内部的bean factory做各种设置,以方便后面使用。具体就不介绍了。感兴趣可以自行研究代码。
postProcessBeanFactory是一个空方法,可以自定义一些对bean factory的定制化处理。由此以及后续的过程可以看出,Spring非常注重扩展性,留出了很多供使用者灵活扩展的地方,充分体现了“对修改关闭,对扩展开放”的面向对象设计原则。
invokeBeanFactoryPostProcessors:实例化并调用所有的BeanFactoryPostProcessor,BeanFactoryPostProcessor就是在bean factory的标准初始化流程结束之后,对它进行一些特殊配置的类。这个接口和后面的一些接口都可以看出Spring设计的原则,那就是先定义好某个功能的标准处理流程,但也提供了进行定制化处理的接口,并通过先注册后调用的方式很有秩序的进行处理。
registerBeanPostProcessors:实例化并调用所有已经注册的BeanPostProcessor。BeanPostProcessor和BeanFactoryPostProcessor类似,只不过一个是针对bean factory,一个是针对具体的bean。它定义了两个方法postProcessBeforeInitialization和postProcessAfterInitialization。前者会在某个bean的初始化方法(InitializingBean接口的afterPropertiesSet方法,或自定义的init-method)调用之前被调用。后者则是在初始化方法调用之后调用。
initMessageSource方法初始化message source。
initApplicationEventMulticaster方法初始化应用事件多播器。应用事件多播器是管理一系列ApplicationListener的,并且发布事件给它们。
onRefresh空方法,留给子类扩展。
registerListeners是获取所有实现了ApplicationListener的类,并注册它们,同时将一些早起的Application event发布出去。
finishBeanFactoryInitialization终于到最重要的一步了,就是完成Context中的bean factory的初始化,并初始化所有的还未初始化的单例bean。这个方法首先又对bean factory做了一系列设置,之后调用DefaultListableBeanFactory的preInstantiateSingletons方法对bean进行了初始化。
|
|
基本过程是就是遍历所有的bean definition,判断不是抽象类,同时是单例,并且没有设置lazy-init的就进行处理。处理时又分为是否是工厂类和不是工厂类进行处理。普通bean直接调用getBean进行处理,工厂bean则要进行一些处理,判断是否是立即加载的。
getBean内部直接调用了doGetBean方法,doGetBean中最终调用了createBean方法来创建一个bean,createBean中调用了doCreateBean来实际创建一个bean。
Spring是通过一个BeanWrapper接口来包裹我们实际要创建的类型的bean,这也是一种比较常见的设计模式,就是通过包装类来提供一些额外的功能。BeanWrapper的实现类主要是实现了Bean的属性编辑器的功能。doCreateBean做的事情比较杂,后续有时间再专门分析。
finishRefresh方法主要是完成刷新,主要做了一些善后工作。
通过对ClassPathXmlApplicationContext的启动过程的分析,我们可以总结一些规律。一是Spring的应用上下文的类体系设计得比较复杂,也因此显得很强大和完善。二是标准流程和扩展流程相分离,给使用者的扩展留出了足够的空间。三是采用了很多内部缓存类,比如缓存了bean的定义,bean实例,bean的name等都用了不同的集合做了专门的缓存。特别是针对单例bean的三级缓存,可以解决循环依赖的问题。