读spring源码记录(四)--还是蛮多需要学习的,多读注释

“当时年少春衫薄,骑马倚斜桥,满楼红袖招”
常常有人沉溺于回忆之中,但是我个人觉得还是要向前看,做好眼前的事才是对自己最大的负责,“来世不可待,往事不可追。”

一、我阅读源码前参照书籍

相信阅读源码的人,应该都查过相关的书籍,我在阅读源码的时候,我查看了一下相关的书籍,业界好像常常有人推荐《spring源码深度解析》这本书,我也看了一半,开始阅读源码,这本书从BeanFactory入手,先从解析配置文件开始,讲XmlBeanFactory这个类,我依葫芦画瓢,发现这个类已经不推荐使用了。
@Deprecated:若某类或某方法加上该注解之后,表示此方法或类不再建议使用,调用时也会出现删除线,但并不代表不能用,只是说,不推荐使用,因为还有更好的方法可以调用。

XmlBeanFactory不推荐使用

还有一本书《Spring技术内幕》,这个本书也应该看spring源码的朋友,可能会看的一本书。

二、阅读源码前自己的理解与想法

一开始到这个地方,不知道该怎么继续看下去了,但是细细想来,按照我们自己的理解,首先,我的理解是(个人理解,错误的地方请大家指正),任何项目启动的时候,第一件事是干什么咧,我认为是读取配置,讲项目需要的相关的类加载到内存中,依此类推,spring最开始学习的时候,大家都是先从xml配置beans开始,那么启动的时候,spring是不是也应该先把xml中的配置读取到,然后按照配置,将相关的bean放到容器中,依照设定好的流程,完善上下文的其他部分,也就是ApplicationContext。
我先把自己的理解猜想写下来,再阅读源码的过程,一点点修正我的想法。

三、开始证实

既然我认为上下文(ApplicationContext)是这个源码阅读的关键,那么我就从构建一个上下文(ApplicationContext)开始。

package com.laoye.test;

import com.laoye.spring.beans.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeansTest {

    @Test
    public void testStudent(){
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml");
        Student student = context.getBean("student",Student.class);
        System.out.println(student.getName());
    }
}

代码如上,具体的如何构建spring阅读源码环境以及增加一个测试模块,我已经在记录二中介绍过了,我就不过多介绍了。
总共三行代码,其中第一行,读取xml配置,生成上下文,可以看看ApplicationContext和ClassPathXmlApplicationContext之间的关系:


ClassPathXmlApplicationContext

首先走进第一行代码,就是进入ClassPathXmlApplicationContext的构造器中,构造器代码如下:

/**
     * Create a new ClassPathXmlApplicationContext, loading the definitions
     * from the given XML file and automatically refreshing the context.
     * @param configLocation resource location
     * @throws BeansException if context creation failed
     */
    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

首先看注解中的介绍这个构造器要做什么事:创建新的ClassPathXmlApplicationContext,加载xml文件中的定义并自动刷新上下文。那么实际怎么做的,向下走,代码如下:

/**
     * Create a new ClassPathXmlApplicationContext with the given parent,
     * loading the definitions from the given XML files.
     * @param configLocations array of resource locations
     * @param refresh whether to automatically refresh the context,
     * loading all bean definitions and creating all singletons.
     * Alternatively, call refresh manually after further configuring the context.
     * @param parent the parent context
     * @throws BeansException if context creation failed
     * @see #refresh()
     */
    public ClassPathXmlApplicationContext(
            String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

其中的configLocations是我们写入的参数classpath*:application.xml ,refresh为true,parent为null,当然在这里最好也读一下注释内容,前半部分跟上面看的内容差不多,多了一个内容loading all bean definitions and creating all singletons.Alternatively, call refresh manually after further configuring the context.(加载所有定义的bean,或者在进一步配置上下文之后手动调用refresh)。
代码中super(parent);这句代码,Java中子类的构造器中必须调用父类的构造器,如果父类没有无参的构造器,则必须显式调用父类的构造器,就是使用supper,并且是写到第一行,相当于添加父类的引用,这种显式调用一直到类AbstractApplicationContext,可以由上面提供的类的继承图可以看到ClassPathXmlApplicationContext向上跨过四层到AbstractApplicationContext,AbstractApplicationContext中使用的代码如下:

/**
     * Create a new AbstractApplicationContext with the given parent context.
     * @param parent the parent context
     */
    public AbstractApplicationContext(@Nullable ApplicationContext parent) {
        this();
        setParent(parent);
    }

按照习惯,先读注释,再看代码,下面是两行代码分别调用的方法:
this()指向AbstractApplicationContext的构造方法

/**
     * Create a new AbstractApplicationContext with no parent.
     */
public AbstractApplicationContext() {
        this.resourcePatternResolver = getResourcePatternResolver();
    }

这个方法构造器内生成了一个资源解析器,可以走进去看一下,就是new了一个资源解析器对象PathMatchingResourcePatternResolver(它实现了接口ResourcePatternResolver)。不过记住这个对象,在后面的源码中会用到,不然到时看代码的时候一头雾水。
setParent(parent)方法的代码:

/**
     * Set the parent of this application context.
     * <p>The parent {@linkplain ApplicationContext#getEnvironment() environment} is
     * {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with
     * this (child) application context environment if the parent is non-{@code null} and
     * its environment is an instance of {@link ConfigurableEnvironment}.
     * @see ConfigurableEnvironment#merge(ConfigurableEnvironment)
     */
    @Override
    public void setParent(@Nullable ApplicationContext parent) {
        this.parent = parent;
        if (parent != null) {
            Environment parentEnvironment = parent.getEnvironment();
            if (parentEnvironment instanceof ConfigurableEnvironment) {
                getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
            }
        }
    }

应该可以知道,参数传进来是null,所以代码就很好懂。
显式的supper()方法到这里就是尽头,接下来回到ClassPathXmlApplicationContext构造器,看第二行代码 setConfigLocations(configLocations);走到方法里面去看

/**
   * Set the config locations for this application context.
   * <p>If not set, the implementation may use a default as appropriate.
   */
  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] = resolvePath(locations[i]).trim();
          }
      }
      else {
          this.configLocations = null;
      }
  }

