Spring IOC容器的实现原理

在设计时,首先需要考虑IOC容器的功能(输入和输出),可以借助下图初步的看一下IOC的整体功能:


image

在此基础上,我们初步的去思考,如果作为一个IOC容器的设计者,主体上应该包含哪几个部分:

  • 加载Bean的配置(比如xml配置)
    • 比如不同类型资源的加载,解析成生成统一Bean的定义
  • 根据Bean的定义加载生成Bean的实例,并放置在Bean容器中
    • 比如Bean的依赖注入,Bean的嵌套,Bean存放(缓存)等
  • 除了基础Bean外,还有常规针对企业级业务的特别Bean
    • 比如国际化Message,事件Event等生成特殊的类结构去支撑
  • 对容器中的Bean提供统一的管理和调用
    • 比如用工厂模式管理,提供方法根据名字/类的类型等从容器中获取Bean
  • ...

1. Spring IOC的体系结构

Spring Bean的创建时典型的工厂模式,一系列的Bean工厂,为开发者管理对象间的依赖关系童工了很多便利和基础服务;在顶层设计结构中主要围绕着BeanFactory和xxxRegistry进行。
BeanFactory:工厂模式定义了IOC容器的基本功能规范
BeanRegistry:向IOC容器手工注册BeanDefinition 对象的方法

1.1 BeanFactory定义了那些基本功能规范?

BeanFactory作为一个最顶层的接口,有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory。我们看下BeanFactory接口:

public interface BeanFactory {    
      
    //用于取消引用实例并将其与FactoryBean创建的bean区分开来。
    //例如,如果命名的bean是FactoryBean,则获取将返回Factory,而不是Factory返回的实例。
    String FACTORY_BEAN_PREFIX = "&"; 
        
    //根据bean的名字和Class类型等来得到bean实例    
    Object getBean(String name) throws BeansException;    
    Object getBean(String name, Class requiredType) throws BeansException;    
    Object getBean(String name, Object... args) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    //返回指定bean的Provider
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    //检查工厂中是否包含给定name的bean,或者外部注册的bean
    boolean containsBean(String name);

    //检查所给定name的bean是否为单例/原型
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    //判断所给name的类型与type是否匹配
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    //获取给定name的bean的类型
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    //返回给定name的bean的别名
    String[] getAliases(String name);
}

1.3 BeanFactory为什么要定义这么多层次的接口?定义了那些接口?

image

主要是为了区分在 Spring 内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。定义的接口如下:

  • ListableBeanFactory:该接口定义了访问容器中 Bean 基本信息的若干方法,如查看Bean 的个数、获取某一类型 Bean 的配置名、查看容器中是否包括某一 Bean 等方法;
  • HierarchicalBeanFactory:父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器; 通过 HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。(可以理解为容器之间的继承关系)Spring 使用父子容器实现了很多功能,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务层和持久层的 Bean 则看不到展现层的 Bean。
  • ConfigurableBeanFactory:是一个重要的接口,增强了 IoC 容器的可定制性,它定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法;
  • ConfigurableListableBeanFactory:ListableBeanFactory 和 ConfigurableBeanFactory的融合;
  • AutowireCapableBeanFactory:定义了将容器中的 Bean 按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法;

1.4 如何将Bean注册到BeanFactory中?

Spring 配置文件中每一个<bean>节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注册 BeanDefinition 对象的方法。

Bean对象存在依赖嵌套等关系,所以设计者设计了BeanDefinition,它用来对Bean对象及关系定义;理解时只需要抓住如下三个要点:

  • BeanDefinition 定义了各种Bean对象及其相互的关系
  • BeanDefinitionReader 这是BeanDefinition的解析器
  • BeanDefinitionHolder 这是BeanDefination的包装类,用来存储BeanDefinition,name以及aliases等。

1. BeanDefinition:各种Bean对象及其相互的关系

image

2. BeanDefinitionReader: Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。
image

3. BeanDefinitionHolder:BeanDefinitionHolder 这是BeanDefination的包装类,用来存储BeanDefinition,name以及aliases等。
image

1.5 ApplicationContext接口的实现

在考虑ApplicationContext接口的实现时,关键的点在于,不同Bean的配置方式(比如xml,groovy,annotation等)有着不同的资源加载方式,这便衍生除了众多ApplicationContext的实现类。


