源代码计划: 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 容器的刷新工作

相关推荐
啦啦右一32 分钟前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien33 分钟前
Spring Boot常用注解
java·spring boot·后端
盛派网络小助手2 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
∝请叫*我简单先生3 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl
zquwei4 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring
dessler4 小时前
Docker-run命令详细讲解
linux·运维·后端·docker
Q_19284999065 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
ZSYP-S5 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
Yuan_o_6 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端