源代码计划: Spring 5.6 源码解析

源代码计划: Spring 5.6 源码解析

该版本是以 Spring 5.6 为基础,从 bean 的创建流程、AOP 的核心对象创建、事务的创建加载整个 spring 框架的深度解析。

源代码计划: Spring 源码深度解析 倾向于以书本为线索的不同之处在于,该版本更多的是案例的演示与 debug 的整个流程。

前言

​ 在之前的关于 spring 源码的另一篇 随笔 当中,以 <spring 源码深度解析> 一书为开篇,跟随章节内容研究过关于 spring 3.2 版本最原始的容器启动加载而原型。

java 复制代码
@SuppressWarnings("deprecation")
public class BeanFactoryTest {
	@Test
	public void testSimpleLoad(){
		BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
		MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");
		System.out.println(myTestBean.getTestStr());
	}
}

​ 在 5.6 版本当中笔者换另一种资源加载的方式进行启动,研究继承实现关系。

java 复制代码
public class Main {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("application-peppa.xml");
		Student bean = application.getBean(Student.class);
		System.out.println(bean.getName());
	}
}

​ 在前的 3.2 版本开篇曾提到过 spring 的核心功能有两个大类。 XmlBeanDefinitionReader DefaultListableBeanFactory ,而中前者用于加载解析 XML 文件而后者用于创建注册 Bean 。那就看看,ClassPathXmlApplicationContext 具体是怎么实现 解析注册 的呢?

​ 从图中的 ClassPathXmlApplicationContext 基础关系类图中可以看到,AbstractApplicationContext 是一个绑定解析配置文件与容器实例化的实现类。

XML 配置文件的加载

经过对 ClassPathXmlApplicationCintext 构造方法的重载调用,直到 super(parent) 处对一系列父类的构造方法进行调用。直到 AbstartctApplicationContext 的构造方法,在一系列的父类构造的调用当中有大量的初始化变量。这些变量都是可以关注的。

java 复制代码
public ClassPathXmlApplicationContext(
        String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
        throws BeansException {
    // 调用父类的构造方法,进行相关的对象创建的初始化等操作(对父类的一些构造方法不要忽略,会有一些额外的属性填充工作)
    super(parent);
    setConfigLocations(configLocations); // 设置应用程序上下文的资源配置路径 ${jdbc.url},${jdbc.username} 解析路径
    if (refresh) {
        refresh();
    }
}
java 复制代码
/**
 * Create a new AbstractApplicationContext with no parent.
 */
public AbstractApplicationContext() { // 用于解析当前系统的资源【xml,配置文件都属于资源】
    this.resourcePatternResolver = getResourcePatternResolver(); // 创建资源模式处理器(refresh()方法就在此类当中)
}
java 复制代码
protected ResourcePatternResolver getResourcePatternResolver() {
    // 创建一个资源模式解析器 (其实就是用于解析 xml 配置文件)
    return new PathMatchingResourcePatternResolver(this);
}
java 复制代码
public void setParent(@Nullable ApplicationContext parent) {
    this.parent = parent;
    if (parent != null) { // 在当前的 spring 项目当中,看不到【父子容器】的概念,如果进入到 spring-mvc 的时候就会出现父子容器
        Environment parentEnvironment = parent.getEnvironment(); // 如果父容器不为空,获取父容器的环境对象
        if (parentEnvironment instanceof ConfigurableEnvironment) { // 如果当前的环境对象是一个可配置的环境对象
            getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); // 进行相关的一个合并工作
        }
    }
}

​ 这里的 AbstartApplicationContext 的构造方法当中的 setParent 方法用于设置父类的处理父类的容器,但是在当前的 Spring 项目当中是没有父类的容器的。因此这里 this.parent 也就为 null 。

​ 在返回到 AbstarctXmlApplicationContext 类的属性初始化当中有一个 validating 变量,该变量的属性值为 true 。而该变量的主要作用是设置 xml 加载的配置文件内部的 验证模式 默认是 XSD。而关于 XML 文件的验证加载方式在之前的 源代码计划: Spring 源码深度解析 中同样也提到过,其中提到关于 手动指定自动验证

java 复制代码
private boolean validating = true; // 设置 xml 文件的验证标志, 默认是 true [验证配置文件加载的表示 xsd ?]
java 复制代码
/**
 * Initialize the bean definition reader used for loading the bean
 * definitions of this context. Default implementation is empty.
 * <p>Can be overridden in subclasses, e.g. for turning off XML validation
 * or using a different XmlBeanDefinitionParser implementation.
 * @param reader the bean definition reader used by this context
 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass
 */
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
    reader.setValidating(this.validating);
}
java 复制代码
/**
* Set whether to use XML validation. Default is {@code true}.
* <p>This method switches namespace awareness on if validation is turned off,
* in order to still process schema namespaces properly in such a scenario.
* @see #setValidationMode
* @see #setNamespaceAware
*/
public void setValidating(boolean validating) {
	this.validationMode = (validating ? VALIDATION_AUTO : VALIDATION_NONE);
	this.namespaceAware = !validating;
}

