Spring源码详细解析之Spring上下文启动(一)

先上一段我们都很熟悉的代码:
这里写图片描述

此次我们研究一下上下文启动Spring到底做了什么,以ClassPathXmlApplicationContext为例。
在开始我们的源码追踪旅行之前,我们先贴几张关系图(这对于我们后面理解很重要!!)
这些关系图只是一部分关系,我会不段更新
上下文部分关系图:
上下文部分关系图
Bean工厂关系图
环境关系图
资源解释器关系图

一、启动大体流程思路

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);//第一步,加载配置路径
        if (refresh) {
            refresh();//第二步
        }
    }

其中第二步细分为:

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // 准备上下文刷新
            prepareRefresh();

            // 告诉子类刷新bean工厂。
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // 准备bean工厂方便使用该上下文。
            prepareBeanFactory(beanFactory);

            try {
                // 允许在上下文子类中对bean工厂进行后处理。
                postProcessBeanFactory(beanFactory);

                // 后置处理器处理bean
                invokeBeanFactoryPostProcessors(beanFactory);

                // 注册拦截bean创建的bean处理器.
                registerBeanPostProcessors(beanFactory);

                // 初始化此上下文的消息源。
                initMessageSource();

                // 初始化此上下文的事件多播器。
                initApplicationEventMulticaster();

                // 在特定的上下文子类中初始化其他特殊的bean。
                onRefresh();

                // 检查监听器bean并注册它们。
                registerListeners();

                // 实例化所有剩下的(非惰性初始化)单例。
                finishBeanFactoryInitialization(beanFactory);

                // 发布相应的事件。
                finishRefresh();
            }

            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

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

                // Propagate exception to caller.
                throw ex;
            }
        }
    }

我们先来讲一下

①setConfigLocations(configLocations)

作用:设置配置路径
这个步骤是做了些什么:
1、ClassPathXmlApplicationContext调用父类AbstractRefreshableConfigApplicationContextsetConfigLocations(configLocations),代码如下:

public void setConfigLocations(String[] locations) {
                //locations是我们加载的application.xml,这个可以有多个,是一个String[]
        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;
        }
    }

resolvePathAbstractRefreshableConfigApplicationContext的一个私有方法,这个方法我们需要注意的是,this.getEnvironment()是该类在构造时自己new出来的StandardEnvironment(具体大家可以自己对照代码看,笔者这里尽可能提供详细路线)。StandardEnvironment类继承于AbstractEnvironment

protected String resolvePath(String path) {
        return this.getEnvironment().resolveRequiredPlaceholders(path);
    }

接下来我们进入resolveRequiredPlaceholders方法,这是AbstractEnvironment的公有方法。在这个方法里,我们需要注意的是this.propertyResolverthis.propertyResolverPropertySourcesPropertyResolver类型。

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        return this.propertyResolver.resolveRequiredPlaceholders(text);
    }

AbstractEnvironment抽象类的构造函数里对propertySources进行了初始化,再通过资源配置器构造出propertyResolver

ConfigurablePropertyResolver propertyResolver =
            new PropertySourcesPropertyResolver(this.propertySources);
public AbstractEnvironment() {
        String name = this.getClass().getSimpleName();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(format("Initializing new %s", name));
        }
                //该方法是一个空实现的方法,我们这里的StandardEnvironment对它进行了实现。
        this.customizePropertySources(this.propertySources);

        if (this.logger.isDebugEnabled()) {
            this.logger.debug(format(
                    "Initialized %s with PropertySources %s", name, this.propertySources));
        }
    }

那我们再来看看StandardEnvironment里到底对propertySources进行了哪些初始:

protected void customizePropertySources(MutablePropertySources propertySources) {
                //systemProperties
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        //systemEnvironment
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

我们现在知道了propertyResolver的获取,而resolveRequiredPlaceholders内部其实就是创建一个占位符解析器PropertyPlaceholderHelper(不清楚占位符解析器干嘛的,可以看我的上一篇文章),并进行占位符解析和置换。

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        if (strictHelper == null) {
            strictHelper = createPlaceholderHelper(false);
        }
        return doResolvePlaceholders(text, strictHelper);
    }

