【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

点击上方“Java面试题精选”,关注公众号

面试刷图,查缺补漏

号外:往期面试题,10篇为一个单位归置到本公众号菜单栏-面试题,有需要的欢迎翻阅

阶段汇总集合:

一、老规矩,先比比点幺蛾子

作为一个经常使用 Spring 的后端程序员,小编很早就想彻底弄懂整个 Spring 框架了!但它整体是非常大的,所有继承图非常复杂,加上小编修行尚浅,显得力不从心。不过,男儿在世当立志,今天就先从 Spring IOC 容器的初始化开始说起,即使完成不了对整个 Spring 框架的完全掌握,也不丢人,因为小编动手了,稳住,咱能赢!

下面说一些阅读前的建议:

  • 1、阅读源码分析是非常无聊的,但既然你进来了,肯定也是对这个东西进行了解,也希望这篇总结能对你有所启发。
  • 2、前方高能,文章可能会非常的长,图文并茂。
  • 3、阅读前建议你对相关设计模式、软件设计 6 大原则有所了解,小编会在行文中进行穿插。
  • 4、小编在读大四,学识尚浅,喜欢专研,如果你发现文章观点有所错误或者与你见解有差异,欢迎评论区指出和交流!
  • 5、建议你边看文章的时候可以边在 IDE 中进行调试跟踪
  • 6、文章所有 UML 图利用 idea 自动生成,具体生成方法为:`选中一个类名,先ctrl+shift+alt+U,再ctrl+alt+B,然后回车即可`
  • 二、文章将围绕什么来进行展开?

    不多,就一行代码,如下图:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    这句是 Spring 初始化的代码,虽然只有一句代码,但内容贼多!

    三、Spring 容器 IOC 有哪些东西组成?

    这样子,小编先理清下思路,一步一步来:

  • 1、上面那句代码有个文件叫`applicationContext.xml`, 这是个资源文件,由于我们的`bean`都在里边进行配置定义,那 Spring 总得对这个文件进行`读取并解析`吧!所以 Spring 中有个模块叫`Resource`模块,顾名思义,就是`资源`嘛!用于对所有资源`xml、txt、property`等文件资源的抽象。关于对`Resource`的更多知识,可以参考下边两篇文章:
  • 谈一谈我对 Spring Resource 的理解:https://juejin.im/post/5ab0ce60518825611a405106

    Spring 资源文件剖析和策略模式应用(李刚):http://www.ibm.com/developerworks/cn/java/j-lo-spring-resource/index.html

    下面先贴一张小编生成的类图 (图片有点大,不知道会不会不清晰,如果不清晰可以按照上面说的idea生成方法去生成即可)

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    可以看到 Resource是整个体系的根接口,点进源码可以看到它定义了许多的 策略方法,因为它是用了 策略模式这种设计模式,运用的好处就是 策略接口/类定义了同一的策略,不同的子类有不同的具体策略实现,客户端调用时传入一个具体的实现对象 比如UrlResource或者FileSystemResource 策略接口/类Resource即可!

    所有 策略如下:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~
  • 2、上面讲了 Spring 框架对各种资源的抽象采用了`策略模式`,那么问题来了,现在表示资源的东西有了,那么是怎么把该资源加载进来呢?于是就有了下面的`ResourceLoader`组件,该组件负责对 Spring 资源的加载,资源指的是`xml`、`properties`等文件资源,返回一个对应类型的`Resource`对象。。UML 图如下:
  • 【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    从上面的 UML 图可以看出, ResourceLoader组件其实跟 Resource组件差不多,都是一个根接口,对应有不同的子类实现,比如加载来自文件系统的资源,则可以使用 FileSystemResourceLoader, 加载来自 ServletContext上下文的资源,则可以使用 ServletContextResourceLoader。还有最重要的一点,从上图看出, ApplicationContext, AbstractApplication是实现了 ResourceLoader的,这说明什么呢?说明我们的应用上下文 ApplicationContext拥有加载资源的能力,这也说明了为什么可以通过传入一个 String resource path ClassPathXmlApplicationContext("applicationContext.xml")就能获得 xml 文件资源的原因了!清晰了吗?nice!

  • 3、上面两点讲到了,好!既然我们拥有了加载器`ResourceLoader`,也拥有了对资源的描述`Resource`, 但是我们在 xml 文件中声明的`bean/`标签在 Spring 又是怎么表示的呢?注意这里只是说对`bean`的定义,而不是说如何将`bean/`转换为`bean`对象。我想应该不难理解吧!就像你想表示一个学生`Student`,那么你在程序中肯定要声明一个类`Student`吧!至于学生数据是从`excel`导入,或者程序运行时`new`出来,或者从`xml`中加载进来这些都不重要,重要的是你要有一个将现实中的实体表示为程序中的对象的东西,所以`bean/`也需要在 Spring 中做一个定义!于是就引入一个叫`BeanDefinition`的组件,UML 图如下:
  • 【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    下面讲解下 UML 图:

    首先配置文件中的 bean/标签跟我们的 BeanDefinition是一一对应的, bean元素标签拥有 class scope lazy-init等配置属性, BeanDefinition则提供了相应的 beanClass scope lazyInit属性。

    其中 RootBeanDefinition是最常用的实现类,它对应一般性的 bean元素标签, GenericBeanDefinition是自 2.5以后新加入的 bean文件配置属性定义类,是一站式服务类。在配置文件中可以定义父 bean和子 bean,父 bean RootBeanDefinition表示,而子 bean ChildBeanDefiniton表示,而没有父 bean bean就使用 RootBeanDefinition表示。 AbstractBeanDefinition对两者共同的类信息进行抽象。  Spring通过 BeanDefinition将配置文件中的 bean配置信息转换为容器的内部表示,并将这些 BeanDefiniton注册到 BeanDefinitonRegistry中。 Spring容器的 BeanDefinitionRegistry就像是 Spring配置信息的内存数据库,主要是以 map的形式保存,后续操作直接从 BeanDefinitionRegistry中读取配置信息。一般情况下, BeanDefinition只在容器启动时加载并解析,除非容器刷新或重启,这些信息不会发生变化,当然如果用户有特殊的需求,也可以通过编程的方式在运行期调整 BeanDefinition的定义。

  • 4、有了加载器`ResourceLoader`,也拥有了对资源的描述`Resource`,也有了对`bean`的定义,我们不禁要问,我们的`Resource`资源是怎么转成我们的`BeanDefinition`的呢? 因此就引入了`BeanDefinitionReader`组件, Reader 嘛!就是一种读取机制,UML 图如下:
  • 【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    从上面可以看出,Spring 对 reader 进行了抽象,具体的功能交给其子类去实现,不同的实现对应不同的类,如 PropertiedBeanDefinitionReader, XmlBeanDefinitionReader对应从 Property 和 xml 的 Resource 解析成 BeanDefinition

    其实这种读取数据转换成内部对象的,不仅仅是 Spring 专有的,比如:Dom4j 解析器 SAXReader reader = new SAXReader(); Document doc = reader.read(url.getFile());//url 是一个 URLResource 对象 严格来说,都是 Reader 体系吧,就是将统一资源数据对象读取转换成相应内部对象。

  • 5、好了!基本上所有组件都快齐全了!对了,还有一个组件,你有了`BeanDefinition`后,你还必须将它们注册到工厂中去,所以当你使用`getBean()`方法时工厂才知道返回什么给你。还有一个问题,既然要保存注册这些`bean`, 那肯定要有个数据结构充当容器吧!没错,就是一个`Map`, 下面贴出`BeanDefinitionRegistry`的一个实现,叫`SimpleBeanDefinitionRegistry`的源码图:
  • 【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    BeanDefinitionRegistry的 UML 图如下:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    从图中可以看出, BeanDefinitionRegistry有三个默认实现,分别是 SimpleBeanDefinitionRegistry DefaultListableBeanFactory GenericApplicationContext, 其中 SimpleBeanDefinitionRegistry DefaultListableBeanFactory都持有一个 Map,也就是说这两个实现类把保存了 bean。而 GenericApplicationContext则持有一个 DefaultListableBeanFactory对象引用用于获取里边对应的 Map。在 DefaultListableBeanFactory

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    GenericApplicationContext

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~
  • 6、前面说的 5 个点基本上可以看出`ApplicationContext`上下文基本直接或间接贯穿所有的部分,因此我们一般称之为`容器`,除此之外,`ApplicationContext`还拥有除了`bean容器`这种角色外,还包括了获取整个程序运行的环境参数等信息(比如 JDK 版本,jre 等),其实这部分 Spring 也做了对应的封装,称之为`Enviroment`, 下面就跟着小编的 eclipse, 一起 debug 下容器的初始化工程吧!
  • 【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    四、实践是检验真理的唯一标准

    学生类 Student.java如下:

    
    package com.wokao666;
    
    public class Student {
    
        private int id;
        private String name;
        private int age;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public Student(int id, String name, int age) {
            super();
            this.id = id;
            this.name = name;
            this.age = age;
        }
    
        public Student() {
            super();
        }
    
        @Override
        public String toString() {
            return "Student [id=" + id + ", ]";
        }
    
    }
    

    application.xml中进行配置,两个 bean:

    
    bean id="stu1" class="com.wokao666.Student"
         property /property
         property /property
         property /property
     /bean
      bean id="stu2" class="com.wokao666.Student"
         property /property
         property /property
         property /property
     /bean
    

    好了,接下来给最开头那段代码打个断点 ( Breakpoint):

    第一步: 急切地加载ContextClosedEvent类,以避免在WebLogic 8.1中的应用程序关闭时出现奇怪的类加载器问题。

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    这一步无需太过在意!

    第二步: 既然是new ClassPathXmlApplicationContext() 那么就调用构造器嘛!

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    第三步:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    第四步:

    好,我们跟着第三步中的 super(parent),再结合上面第三节的第 6 小点 UML 图一步一步跟踪,然后我们来到 AbstractApplicationContext的这个方法:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    那么里边的 resourcePatternResolver的类型是什么呢?属于第三节说的 6 大步骤的哪个部分呢?通过跟踪可以看到它的类型是 ResourcePatternResolver类型的,而 ResourcePatternResolver又是继承了 ResourceLoader接口,因此属于加载资源模块,如果还不清晰,咱们再看看 ResourcePatternResolver的源码即可,如下图:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    对吧!不仅继承 ResourceLoader接口,而且只定义一个 getResources()方法用于返回 Resource[]资源集合。再者,这个接口还使用了 策略模式,其具体的实现都在实现类当中,好吧!来看看 UML 图就知道了!

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    PathMatchingResourcePatternResolver这个实现类呢!它就是用来解释不同路径资源的,比如你传入的资源路径有可能是一个常规的 url, 又或者有可能是以 classpath*前缀,都交给它处理。

    ServletContextResourcePatternResolver这个实现类顾名思义就是用来加载 Servlet上下文的,通常用在 web 中。

    第五步:

    接着第四步的方法,我们在未进入第四步的方法时,此时会对 AbstractApplicationContext进行实例化,此时 this对象的某些属性被初始化了 (如日志对象),如下图:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    接着进入 getResourcePatternResolver()方法:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    第四步说了, PathMatchingResourcePatternResolver用来处理不同的资源路径的,怎么处理,我们先进去看看!

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    如果找到,此时控制台会打印 找到用于OSGi包URL解析的Equinox FileLocator日志。没打印很明显找不到!

    运行完成返回 setParent()方法。

    第六步:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    如果父代是非 null,,则该父代与当前 this应用上下文环境合并。显然这一步并没有做什么事! parent显然是 null的,那么就不合并嘛!还是使用当前 this的环境。

    做个总结:前六步基本上做了两件事:

  • 1、初始化相关上下文环境,也就是初始化`ClassPathXmlApplicationContext`实例
  • 2、获得一个`resourcePatternResolver`对象,方便第七步的资源解析成`Resource`对象
  • 第七步:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    第七步又回到刚开始第三步的代码,因为我们前面 6 步已经完成对 super(parent)的追踪。让我们看看 setConfigLocation()方法是怎么一回事~

    
    /**
     * Set the config locations for this application context.//未应用上下文设置资源路径
     * pIf not set, the implementation may use a default as appropriate.//如果未设置,则实现可以根据需要使用默认值。
     */
    public void setConfigLocations(String... locations) {
        if (locations != null) {//非空
            Assert.noNullElements(locations, "Config locations must not be null");//断言保证locations的每个元素都不为null
            this.configLocations = new String[locations.length];
            for (int i = 0; i  locations.length; i++) {
                this.configLocations[i] = resolvePath(locations[i]).trim();//去空格,很好奇resolvePath做了什么事情?
            }
        }
        else {
            this.configLocations = null;
        }
    }
    

    进入 resolvePath()方法看看:

    
    /**
     * 解析给定的资源路径,必要时用相应的环境属性值替换占位符,应用于资源路径配置。
     * Resolve the given path, replacing placeholders with corresponding
     * environment property values if necessary. Applied to config locations.
     * @param path the original file path
     * @return the resolved file path
     * @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
     */
    protected String resolvePath(String path) {
        return getEnvironment().resolveRequiredPlaceholders(path);
    }
    

    进入 getEnvironment()看看:

    
    /**
     * {@inheritDoc}
     * pIf {@code null}, a new environment will be initialized via
     * {@link #createEnvironment()}.
     */
    @Override
    public ConfigurableEnvironment getEnvironment() {
        if (this.environment == null) {
            this.environment = createEnvironment();
        }
        return this.environment;
    }
    

    进入 createEnvironment(), 方法,我们看到在这里创建了一个新的 StandardEnviroment对象,它是 Environment的实现类,表示容器运行的环境,比如 JDK 环境,Servlet 环境,Spring 环境等等,每个环境都有自己的配置数据,如 System.getProperties() System.getenv()等可以拿到 JDK 环境数据; ServletContext.getInitParameter()可以拿到 Servlet 环境配置数据等等, 也就是说 Spring 抽象了一个 Environment来表示环境配置。

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    生成的 StandardEnviroment对象并没有包含什么内容,只是一个标准的环境,所有的属性都是默认值。

    总结:对传入的 path进行路径解析

    第八步: 这一步是重头戏

    先做个小结:到现在为止,我们拥有了以下实例:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    现在代码运行到如下图的 refresh()方法:

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    看一下这个方法的内容是什么?

    
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // 刷新前准备工作,包括设置启动时间,是否激活标识位,初始化属性源(property source)配置
            prepareRefresh();
    
            // 创建beanFactory(过程是根据xml为每个bean生成BeanDefinition并注册到生成的beanFactory
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
            //准备创建好的beanFactory(给beanFactory设置ClassLoader,设置SpEL表达式解析器,设置类型转化器【能将xml String类型转成相应对象】,
            //增加内置ApplicationContextAwareProcessor对象,忽略各种Aware对象,注册各种内置的对账对象【BeanFactory,ApplicationContext】等,
            //注册AOP相关的一些东西,注册环境相关的一些bean
            prepareBeanFactory(beanFactory);
    
            try {
                // 模板方法,为容器某些子类扩展功能所用(工厂后处理器)这里可以参考BeanFactoryPostProcessor接口的postProcessBeanFactory方法
                postProcessBeanFactory(beanFactory);
    
                // 调用所有BeanFactoryPostProcessor注册为Bean
                invokeBeanFactoryPostProcessors(beanFactory);
    
                // 注册所有实现了BeanPostProcessor接口的Bean
                registerBeanPostProcessors(beanFactory);
    
                // 初始化MessageSource,和国际化相关
                initMessageSource();
    
                // 初始化容器事件传播器
                initApplicationEventMulticaster();
    
                // 调用容器子类某些特殊Bean的初始化,模板方法
                onRefresh();
    
                // 为事件传播器注册监听器
                registerListeners();
    
                // 初始化所有剩余的bean(普通bean)
                finishBeanFactoryInitialization(beanFactory);
    
                // 初始化容器的生命周期事件处理器,并发布容器的生命周期事件
                finishRefresh();
            }
            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }
                // 销毁已创建的bean
                destroyBeans();
                // 重置`active`标志
                cancelRefresh(ex);
                throw ex;
            }
            finally {
                                //重置一些缓存
                resetCommonCaches();
            }
        }
    }
    

    在这里我想说一下,这个 refresh()方法其实是一个模板方法, 很多方法都让不同的实现类去实现,但该类本身也实现了其中一些方法,并且这些已经实现的方法是不允许子类重写的,比如: prepareRefresh()方法。更多模板方法设计模式,可看我之前的文章 谈一谈我对‘模板方法’设计模式的理解(Template)。

    先进入 prepareRefresh()方法:

    
    /**
     * Prepare this context for refreshing, setting its startup date and
     * active flag as well as performing any initialization of property sources.
     */
    protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();//设置容器启动时间
        this.closed.set(false);//容器关闭标志,是否关闭?
        this.active.set(true);//容器激活标志,是否激活?
        
        if (logger.isInfoEnabled()) {//运行到这里,控制台就会打印当前容器的信息
            logger.info("Refreshing " + this);
        }
    
        // 空方法,由子类覆盖实现,初始化容器上下文中的property文件
        initPropertySources();
    
        //验证标记为必需的所有属性均可解析,请参阅ConfigurablePropertyResolver#setRequiredProperties
        getEnvironment().validateRequiredProperties();
    
        //允许收集早期的ApplicationEvents,一旦多播器可用,即可发布...
        this.earlyApplicationEvents = new LinkedHashSetApplicationEvent();
    }
    

    控制台输出:

    
    三月 22, 2018 4:21:13 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
    信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@96532d6: startup date [Thu Mar 22 16:21:09 CST 2018]; root of context hierarchy
    

    第九步:

    进入 obtainFreshBeanFactory()方法:

    
    /**
     * 告诉子类刷新内部bean工厂(子类是指AbstractApplicationContext的子类,我们使用的是ClassPathXmlApplicationContext)
     * Tell the subclass to refresh the internal bean factory.
     */
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();//刷新Bean工厂,如果已经存在Bean工厂,那就关闭并销毁,再创建一个新的bean工厂
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();//获取新创建的Bean工厂
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);//控制台打印
        }
        return beanFactory;
    }
    

    进入 refreshBeanFactory()方法:

    
    /**
     * 该实现执行该上下文的基础Bean工厂的实际刷新,关闭以前的Bean工厂(如果有的话)以及为该上下文的生命周期的下一阶段初始化新鲜的Bean工厂。
     * This implementation performs an actual refresh of this context's underlying
     * bean factory, shutting down the previous bean factory (if any) and
     * initializing a fresh bean factory for the next phase of the context's lifecycle.
     */
    @Override
    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {//如果已有bean工厂
            destroyBeans();//销毁
            closeBeanFactory();//关闭
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();//创建一个新的bean工厂
            beanFactory.setSerializationId(getId());//为序列化目的指定一个id,如果需要,可以将此BeanFactory从此id反序列化回BeanFactory对象。
            //定制容器,设置启动参数(bean可覆盖、循环引用),开启注解自动装配
            customizeBeanFactory(beanFactory);
            ////将所有BeanDefinition载入beanFactory中,此处依旧是模板方法,具体由子类实现
            loadBeanDefinitions(beanFactory);
            //beanFactory同步赋值
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }
    

    总结:这一步主要的工作就是判断刷新容器前是否已经有 beanfactory 存在,如果有,那么就销毁旧的 beanfactory, 那么就销毁掉并且创建一个新的 beanfactory 返回给容器,同时将 xml 文件的 BeanDefinition注册到 beanfactory 中。 如果不太清楚可以回过头看看我们的第三节第5点内容

    第十步:

    进入第九步的 loadBeanDefinitions(beanFactory)方法中去 take a look:

    
    /**
     * 使用XmlBeanDefinitionReader来加载beandefnition,之前说过使用reader机制加载Resource资源变为BeanDefinition对象
     * Loads the bean definitions via an XmlBeanDefinitionReader.
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     * @see #initBeanDefinitionReader
     * @see #loadBeanDefinitions
     */
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 创建XmlBeanDefinitionReader对象
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
        // 使用当前上下文Enviroment中的Resource配置beanDefinitionReader,因为beanDefinitionReader要将Resource解析成BeanDefinition嘛!
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    
        //初始化这个reader
        initBeanDefinitionReader(beanDefinitionReader);
        //将beandefinition注册到工厂中(这一步就是将bean保存到Map中)
        loadBeanDefinitions(beanDefinitionReader);
    }
    

    控制台输出:

    
    三月 22, 2018 5:09:40 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
    信息: Loading XML bean definitions from class path resource [applicationContext.xml]
    

    第十一步:

    进入 prepareBeanFactory(beanFactory)方法:

    
    //设置bean类加载器
    //设置Spring语言表达式(SpEL)解析器
    //扫描ApplicationContextAware bean
    //注册类加载期类型切面织入(AOP)LoadTimeWeaver
    //为各种加载进入beanFactory的bean配置默认环境
    

    第十二步:

    postProcessBeanFactory(beanFactory)方法:

    postProcessBeanFactory同样作为一个模板方法,由子类来提供具体的实现,子类可以有自己的特殊对 BeanDefinition后处理方法,即子类可以在这对前面生成的 BeanDefinition,即 bean的元数据再处理。比如修改某个 bean id/name属性、 scope属性、 lazy-init属性等。

    第十三步:

    invokeBeanFactoryPostProcessors(beanFactory)方法:

    该方法调用所有的 BeanFactoryPostProcessor,它是一个接口,实现了此接口的类需重写 postProcessBeanFactory()这个方法,可以看出该方法跟第十二步的方法是一样的,只不过作为接口,更多的是提供给开发者来对生成的 BeanDefinition做处理,由开发者提供处理逻辑。

    第十四步:

    其余剩下的方法基本都是像 初始化消息处理源, 初始化容器事件, 注册bean监听器到事件传播器上,最后完成容器刷新。

    五、总结

    恭喜我,我终于写完了,同样也恭喜你,你也阅读完了。

    我很佩服我自己能花这么长时间进行总结发布,之所以要进行总结,那是因为小编还是赞同 好记性不如烂笔头的说法。

    你不记,你过阵子就会忘记,你若记录,你过阵子也会忘记!区别在于忘记了,可以回过头在很短的时间内进行回忆,查漏补缺,减少学习成本。

    再者,我认为我分析的还不是完美的,缺陷很多,因此我将我写的所有文章发布出来和大家探讨交流,汕头大学有校训说得非常地好,那就是说之知识是用来共享的,因为共享了,知识才能承前启后。

    现在再梳理一下 Spring 初始化过程:

  • 1、首先初始化上下文,生成`ClassPathXmlApplicationContext`对象,在获取`resourcePatternResolver`对象将`xml`解析成`Resource`对象。
  • 2、利用 1 生成的 context、resource 初始化工厂,并将 resource 解析成 beandefinition, 再将 beandefinition 注册到 beanfactory 中。
  • 朋友们,发现毛病,请评论告诉小编,一起交流一起交流!

    作者:拥抱心中的梦想
    juejin.im/post/5ab30714f265da237b21fbcc

    与其在网上拼命找题?** 不如马上关注我们~**

    【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~

    原文始发于微信公众号(Java面试题精选):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    转载声明:转载请注明出处,本技术博客是本人原创文章

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~


     上一篇
    【131期】面试必会之ArrayList源码分析&手写ArrayList 【131期】面试必会之ArrayList源码分析&手写ArrayList
    点击上方“Java面试题精选”,关注公众号 面试刷图,查缺补漏 号外:往期面试题,10篇为一个单位归置到本公众号菜单栏-面试题,有需要的欢迎翻阅 阶段汇总集合: 注:本文所有方法和示例基于jdk1.8 简介 ArrayList是我们开发中
    2021-04-05
    下一篇 
    【134期】面试官——你知道 Redis 内部是怎么实现它的字符串的么? 【134期】面试官——你知道 Redis 内部是怎么实现它的字符串的么?
    点击上方“Java面试题精选”,关注公众号 面试刷图,查缺补漏 号外:往期面试题,10篇为一个单位归置到本公众号菜单栏-面试题,有需要的欢迎翻阅 阶段汇总集合: 本人在找工作面试时在Redis相关问题上可栽了跟头。在面试前按常规套路准备了一
    2021-04-05