继续执行回到 AbstartctApplicationContext 的子类 ClassPathXmlApplicationContext 类当中的 setConfigLocations 方法当中。

java 复制代码
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++) { // 这里可能有多个配置文件路径
            // 思考【我们有没有在配置文件文件中写过 ${jdbc.url},${jdbc.username}】 既然名字是这样解析的,那么 xml 配置文件中的 是不是也是这样的呢 ? 【功能复用】
            this.configLocations[i] = resolvePath(locations[i]).trim(); // 解析给定的路径【比如解析 spring-${username}.xml这方式】
        }
    }
    else {
        this.configLocations = null;
    }
}

​ 该方法的主要作用就是将我们通过 ClassPathXmlApplicationContext( "application-peppa.xml") 传入的 xml 多配置文件处理成一个 String 类的配置文件数组。

​ 但其中还有另外一个细节是关于 resolvePath 方法的。这里的 resolvePath 方法的任务完成通配符进行解析,例如: 将 application-${user.name}.xml 解析成 application-peppa.xml 。

java 复制代码
protected String resolvePath(String path) {
    // getEnvironment() 第一个获取当前系统环境变量的值,为替换做准备
    // resolveRequiredPlaceholders(path) 解析必要的占位符[${xxxx}],进行替换
    return getEnvironment().resolveRequiredPlaceholders(path);
}

​ 首先, 通配符中的变量属性都是来自于当前的环境,因此必定是先要通过 getEnviroment() 获取当前的运行环境,后面的 resolveRequiredPlaceholders(path) 方法进行具体的解析 。

java 复制代码
public ConfigurableEnvironment getEnvironment() {
    if (this.environment == null) { // 首次进入当前的环境为空
        this.environment = createEnvironment();
    }
    return this.environment;
}
java 复制代码
protected ConfigurableEnvironment createEnvironment() {
    return new StandardEnvironment();
}
java 复制代码
public AbstractEnvironment() {
    // 在创建环境对象的时候,调用父类的构造方法。定制化属性资源,在父类当中是一个空的实现, 在调用的子类的时候具体实现。
    customizePropertySources(this.propertySources);
}

​ 最终在 getSystemProperties()etSystemEnvironment() 方法当中获取系统的属性。因此在进行对象创建的时候,父类初始化的时候就已经完成对于环境的加载创建。

java 复制代码
protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(
            new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); // 获取系统属性
    propertySources.addLast(
            new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));// 获取系统环境
}

XML 配置文件的路径解析

回到具体的解析方法 resolveRequiredPlaceholdes 当中

java 复制代码
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
        // 占位符的助手 [前缀 ${ 后缀 } 分隔符 :]
        this.strictHelper = createPlaceholderHelper(false);
    }
    return doResolvePlaceholders(text, this.strictHelper); // 开始解析
}
java 复制代码
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
    return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
            this.valueSeparator, ignoreUnresolvablePlaceholders);
}

获取对象属性当中的解析占位符的具体符号,通过 doResolvePlaceolders 方法完成具体的解析工作。

java 复制代码
protected String parseStringValue(
        String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {

    int startIndex = value.indexOf(this.placeholderPrefix); // 获取占位符前缀 [${] 的下标位置
    if (startIndex == -1) {
        return value;
    }

    StringBuilder result = new StringBuilder(value);
    while (startIndex != -1) {
        int endIndex = findPlaceholderEndIndex(result, startIndex); // 查找占位符结束的下标位置
        if (endIndex != -1) {
            String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); // 截取占位符中的属性
            String originalPlaceholder = placeholder;
            if (visitedPlaceholders == null) {
                visitedPlaceholders = new HashSet<>(4);
            }
            if (!visitedPlaceholders.add(originalPlaceholder)) {
                throw new IllegalArgumentException(
                        "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
            }
            // Recursive invocation, parsing placeholders contained in the placeholder key.
            placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // 递归调用截取第一个占位符包含的字符串可能会包含  os-${username}}
            // Now obtain the value for the fully resolved key...
            String propVal = placeholderResolver.resolvePlaceholder(placeholder); // 从系统中获取对应的解析到的属性
            if (propVal == null && this.valueSeparator != null) {
                int separatorIndex = placeholder.indexOf(this.valueSeparator);
                if (separatorIndex != -1) {
                    String actualPlaceholder = placeholder.substring(0, separatorIndex);
                    String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                    propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                    if (propVal == null) {
                        propVal = defaultValue;
                    }
                }
            }
            if (propVal != null) {
                // Recursive invocation, parsing placeholders contained in the
                // previously resolved placeholder value.
                propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); // 解析到占位符
                result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); //占位符替换
                if (logger.isTraceEnabled()) {
                    logger.trace("Resolved placeholder '" + placeholder + "'");
                }
                startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
            }
            else if (this.ignoreUnresolvablePlaceholders) {
                // Proceed with unprocessed value.
                startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
            }
            else {
                throw new IllegalArgumentException("Could not resolve placeholder '" +
                        placeholder + "'" + " in value \"" + value + "\"");
            }
            //删除集合当中的解析元素 spring-${username}.xml
            visitedPlaceholders.remove(originalPlaceholder);
        }
        else {
            startIndex = -1;
        }
    }
    return result.toString();
}