这里注意下面这个方法的getProperty(placeholderName)部分是由PropertySourcesPropertyResolver实现PropertyResolver接口而来的。而在这个方法里,getProperty实际就是从PropertySources里获取占位符对应的值。而PropertySources该资源实际就是上面我们提到的StandardEnvironment里面加载的2个系统资源。

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
        return helper.replacePlaceholders(text, new PlaceholderResolver() {
            public String resolvePlaceholder(String placeholderName) {
                return getProperty(placeholderName);
            }
        });
    }

说的有点乱,我总结一下,顺便放个test,大家可能就清晰了。第一步setConfigLocations(configLocations)说白了就是替换调path里的占位符。但是只能从系统属性和环境变量的配置文件中寻找占位符的替换。
例子如下:

package com.xxx.xxx.xxx.facade;

import com.alibaba.fastjson.JSONObject;
import com.xxx.xxx.xxx.service.identification.phone3item.ThreeItemVerifyService;
import com.xxx.xxx.xxx.service.lang.GenericService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.*;
import java.util.Properties;
import java.util.UUID;

public class TestSpring {
    @Test
    public void test() throws Exception{
        /**
         * 新增系统属性,方便测试
         */
        System.setProperty("test","applicationContext");
        System.getProperties().list(System.out);
        System.out.println("我设置的test占位符替换值:"+System.getProperty("test"));


        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("${test}.xml");
        GenericService genericService = (ThreeItemVerifyService)applicationContext.getBean("threeItemVerifyService");
        JSONObject json = new JSONObject();
        json.put("service", "suning.sfeicuss.identification.phone.3item.verify");
        json.put("version", "1.0");
        json.put("outBizNo", UUID.randomUUID());
        json.put("appCode", "xxx");
        json.put("phone", "xxx");
        json.put("idNo", "xxx");
        json.put("name", "xxx");
        genericService.execute(json);

    }
}

控制台结果:
这里写图片描述

我们开始分析第二步

②prepareRefresh()

作用:准备此上下文进行刷新,设置其启动日期和活动标志以及执行属性资源的任何初始化。

 protected void prepareRefresh() {
        //启动时间
        this.startupDate = System.currentTimeMillis();

        //加锁,防止,加状态
        synchronized (this.activeMonitor) {
            this.active = true;
        }

        if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        }

        // 在上下文环境中初始化任何占位符属性源
        initPropertySources();                   

        // 验证标记为必需的所有属性是可解析的
        // 请参阅ConfigurablePropertyResolver#setRequiredProperties
        this.environment.validateRequiredProperties();          
    }

这里我们需要注意的是initPropertySources这个方法是AbstractApplicationContext父类的一个空实现,ClassPathXmlApplicationContext继承关系中也没人实现它,所以对于ClassPathXmlApplicationContext来说就是空实现,不做任何操作。

③obtainFreshBeanFactory()

作用:初始化bean工厂

 protected final void refreshBeanFactory() throws BeansException {
        //查看是否已经存在bean工厂,存在则销毁bean,且关闭工厂
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //创建bean工厂
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            //为序列化目的指定一个id,允许这个BeanFactory,从此id反序列化回BeanFactory对象。
            beanFactory.setSerializationId(getId());
            //设置bean工厂属性
            customizeBeanFactory(beanFactory);
            //加载定义的bean
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }
  • createBeanFactory()该方法内部是用给定的父项创建一个新的DefaultListableBeanFactory,过程中给最上层是给AbstractBeanFactory初始化parentBeanFactory值,该字段是父bean工厂,用于bean继承支持。具体不在详细展开,也很清晰。代码如下:
 protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
    }
  • customizeBeanFactory()方法设置了”是否允许bean相同名称不同定义”、”是否自动尝试解决bean之间的循环引用”,最后又”为此BeanFactory设置一个自定义autowire候选解析器以供使用当决定一个bean定义是否应该被视为一个自动装配的候选人”,即声明了一个AutowireCandidateResolver(自动注解解析器):用来判断定义的bean是否需要被自动注入。代码如下:
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
        if (this.allowBeanDefinitionOverriding != null) {
            beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.allowCircularReferences != null) {
            beanFactory.setAllowCircularReferences(this.allowCircularReferences);
        }
        beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
    }
  • loadBeanDefinitions(beanFactory)ClassPathXmlApplicationContext的父类AbstractXmlApplicationContext里的一个方法,这个方法用来加载xml,过程挺复杂的,但是思路都差不多。这里简要地介绍一下。
    这里以application.xml的部分为例子:
    这里写图片描述