我们传的参数locations为classpath*:application.xml,代码简单,configLocations是一个String数组,值得注意的是,setConfigLocations这个方法,在类AbstractRefreshableConfigApplicationContext中,有上面的继承图我们可以知道,ClassPathXmlApplicationContext向上两层就是该类。
ClassPathXmlApplicationContext构造器中剩下的就是整个方法的重中之重,refresh()方法(boolean变量refresh传的true,所以refresh是必须调用的),
走到refresh()方法里:

@Override
    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) {
                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();
            }
        }
    }

这里我们可以看到,spring的整体风格的一个鲜明的例子,我们写代码也应该学习这种方式,整个方法步骤分明,每一个步骤都有说明,组合各个基础方法,并且异常处理非常的好,我个人认为这一个模版方法,值得深入了解。
由于这个是接口方法的实现,所以整个方法的注释在上层接口方法上,这个方法到底在干一个什么事,可以从注解中了解到:
Load or refresh the persistent representation of the configuration,which might an XML file, properties file, or relational database schema.As this is a startup method, it should destroy already created singletons if it fails, to avoid dangling resources. In other words, after invocation of that method, either all or no singletons at all should be instantiated.
(加载或刷新配置的持久表示形式,这可能是XML文件、属性文件或关系数据库。由于这是一种启动方法,因此如果失败,它应该销毁已创建的单例,以避免资源空置。换句话说,在调用该方法之后,要么全部实例化,要么根本不实例化单例。)
现在再来看这个方法,先读每一行代码上的注解,可以看到步骤:
1.Prepare this context for refreshing.(准备上下文的刷新)
2.Tell the subclass to refresh the internal bean factory.(通知子类刷新内部的bean factory)
3.Prepare the bean factory for use in this context.(准备上下文使用的bean factory)
4.Allows post-processing of the bean factory in context subclasses.(在上下文子类中,允许bean factory的后处理)
5.Invoke factory processors registered as beans in the context.(在上下文中调用注册为bean的工厂处理器)
6.Register bean processors that intercept bean creation.(注册拦截bean创建的bean处理器。)
7.Initialize message source for this context.(为此上下文初始化消息源。)
8.Initialize event multicaster for this context.(初始化上下文中事件广播。)
9.Initialize other special beans in specific context subclasses.(初始化特定上下文子类中的其他特殊bean。)
10.Check for listener beans and register them.(检查监听bean并注册。)
11.Instantiate all remaining (non-lazy-init) singletons.(实例化(非延迟加载)剩下的单例)
12.Last step: publish corresponding event.(最后一步:发布对应的事件)
翻译仅供参考
说明一下:lazy-init这个配置只针对scope为singleton的bean,延迟加载 ,设置为lazy的bean将不会在ApplicationContext启动时提前被实例化,而是在第一次向容器通过getBean索取bean时实例化的。这种懒加载的方式,也是降低启动时间的一种手段。
我会一点点慢慢记录,争取每一篇文章不会太长也不会太短,太长看到一半就不想看了,太短说不了什么内容。会在下一篇开始详细介绍每一个步骤。

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