image
  1. 从类结构设计上看,围绕是否需要Refresh容器衍生出两个抽象类:
  • GenericApplicationContext:是初始化的时候就创建容器,往后的每次refresh都不会更改。
  • AbstractRefreshableApplicationContext:AbstractRefreshableApplicationContext及子类的每次refresh都是先清除已有(如果不存在就创建)的容器,然后再重新创建;AbstractRefreshableApplicationContext及子类无法做到GenericApplicationContext混合搭配从不同源头获取bean的定义信息
  1. 从加载的源来看(比如xml,annotation等), 衍生出众多类型的ApplicationContext, 典型比如:
  • FileSystemXmlApplicationContext:从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件。
  • ClassPathXmlApplicationContext:从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式。
  • AnnotationConfigApplicationContext: 从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式。
  1. 设计者在设计时AnnotationConfigApplicationContext为什么是继承GenericApplicationContext?
  • 因为基于注解的配置,是不太会被运行时修改的,这意味着不需要进行动态Bean配置和刷新容器,所以只需要GenericApplicationContext。
    而基于XML这种配置文件,这种文件是容易修改的,需要动态性刷新Bean的支持,所以XML相关的配置必然继承AbstractRefreshableApplicationContext; 且存在多种xml的加载方式(位置不同的设计),所以必然会设计出AbstractXmlApplicationContext, 其中包含对XML配置解析成BeanDefination的过程。

最后结合设计结构来看一张图:

image

2. IOC初始化流程

首先可以从ClasspathXmlApplicationContext对象入手,创建容器,探究初始化流程。

ApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml", "daos.xml", "services.xml");
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
    this(configLocations, true, (ApplicationContext)null);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
    // 设置Bean资源加载器,调用AbstractApplicationContext的构造方法
    super(parent);

    // 设置配置路径
    this.setConfigLocations(configLocations);

    // 初始化容器
    if (refresh) {
        this.refresh();
    }
}

2.1 设置资源解析器和环境

调用父类容器AbstractApplicationContext的构造方法(super(parent)方法)为容器设置好Bean资源加载器

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
    // 默认构造函数初始化容器id, name, 状态 以及 资源解析器
    this();

    // 将父容器的Environment合并到当前容器
    this.setParent(parent);
}

通过AbstractApplicationContext默认构造函数初始化容器id, name, 状态 以及 资源解析器

public AbstractApplicationContext() {
    this.logger = LogFactory.getLog(this.getClass());
    this.id = ObjectUtils.identityToString(this);
    this.displayName = ObjectUtils.identityToString(this);
    this.beanFactoryPostProcessors = new ArrayList();
    this.active = new AtomicBoolean();
    this.closed = new AtomicBoolean();
    this.startupShutdownMonitor = new Object();
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.applicationListeners = new LinkedHashSet();
    this.resourcePatternResolver = this.getResourcePatternResolver();
}
// Spring资源加载器
protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}

通过AbstractApplicationContext的setParent(parent)方法将父容器的Environment合并到当前容器

public void setParent(@Nullable ApplicationContext parent) {
    this.parent = parent;
    if (parent != null) {
        Environment parentEnvironment = parent.getEnvironment();
        if (parentEnvironment instanceof ConfigurableEnvironment) {
            this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment);
        }
    }
}

2.2 设置配置路径

在设置容器的资源加载器之后,接下来FileSystemXmlApplicationContet执行setConfigLocations方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean定义资源文件的定位

public void setConfigLocations(@Nullable String... locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];

        for(int i = 0; i < locations.length; ++i) {
            // 解析配置路径
            this.configLocations[i] = this.resolvePath(locations[i]).trim();
        }
    } else {
        this.configLocations = null;
    }
}
protected String resolvePath(String path) {
    // 从上一步Environment中解析
    return this.getEnvironment().resolveRequiredPlaceholders(path);
}

2.3 主体流程