关于该方法当中有几个关键的调用这里提一下:

  • findPlaceholderEndIndex () 该方法主要是找到结束的匹配索引下标位置

    java 复制代码
    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
        int index = startIndex + this.placeholderPrefix.length(); // 占位符开始的位置 + 前缀索引的长度
        int withinNestedPlaceholder = 0;
        while (index < buf.length()) {
            if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { // 判断当前的下一个字符是否出现了后缀 [}]
                if (withinNestedPlaceholder > 0) {
                    withinNestedPlaceholder--;
                    index = index + this.placeholderSuffix.length();
                }
                else {
                    return index;
                }
            }
            else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) { // 如果没有出现则判断是否出现新的占位符 [{] ,该种情况主要用于判断是否出现类似于 [spring-${os-${username}}.xml] 占位符嵌套
                withinNestedPlaceholder++;
                index = index + this.simplePrefix.length();
            }
            else {
                index++;
            }
        }
        return -1;
    }
  • parseStringValue() 这里有一个递归的调用,这主要用于处理 {user.name-{user.host}} 这种通配符中嵌套通配符的方式的。

  • placeholderResolver.resolvePlaceholder(placeholder); 从系统当中获取指定的属性值

而这里有进行了一次通配符解析的调用,主要用于处理,系统环境属性当中是否存在通配符的这种情况。

最终删除原有配置文件集合当中的为解析前的文件。

refresh 方法

完成了关于 XML 配置文件的路径加载后,开始进行核心的处理 refresh 方法。

java 复制代码
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh(); // 准备 Refresh 刷新 (前戏)

        // Tell the subclass to refresh the internal bean factory.
        // 创建容器对象: DefaultListableBeanFactory
        // 加载 xml 配置文件的属性到当前工程当中, 最重要的就是 BeanDefinition 的属性填充
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory); //准备 BeanFactory Bean工厂 (前戏)

        try {
            // 子类覆盖方法做的处理,此处我们自己一般不做任何的工作,但是可以查到 web 中的代码,是由具体的实现的
            // Allows post-processing of the bean factory in context subclasses. (子类扩展:后置增强处理器)
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context. (实例化并且执行已经注册的 BFPP beans )
            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();
        }
    }
}

refresh 当中一共有 12 个核心方法分别是:

  • prepareRefresh() // 完成刷新前的准备工作

scss 复制代码
    	obtainFreshBeanFactory(); //创建 DefaultListableBeanFactory 容器对象,加载 XML 配置文件到当前工具,完成 BeanDefinition 的属性填充
  • prepareBeanFactory(beanFactory); //准备 BeanFactory 工厂

  • postProcessBeanFactory(beanFactory); //后置增强处理器,子类具体实现

  • invokeBeanFactoryPostProcessors(beanFactory); //实例化以及注册的 BFPP

  • registerBeanPostProcessors(beanFactory); // 注册具体的 Bean

  • initMessageSource(); //国际化处理

  • initApplicationEventMulticaster(); //初始化时间监听器

  • onRefresh(); //初始化特殊的 Bean 空实现

  • registerListeners(); //注册监听器

  • finishBeanFactoryInitialization(beanFactory); // 实例化剩下的非懒加载对象

  • finishRefresh(); //完成整体的刷新

prepareRefresh 容器的刷新工作

相关推荐
安的列斯凯奇27 分钟前
SpringBoot篇 单元测试 理论篇
spring boot·后端·单元测试
架构文摘JGWZ1 小时前
FastJson很快,有什么用?
后端·学习
BinaryBardC1 小时前
Swift语言的网络编程
开发语言·后端·golang
邓熙榆1 小时前
Haskell语言的正则表达式
开发语言·后端·golang
专职4 小时前
spring boot中实现手动分页
java·spring boot·后端
Ciderw4 小时前
Go中的三种锁
开发语言·c++·后端·golang·互斥锁·
m0_748246355 小时前
SpringBoot返回文件让前端下载的几种方式
前端·spring boot·后端
m0_748230445 小时前
创建一个Spring Boot项目
java·spring boot·后端
卿着飞翔5 小时前
Java面试题2025-Mysql
java·spring boot·后端
C++小厨神5 小时前
C#语言的学习路线
开发语言·后端·golang