第一步,对location操作

  1. 对路径进行解析,包括是不是以classpath*:开头等。
  2. location字符串生成resource,如果是classpath:为起始的location生成ClassPathResource,如果是url类型的location生成UrlResource,如果是其他,生成ClassPathContextResource
    在生成resource的过程中,对path进行了 StringUtils.cleanPath
public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        //看前缀是不是classpath*:
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
            //// 加载出该路径下的所有配置文件,并将File构造成Resource
            if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                return findPathMatchingResources(locationPattern);
            }
            else {
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
            }
        }
        //如果不是classpath*:开头的话,
        else {
            // Only look for a pattern after a prefix here
            // (to not get fooled by a pattern symbol in a strange prefix).
            int prefixEnd = locationPattern.indexOf(":") + 1;
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                // a file pattern
                return findPathMatchingResources(locationPattern);
            }
            else {
                // 仅仅是一个简单的resource加载
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }

第二步:对resource操作

  1. 通过ClassPathResourcegetClassloader.getResourceAsStream获取流,再放入saxInputSource
  2. 再将InputSourceClassPathResource进行解析。
  3. DocumentBuilderFactory解析inputSource 获取xmldoc。获取docroot标签,看root标签是否包含profile。这里案例不包含。
  4. registerBeanDefinitions(doc, resource)解析, 解析根节点importaliasbeanbeans 这些标签, 比如,当发现import后,接着发现resourceresource标签的值是classpath*:beans-*.xml
    接下来将classpath*:beans-*.xml切开,现将classpath*:下面的所有File加载放入 File[]中,classpath*==getclassLoader.getresourcepath(),然后再将File[]里的文件用getPathMatcher().match()beans-*.xml做匹配,匹配成功的File放入 Set(File)中,再将每个配置文件构建成FileSystemResource,返回了一个Resource[],再递归循环解析Resource里面的每一个classpath*:下与beans-*.xml匹配成功的xml文件。
    这个过程中,代码量有点多,这里稍微附几张关键图:
    这里写图片描述
    这里写图片描述
    这里写图片描述

上面所说的,你们一定会说乱,没事,这里总结下写了一个加载的demo帮助我们理解,加载的过程。代码如下:

@Test
    public void test02() throws Exception{

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        logger.debug("加载前的bean工厂:{}",beanFactory.getBeanDefinitionNames());

        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        logger.debug("我们开始解析....applicationContext.xml");

        ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver();
        Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources("applicationContext.xml");
        logger.debug("resources:{}",resources);

        //循环加载resources
        for (Resource resources1:resources){
            logger.debug("第一个resource进来解析了:{}",resources1);
            EncodedResource encodedResource = new EncodedResource(resources1);

            //获取xml资源的流
            InputStream inputStream = encodedResource.getResource().getInputStream();
            InputSource inputSource = new InputSource(inputStream);

            //解析xml
            Document doc = new DefaultDocumentLoader().loadDocument(
                    inputSource, new ResourceEntityResolver(resourceLoader), new SimpleSaxErrorHandler(LogFactory.getLog(getClass())), 3, false);
            Element root = doc.getDocumentElement();
            logger.debug("applicationContext.xml的根标签是:{}",root);

            //加载resource
            xmlBeanDefinitionReader.registerBeanDefinitions(doc,resources1);
        }

        logger.debug("加载后的bean工厂:{}", Arrays.toString(beanFactory.getBeanDefinitionNames()));
    }

运行结果:
这里写图片描述
…………………………………中间日志省略…………………………………
这里写图片描述

到此篇幅已经很长啦,下次再接着写。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值