Spring IoC容器对Bean定义资源的载入是从refresh()函数开始的,refresh()是一个模板方法,refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。refresh的作用类似于对IoC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

            // Prepare this context for refreshing.
            /*
            * 刷新预处理工作
            * initPropertySources(); 初始化一些属性设置,子类自定义个性化的属性设置。
            * 属性校验。
            * 使用一个  new LinkedHashSet<>(this.applicationListeners); 保存容器中的早期事件,等到事件派发器初始化后,将事件派发出去
            * */
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            /*
            * 获取BranFactory
            * refreshBeanFactory(); 刷新BeanFactory,创建一个BeanFactory对象(DefultListableBeanFactory();)并设置序列化id
            * */
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            /*
            * 预准备工作,对BeanFactory设置属性
            * 1.设置类加载器以及表达式解析器
            * 2.添加部分BeanPostProcessor(ApplicationContextAwareProcessor)
            * 3.设置忽略的自动装配的接口
            * 4.注册可以解析的自动装配
            * 5.添加BeanPostProcessor(ApplicationListenerDetector)
            * 6.添加编译时的AspectJ支持
            * 7.给容器中注册一些可用的组件,environment、systemProperties、systemEnviroment等信息
            * */
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                /*
                * BeanFactory主备工作完成后进行后置处理器工作,子类通过重写方法,在BeanFactory创建并预准备完成后做进一步设置
                * */
                postProcessBeanFactory(beanFactory);
                //========================================== 以上是BeanFactory的创建以及预准备工作   ==========================================

                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // Invoke factory processors registered as beans in the context.
                /*
                * 执行BeanFactory的后置处理器,在BeanFactory的标准初始化完成后执行
                *   BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 两个接口
                * 1.先执行BeanDefinitionRegistryPostProcessor接口的后置处理器
                *   1).获取所有的 BeanDefinitionRegistryPostProcessor
                *   2).看优先级排序后执行 postProcessor。postProcessorDefinitionRegistry();
                *   3).再执行实现ordered顺序接口的后置处理器执行对应的方法
                *   4).最后执行没有任何优先级或顺序的后置处理器
                * 2.再执行BeanFactoryPostProcessor接口的后置处理器
                *   1).获取所有的 BeanFactoryPostProcessor
                 *  2).看优先级排序后执行 postProcessor。BeanFactoryPostProcessor();
                 *  3).再执行实现ordered顺序接口的后置处理器执行对应的方法
                 *  4).最后执行没有任何优先级或顺序的后置处理器
                * */
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                /*
                * 注册Bean的后置处理器,拦截Bean创建过程,不同的接口类型的后置处理器在Bean创建前后执行的时机是不一样的
                * 1.获取所有的后置处理器,都默认有指定优先级
                * 2.先注册实现优先级的后置处理器,后注册实现顺序的后置处理器,把每一个后执行处理器添加到 BeanFactory 中
                * 3.最后注册没有任何优先级的接口
                * 4.最终注册MergedBeanDefinitionPostProcessor
                * 5.注册一个ApplicationListenerDetector,来再创建Bean完成后检查是否是监听器,如果是放到容器中
                * */
                registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();

                // Initialize message source for this context.
                /*
                * 初始化MessageSource组件,做国际化、消息绑定、消息解析等功能
                * 1.获取BeanFactory
                * 2.看是否有MessageSource的组件,如果有赋值给MessageSource属性,没有则创建一个DetegatingMessageSource
                * 3.把创建好的MessageSource放到容器中
                * */
                initMessageSource();

                // Initialize event multicaster for this context.
                /*
                * 初始化事件派发器
                * 1.获取BeanFactory,从BeanFactory中获取事件派发器
                * 2.如果没有则创建一个SimpleApplicationEventMulticaster,并放到容器中
                * */
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                /*
                * 留给子容器,重写onRefresh();自定义其他逻辑
                * */
                onRefresh();

                // Check for listener beans and register them.
                /*
                * 检查和注册监听器
                * 1.从容其中拿到所有的监听器
                * 2.将每一个监听器添加到事件派发器中
                * 3.派发之间步骤产生的事件
                * */
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                /*
                * 初始化剩下的所有的单例Bean
                * 1.beanFactory.preInstantiateSingletons();
                *   1).获取所有的Bean的定义信息遍历,RootBeanDefinition
                *   2).判断Bean不是抽象的、单例的、不是懒加载
                *   3).判断是否是FactoryBean,是否是实现FactoryBean的工厂Bean,如果是利用工厂创建对象,如果不是利用getBean创建对象
                *       1》.doGetBean();先获取缓存中保存的单例,如果没有就开始创建
                *       2》.标记当前Bena正在被创建,防止多线程创建,导致不是单例Bean
                *       3》.拿到Bean的定义信息
                *       4》.获取当前Bean依赖的其他Bean,如果有调用getBean,把依赖对象创建出来
                *       5》.createBean();
                *           a.解析Bean的定义信息
                *           b.BeanPoseProcessor提前拦截返回代理对象
                *           c.如果没有代理对象,调用doCreateBean();
                *           d.Bean的生命周期相关流程。。。
                *       6》.注册Bean的销毁方法
                *       7》.将创建的Bean添加到单例池中
                *   4).所有Bean创建完成后检查所有的Bean是否是SmartInitializingSingleton接口的
                * */
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                /*
                * 完成BeanFactory的初始化和创建工作
                * 1.initLifecycleProcessor();初始化容器生命周期有关的后置处理器,允许实现LifecycleProcessor,在BeanFactory生命周期执行onRefresh()、onClose();
                *   默认从容器找,没有则使用DefultLifecycleProcessor并注册到容器中
                * 2.拿到生命周期处理器执行onRefresh();
                * 3.给容器发布事件,容器刷新完成
                * */
                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.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
                contextRefresh.end();
            }
        }
    }

这里的设计上是一个非常典型的资源类加载处理型的思路,头脑中需要形成如下图的顶层思路

  • 模板方法设计模式,模板方法中使用典型的钩子方法
  • 将具体的初始化加载方法插入到钩子方法之间
  • 将初始化的阶段封装,用来记录当前初始化到什么阶段;常见的设计是xxxPhase/xxxStage;
  • 资源加载初始化有失败等处理,必然是try/catch/finally...


    image

2.4 Refresh方法流程

IOC初始化流程.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容