美文网首页Java高级开发
【Spring源码分析】Bean加载流程概览

【Spring源码分析】Bean加载流程概览

作者: 程序员夏天 | 来源:发表于2019-06-18 14:04 被阅读10次

    代码入口

    之前写文章都会啰啰嗦嗦一大堆再开始,进入【Spring源码分析】这个板块就直接切入正题了。

    很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已,Spring的加载过程相对是不太透明的,不太好去找加载的代码入口。

    下面有很简单的一段代码可以作为Spring代码加载的入口:

    1 ApplicationContext ac =newClassPathXmlApplicationContext("spring.xml"); 

    2 ac.getBean(XXX.class);

    ClassPathXmlApplicationContext用于加载CLASSPATH下的Spring配置文件,可以看到,第二行就已经可以获取到Bean的实例了,那么必然第一行就已经完成了对所有Bean实例的加载,因此可以通过ClassPathXmlApplicationContext作为入口。为了后面便于代码阅读,先给出一下ClassPathXmlApplicationContext这个类的继承关系:

    大致的继承关系是如上图所示的,由于版面的关系,没有继续画下去了,左下角的ApplicationContext应当还有一层继承关系,比较关键的一点是它是BeanFactory的子接口。

    最后声明一下,本文使用的Spring版本为3.0.7,比较老,使用这个版本纯粹是因为公司使用而已。

    ClassPathXmlApplicationContext存储内容

    为了更理解ApplicationContext,拿一个实例ClassPathXmlApplicationContext举例,看一下里面存储的内容,加深对ApplicationContext的认识,以表格形式展现:

    ClassPathXmlApplicationContext构造函数

    看下ClassPathXmlApplicationContext的构造函数:

    从第二段代码看,总共就做了三件事:

      1、super(parent)

        没什么太大的作用,设置一下父级ApplicationContext,这里是null

      2、setConfigLocations(configLocations)

        代码就不贴了,一看就知道,里面做了两件事情:

        (1)将指定的Spring配置文件的路径存储到本地

        (2)解析Spring配置文件路径中的${PlaceHolder}占位符,替换为系统变量中PlaceHolder对应的Value值,System本身就自带一些系统变量比如class.path、os.name、user.dir等,也可以通过System.setProperty()方法设置自己需要的系统变量

      3、refresh()

        这个就是整个Spring Bean加载的核心了,它是ClassPathXmlApplicationContext的父类AbstractApplicationContext的一个方法,顾名思义,用于刷新整个Spring上下文信息,定义了整个Spring上下文加载的流程。

    refresh方法

    上面已经说了,refresh()方法是整个Spring Bean加载的核心,因此看一下整个refresh()方法的定义:

    public void refresh() throws BeansException, IllegalStateException {

            synchronized (this.startupShutdownMonitor) {

                // Prepare this context for refreshing.

                prepareRefresh();

                // Tell the subclass to refresh the internal bean factory.

                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

                // Prepare the bean factory for use in this context.

                prepareBeanFactory(beanFactory);

                try {

                    // Allows post-processing of the bean factory in context subclasses.

                    postProcessBeanFactory(beanFactory);

                    // Invoke factory processors registered as beans in the context.

                    invokeBeanFactoryPostProcessors(beanFactory);

                    // Register bean processors that intercept bean creation.

                    registerBeanPostProcessors(beanFactory);

                    // Initialize message source for this context.

                    initMessageSource();

                    // Initialize event multicaster for this context.

                    initApplicationEventMulticaster();

                    // Initialize other special beans in specific context subclasses.

                    onRefresh();

                    // Check for listener beans and register them.

                    registerListeners();

                    // Instantiate all remaining (non-lazy-init) singletons.

                    finishBeanFactoryInitialization(beanFactory);

                    // Last step: publish corresponding event.

                    finishRefresh();

                }

                catch (BeansException ex) {

                    // Destroy already created singletons to avoid dangling resources.

                    destroyBeans();

                    // Reset 'active' flag.

                    cancelRefresh(ex);

                    // Propagate exception to caller.

                    throw ex;

                }

            }

        }

    每个子方法的功能之后一点一点再分析,首先refresh()方法有几点是值得我们学习的:

      1、方法是加锁的,这么做的原因是避免多线程同时刷新Spring上下文

      2、尽管加锁可以看到是针对整个方法体的,但是没有在方法前加synchronized关键字,而使用了对象锁startUpShutdownMonitor,这样做有两个好处:

    (1)refresh()方法和close()方法都使用了startUpShutdownMonitor对象锁加锁,这就保证了在调用refresh()方法的时候无法调用close()方法,反之亦然,避免了冲突

        (2)另外一个好处不在这个方法中体现,但是提一下,使用对象锁可以减小了同步的范围,只对不能并发的代码块进行加锁,提高了整体代码运行的效率

    3、方法里面使用了每个子方法定义了整个refresh()方法的流程,使得整个方法流程清晰易懂。这点是非常值得学习的,一个方法里面几十行甚至上百行代码写在一起,在我看来会有三个显著的问题:

        (1)扩展性降低。反过来讲,假使把流程定义为方法,子类可以继承父类,可以根据需要重写方法

        (2)代码可读性差。很简单的道理,看代码的人是愿意看一段500行的代码,还是愿意看10段50行的代码?

        (3)代码可维护性差。这点和上面的类似但又有不同,可维护性差的意思是,一段几百行的代码,功能点不明确,不易后人修改,可能会导致“牵一发而动全身”

    prepareRefresh方法

    下面挨个看refresh方法中的子方法,首先是prepareRefresh方法,看一下源码:

    这个方法功能比较简单,顾名思义,准备刷新Spring上下文,其功能注释上写了:

    1、设置一下刷新Spring上下文的开始时间

    2、将active标识位设置为true

    另外可以注意一下12行这句日志,这句日志打印了真正加载Spring上下文的Java类。

    obtainFreshBeanFactory方法

    obtainFreshBeanFactory方法的作用是获取刷新Spring上下文的Bean工厂,其代码实现为:

    其核心是第二行的refreshBeanFactory方法,这是一个抽象方法,有AbstractRefreshableApplicationContext和GenericApplicationContext这两个子类实现了这个方法,看一下上面ClassPathXmlApplicationContext的继承关系图即知,调用的应当是AbstractRefreshableApplicationContext中实现的refreshBeanFactory,其源码为:

    protected final void refreshBeanFactory() throws BeansException {

        if (hasBeanFactory()) {

            destroyBeans();

            closeBeanFactory();

        }

        try {

            DefaultListableBeanFactory beanFactory = createBeanFactory();

            beanFactory.setSerializationId(getId());

            customizeBeanFactory(beanFactory);

            loadBeanDefinitions(beanFactory);

            synchronized (this.beanFactoryMonitor) {

                this.beanFactory = beanFactory;

            }

        }

        catch (IOException ex) {

            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);

        }

    }

    这段代码的核心是第7行,这行点出了DefaultListableBeanFactory这个类,这个类是构造Bean的核心类,这个类的功能会在下一篇文章中详细解读,首先给出DefaultListableBeanFactory的继承关系图:

    AbstractAutowireCapableBeanFactory这个类的继承层次比较深,版面有限,就没有继续画下去了,本图基本上清楚地展示了DefaultListableBeanFactory的层次结构。

    为了更清晰地说明DefaultListableBeanFactory的作用,列举一下DefaultListableBeanFactory中存储的一些重要对象及对象中的内容,DefaultListableBeanFactory基本就是操作这些对象,以表格形式说明:

    每天更新 ,喜欢的朋友麻烦转发关注下, 谢谢啦!!!

    相关文章

      网友评论

        本文标题:【Spring源码分析】Bean加载流程概览

        本文链接:https://www.haomeiwen.com/subject/idkfqctx.html