Spring源码解析——IoC部分(一)

1  概述

        IoC的意义在于它优雅地解决了类之间的复杂依赖关系,依赖对象的获得方式被反转,对象不必主动去获取被依赖的对象,容器会在适当的时机主动把被依赖对象交到对象手中,通过一种叫做依赖注入的方式。

        根据《Spring技术内幕》的介绍,IoC的两个核心接口是BeanFactory和ApplicationContext,它们的操作对象Bean被抽象成BeanDefinition。BeanFactory和ApplicationContext这两个接口都可以视为容器,其中BeanFactory是IoC容器的基本表现形式,ApplicationContext在BeanFactory的基础上提供了更多功能,是IoC容器的高级表现形式。这篇文章主要从接口层面介绍IoC:IoC容器的功能,BeanFactory的启动过程简析。在此基础上我们再通过第二篇和第三篇深入IoC的Bean加载和依赖注入。

1.1  IoC的类体系与功能

Spring源码解析——IoC部分(一)_第1张图片

1.1.1  IoC接口与功能

a. BeanFactory

    BeanFactory只提供Bean获取功能。

b. ListableBeanFactory

    提供获取Bean,BeanDefinition,根据注解获取Bean,根据Bean获取注解功能。

c.  HieracicalBeanFactory

    提供父级BeanFactory获取。

d.  ApplicationEventPublisher

    事件发布功能。

e.  ResourceLoader

    classPath:路径下的文件资源获取。

f.  ResourcePatterResolver

    classPath*:路径下的文件资源获取。

g.  EnvironmentCapable

    获取环境配置(里面是键值对),其配置主要用于区分应用的运行环境。

h.  MessageSource

    国际化资源。

i.  ApplicationContext

    获取容器信息,获取自动注入工厂类。

j.  AutoClosable

    try-whith-resource必须实现的接口,属于java.io包。多说一句,try-whith-resource是Java的语法糖,它的效果是把  try{打开IO,IO操作}catch(异常){}finally{关闭IO}  这种模式缩短到两步:try(打开/关闭IO){IO操作}finally{不用写关闭IO逻辑} ,甚至一步搞定:try(IO打开/关闭){IO操作} 。

k.  LifeCycel

    生命周期

l. Closable

    关闭IO资源

m. ConfiurableApplicationContext

    容器配置和容器生命周期,最重要的是refresh()方法,它是启动容器的地方。

 

小结:

    ApplicationContext接口在BeanFactory基础上增加了资源配置和容器分层功能,ConfigurableApplicationContext接口继承ApplicationContext,收获了Bean获取功能,配置资源获取功能,此外还融入了更多配置和生命周期相关的功能,可谓是接口中的集大成者。

2.开门见山
Spring的启动过程包括三个主要阶段:A.定位资源,B.载入资源,读取Bean信息形成BeanDefinition这种数据结构并保存,C.使用BeanDefinition实例化Bean同时完成依赖注入,根据配置调用初始化方法。B和C阶段是SpringIoC容器参与发挥作用的重要阶段,了解这一节的内容有助于理解后面两篇文章,IoC部分(二)是AB过程的源码解读,IoC部分(三)是C过程的源码解读。
两个术语:
load(载入)——读取Bean配置信息。
register(注册)——把一个东西保存起来,例如保存到Map。

 

Spring支持Xml方式的配置和Springboot纯注解方式的配置两种形式,无论哪种方式下C过程都是大抵相同的。介绍两种形式下A/B两个过程发生的位置之前,先预览spring容器启动的主要过程,这个过程也是我们下一篇文章的分析重点:

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // 启动前准备
      prepareRefresh();

      // 容器初始化,这里是可能载入BeanDefinition的位置
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 容器设置
      prepareBeanFactory(beanFactory);

      try {
         // 再次容器设置,上一步的补充操作
         postProcessBeanFactory(beanFactory);

         // 调用【BeanFactory后处理器】,处理BeanDefinition,这里可能载入BeanDefinition
         invokeBeanFactoryPostProcessors(beanFactory);

         // 注册【Bean后处理器】,这些后处理器接收容器事件,有2个在Bean初始化前后自动调用的方法
         registerBeanPostProcessors(beanFactory);

         // 初始化消息源
         initMessageSource();

         // 初始化事件广播器
         initApplicationEventMulticaster();

         // 初始化特殊Bean
         onRefresh();

         // 注册监听器
         registerListeners();

         // 完成Bean容器初始化,实例化所有非lazy-init的Bean
         finishBeanFactoryInitialization(beanFactory);

         // 发布容器启动完成事件
         finishRefresh();
      }

2.1 Xml方式

具体容器类型:XmlWebApplicationContext

 

a.初始化容器:
      XMLBeanDefinition读取器:XmlBeanDefinitionReader(读取IO流)
         子读取器:DefaultBeanDefinitionDocumentReader
         解析过程:解析Xml节点,根据xml标签namespace读取xml节点中的信息,使用 b.调用BeanFactory后处理器:
            内部注解后处理器(org.springframework.context.annotation.internalConfigurationAnnotationProcessor)

            MyBatis后处理器(org.mybatis.spring.mapper.MapperScannerConfigurer)

c.注册:DefaultListableBeanFactory

 

2.2 纯注解方式(SpringBoot)

具体容器类型:AnnotationConfigServletWebServerApplicationContext(默认)

//SpringBoot启动类SpringApplication中的方法,根据配置创建ApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
   Class contextClass = this.applicationContextClass;
   if (contextClass == null) {
      try {
         switch (this.webApplicationType) {
         case SERVLET: //AnnotationConfigServletWebServerApplicationContext
            contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
            break;
         case REACTIVE: //AnnotationConfigReactiveWebServerApplicationContext
            contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
            break;
         default: //AnnotationConfigApplicationContext
            contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
         }
      }
      catch (ClassNotFoundException ex) {

a.初始化容器:
    无操作
b.调用后处理器:
    后处理器:ConfigurationClassPostProcessor
        解析器:ConfigurationClassParser
          子解析器:ComponentScanAnnotationParser

 

c.注册:DefaultListableBeanFactory

小结:不同的容器,载入和解析BeanDefinition的方式有很大差别,但是这些差别都被接口方法屏蔽,要么在obtainFreshBeanFactory()中,要么在invokeBeanFactoryPostProcessors()中,大的流程在抽象中成型,细的实现在具体中完善,高层不依赖于底层细节,细节连贯起来是高层的体现。学习源码的作用不仅是搞清楚原理,更是对设计思想的融会贯通。


3.调试技巧

这一节交代调试的方式,下一节跟着调试代码的过程细细品味实现细节。

工具:Intrlij IDEA
说明:下面的断点行号均为源码中的行号,不是jar反编译代码的行号
a.断点位置
Spring容器启动位置:org.springframework.context.support.AbstractApplicationContext   515,549
BeanDefinition注册位置:org.springframework.beans.factory.support.DefaultListableBeanFactory   918,924,938

b.设置断点程序挂起条件

BeanDefinition注册位置会有很多BeanDefinition被put到Map中,通常我们只关心某个特定类的beanDefinition是什么时候放进去的,这时可以设置断点程序挂起条件:在断点位置右键编辑挂起条件后点击"Done",开始调试。

Spring源码解析——IoC部分(一)_第2张图片

c.查看类继承体系

在类名上右键>Diagrams>showDiagram,就会生文章开始的那副图片,一张排列完好的类继承结构图,我们甚至可以右键输出成各种格式的图片保存起来。

Spring源码解析——IoC部分(一)_第3张图片

 

你可能感兴趣的:(Java,java,spring,ioc,源码)