第二部分-Spring容器的基本实现(一)- spring的简单使用以及源码中设置配置路径和标准环境

版权声明:转载请注明出处,谢谢合作! https://blog.csdn.net/u011709128/article/details/80849398

Spring容器的基本实现

第二部分-Spring容器的基本实现(一)- spring的简单使用以及源码中设置配置路径和标准环境
在开始分析源码之前,我们来回顾一下Spring的最简单的用法,我们来构建一个小demo


Spring中最简单的用法

首先我们要构建一个maven项目

构建一个空的java项目,并引入spring的jar包,如下图:

这里写图片描述

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
        </dependency>

然后构建两个java类,一个xml配置文件,如下:

package test;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author : alex
 * @version :1.0.0
 * @Date : create by 2018/6/28 17:30
 * @description :一个简单加载bean的小例子
 * @note 注意事项
 */
public class BeanFactoryTest {

    //使用junit做测试
    @Test
    public void test(){
//        XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("BeanFactoryTest.xml"));//老版spring加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("BeanFactoryTest.xml");
        MyTestBean bean = (MyTestBean) applicationContext.getBean("myTestBean");
        System.out.println(bean.getTestStr());

    }
}
package test;

/**
 * @author : alex
 * @version :1.0.0
 * @Date : create by 2018/6/28 17:33
 * @description :bean
 * @note 注意事项
 */
public class MyTestBean {

    private String testStr = "testStr";

    public String getTestStr() {
        return testStr;
    }

    public void setTestStr(String testStr) {
        this.testStr = testStr;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
     http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--bean的原始配置文件-->
    <bean id="myTestBean" class="test.MyTestBean"></bean>

</beans>

构建完毕如下图:
这里写图片描述

构建完毕,运行一下:

这里写图片描述

一个最简单的使用就结束了,是不是觉得很简单,别着急,我们来看看BeanFactoryTest里面的ClassPathXmlApplicationContext类图。

类图

这里写图片描述

idea快捷键查看类图方法:
ctrl+鼠标点击进入ClassPathXmlApplicationContext
然后ctrl+alt+shift+U

扫描二维码关注公众号,回复: 3274499 查看本文章

类图中的类解析

类名称 说明解释
ClassPathXmlApplicationContext 独立XML应用程序上下文,从类路径中获取上下文定义文件,将纯路径解释为包含程序包路径的类路径资源名称
AbstractXmlApplicationContext ApplicationContext实现的便捷基类,从包含由XmlBeanDefinitionReader理解的bean定义的XML文档中绘制配置。
AbstractRefreshableConfigApplicationContext AbstractRefreshableApplicationContext子类,用于添加指定配置位置的常用处理。 作为基于XML的应用程序上下文实现的基类,如ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,以及XmlWebApplicationContext。
AbstractRefreshableApplicationContext ApplicationContext实现的基类,应该支持多次调用AbstractApplicationContext.refresh(),每次创建一个新的内部bean工厂实例。通常(但不一定),这样的上下文将由一组配置位置驱动,以从中加载bean定义。子类实现的唯一方法是loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory),它在每次刷新时被调用。具体实现应该将bean定义加载到给定的DefaultListableBeanFactory中,通常委托给一个或多个特定的bean定义读取器。
BeanNameAware 接口由想要知道其在bean工厂中的bean名称的bean实现。 请注意,通常不建议对象依赖于它的bean名称,因为这代表了对外部配置的潜在脆弱依赖性,以及对Spring API可能不必要的依赖。有关所有bean生命周期方法的列表,请参阅BeanFactory javadocs。
InitializingBean 接口由bean实现,当BeanFactory设置了它们的所有属性后需要做出反应:例如,执行自定义初始化,或仅检查是否已设置所有必需属性。实现InitializingBean的另一种方法是指定一个自定义的init方法,例如在一个XML bean定义中。 有关所有bean生命周期方法的列表,请参阅BeanFactory javadocs。
AbstractApplicationContext ApplicationContext的抽象实现,不要求用于配置的存储类型;只是实现通用上下文功能。使用模版方法设计模式,要求具体的子类来实现抽象方法。与普通的BeanFactory相比,ApplicationContext被假定检测在其内部bean工厂中中定义的特殊bean
Aware 标记超级接口,表示一个bean有资格通过一个回调式方法被Spring容器通知一个特定的框架对象。 实际的方法签名由各个子接口确定,但通常应由一个只接受一个参数的void返回方法组成。请注意,仅实现Aware不提供默认功能。 相反,处理必须显式完成,例如在BeanPostProcessor中。
ConfigurableApplicationContext SPI接口由大多数(如果不是全部)应用程序上下文实现。 除ApplicationContext接口中的应用程序上下文客户端方法外,还提供了配置应用程序上下文的工具。配置和生命周期方法封装在这里以避免使它们对ApplicationContext客户端代码显而易见。 目前的方法只能用于启动和关闭代码。
DefaultResourceLoader ResourceLoader接口的默认实现。 由ResourceEditor使用,并作为AbstractApplicationContext的基类。 也可以单独使用。如果位置值是URL,则返回UrlResource;如果是非URL路径或“classpath:”伪URL,则返回ClassPathResource。
Lifecycle 定义启动/停止生命周期控制方法的通用接口。典型的用例是控制异步处理。注意:此接口不暗示特定的自动启动语义。可以用于直接调用或通过JMX进行管理操作。在后一种情况下,MBeanExporter通常会使用InterfaceBasedMBeanInfoAssembler进行定义,从而将活动控制组件的可见性限制到Lifecycle接口。
ApplicationContext 中央接口为应用程序提供配置。这是应用程序运行时的只读,但可能是如果实现支持这个,则重新加载。ApplicationContext提供:访问应用程序组件的Bean工厂方法。以通用的方式加载文件资源的能力。将事件发布到注册监听器的功能。解析消息的能力,支持国际化。从父上下文继承。在后代中的定义将始终优先。这意味着,例如,一个单父级上下文可以被整个Web应用程序使用,而每个servlet都有独立于任何其他servlet的子环境。生命周期功能,ApplicationContext实现检测和调用
Closeable 定义启动/停止生命周期控制方法的通用接口。典型的用例是控制异步处理。注意:此接口不暗示特定的自动启动语义。考虑为此目的实施SmartLifecycle。可以由两个组件(通常是Spring上下文中定义的Spring bean)和容器(通常是Spring ApplicationContext本身)实现。容器将向每个容器内应用的所有组件传播开始/停止信号,例如,在运行时停止/重启场景。
ResourceLoader 加载资源的策略接口(例如类路径或文件系统资源)。 需要ApplicationContext来提供此功能以及扩展的ResourcePatternResolver支持。DefaultResourceLoader是一个独立的实现,可以在ApplicationContext之外使用,也可以被ResourceEditor使用。在ApplicationContext中运行时,使用特定上下文的资源加载策略,可以从Strings中填充Resource和Resource数组类型的Bean属性。
AutoCloseable 可能包含资源的对象(如文件或套接字句柄)直到它关闭。 AutoCloseable的方法退出代码时自动调用对象-with-resources块已经声明了对象资源规范头文件。这种结构确保及时发布,避免资源耗尽异常和错误否则可能发生。
EnvironmentCapable 接口指示包含并公开环境参考的组件。所有Spring应用程序上下文都是EnvironmentCapable,并且该接口主要用于在接受BeanFactory实例的框架方法中执行instanceof检查,如果实际上可用,它们可能实际上可能是ApplicationContext实例,以便与环境进行交互。
ListableBeanFactory BeanFactory接口的扩展由bean工厂实现,bean工厂可以枚举它们的所有bean实例,而不是按客户端的要求逐个尝试bean的名称查找。预加载所有bean定义的BeanFactory实现(例如基于XML的工厂)可以实现此接口。如果这是HierarchicalBeanFactory,则返回值不会考虑任何BeanFactory层次结构,但仅与当前工厂中定义的Bean相关。使用BeanFactoryUtils助手类也可以在祖先工厂中考虑bean。这个接口中的方法只会尊重这个工厂的bean定义。他们会忽略任何已经通过ConfigurableBeanFactory的registerSingleton方法注册的单例bean,除getBeanNamesOfType和getBeansOfType外,它们也会检查这些手动注册的单例。当然,BeanFactory的getBean也允许透明地访问这些特殊的bean。但是,在典型的情况下,所有的bean都将由外部bean定义来定义,所以大多数应用程序不需要担心这种差异。
HierarchicalBeanFactory 由可以成为层次结构一部分的bean工厂实现的子接口。可以在ConfigurableBeanFactory接口中找到允许以可配置方式设置父项的bean工厂的相应setParentBeanFactory方法。
MessageSource 用于解析消息的策略接口,支持这些消息的参数化和国际化。Spring提供两种开箱即用的生产实现:ResourceBundleMessageSource,建立在标准ResourceBundle之上;ReloadableResourceBundleMessageSource,可以在不重新启动虚拟机的情况下重新加载消息定义;
ApplicationEventPublisher 封装事件发布功能的接口
ResourcePatternResolver 用于将位置解析为Resource对象的策略接口。
BeanFactory 用于访问Spring bean容器的根接口。这是一个bean容器的基本客户端视图;更多的接口如ListableBeanFactory和ConfigurableBeanFactory可用于特定目的。该接口由持有多个bean定义的对象实现,每个定义由一个String名称唯一标识。根据bean的定义,工厂将返回一个包含对象的独立实例(原型设计模式)或一个共享实例(一个优于Singleton设计模式的替代方案,实例在该范围内是一个单例的工厂)。将返回哪种类型的实例取决于bean工厂配置:API是相同的。自Spring 2.0以来,根据具体的应用上下文(例如Web环境中的“请求”和“会话”范围),可以使用更多范围。这种方法的重点在于,BeanFactory是应用程序组件的中央注册表,集中了应用程序组件的配置(例如,单个对象不需要读取属性文件)。

以上解释,翻译自spring官方文档以及各类注释,按照老外的尿性,只是大概解释一下。
我们接下来一步一步断点调试运行程序,探究Spring容器的基本实现。

一步一步探究源码

进入ClassPathXmlApplicationContext类

/**
     * Create a new ClassPathXmlApplicationContext, loading the definitions
     * from the given XML file and automatically refreshing the context.
     * 创建一个新的ClassPathXmlApplicationContext,从给定的XML文件加载定义并自动刷新上下文。
     * @param configLocation resource location
     * @throws BeansException if context creation failed
     */
    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

调用同类方法

    /**
     * Create a new ClassPathXmlApplicationContext with the given parent,
     * 用给定的父项创建一个新的ClassPathXmlApplicationContext,从给定的XML文件中加载定义。
     * 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.
     * 加载所有bean定义并创建所有单例。或者,在进一步配置上下文后手动调用refresh。
     * @param parent the parent context 父级上下文
     * @throws BeansException if context creation failed 如果上下文创建失败,则抛出BeansException
     * @see #refresh() 该方法:返回静态指定的ApplicationListeners列表。
     */
    public ClassPathXmlApplicationContext(
            String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
            throws BeansException {

        super(parent);//设置父级上下文,但是目前为空的
        setConfigLocations(configLocations);//设置配置路径
        if (refresh) {//true自动刷新上下文
            refresh();//返回静态指定的ApplicationListeners列表。
        }
    }

setConfigLocations(configLocations);//设置配置路径
此时进入AbstractRefreshableConfigApplicationContext

    /**
     * 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++) {//for循环解析路径
                this.configLocations[i] = resolvePath(locations[i]).trim();//解析路径(如:BeanFactoryTest.xml)
            }
        }
        else {
            this.configLocations = null;
        }
    }

跟踪解析路径方法resolvePath(locations[i])
进入同类resolvePath(String path)方法

    /**
     * Resolve the given path, replacing placeholders with corresponding
     * environment property values if necessary. Applied to config locations.
     * 解析给定的路径,必要时用相应的环境属性值替换占位符。 应用于配置路径。
     * @param path the original file path 路径参数
     * @return the resolved file path 返回解析文件的路径
     * @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
     */
    protected String resolvePath(String path) {
        return getEnvironment().resolveRequiredPlaceholders(path);
    }

跟踪getEnvironment()往下走
进入AbstractApplicationContext类中

    /**
     * Return the {@code Environment} for this application context in configurable
     * form, allowing for further customization.
     * 以可配置的形式返回此应用程序上下文的{@Code Environment},以允许进一步的自定义。
     * <p>If none specified, a default environment will be initialized via
     * 如果没有指定,则初始化默认环境
     * {@link #createEnvironment()}.
     */
    @Override
    public ConfigurableEnvironment getEnvironment() {
        if (this.environment == null) {
            this.environment = createEnvironment();
        }
        return this.environment;
    }

跟踪createEnvironment();往下走
进入同类方法protected ConfigurableEnvironment createEnvironment()

    /**
     * Create and return a new {@link StandardEnvironment}.
     * 创建并返回一个新的标准环境。
     * <p>Subclasses may override this method in order to supply
     * a custom {@link ConfigurableEnvironment} implementation.
     */
    protected ConfigurableEnvironment createEnvironment() {
        return new StandardEnvironment();
    }

进入StandardEnvironment类,构建标准环境

package org.springframework.core.env;

/**
 * 标准环境,普通Java应用时使用,会自动注册System.getProperties() 和 System.getenv()到环境
 * @author Chris Beams
 * @since 3.1
 * @see ConfigurableEnvironment
 * @see SystemEnvironmentPropertySource
 * @see org.springframework.web.context.support.StandardServletEnvironment
 */
public class StandardEnvironment extends AbstractEnvironment {

    /** System environment property source name: {@value} */
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

    /** JVM system properties property source name: {@value} */
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";


    /**
     * MutablePropertySources类是实现于PropertySources类。
     * 
     * MutablePropertySources内部是使用CopyOnWriteArrayList实现的数组,CopyOnWrite容器即写时复制的容器。
     * CopyOnWrite容器通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,
     * 复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
     * 此处不过多解释CopyOnWrite,需要的话,自行查阅相关文档。
     */
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        //MapPropertySource是继承自EnumerablePropertySource
        //而EnumerablePropertySource继承自PropertySource

        //实际上MapPropertySource(String name, Map<String, Object> source)是一个键值对
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        //getSystemProperties();几乎等同于System.getProperties()
        //获取系统参数

        //SystemEnvironmentPropertySource与MapPropertySource类似,也是一个键值对
        //SystemEnvironmentPropertySource(String name, Map<String, Object> source)
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
        //getSystemEnvironment()几乎等同于System.getenv();
        //获取系统环境变量(System Environment Variable) 

        //此时可以理解为,MutablePropertySources(MapPropertySource(String,PropertySource));

    }

}

插两张图,帮助更好理解一些
这里写图片描述

这里写图片描述

实际上操作propertySources的时候,propertySources内部封装成类似于调用链表的方式,但实质是数组

private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();//声明数组列表

/**
     * Add the given property source object with highest precedence.
     */
    public void addFirst(PropertySource<?> propertySource) {
        if (logger.isDebugEnabled()) {
            logger.debug("Adding PropertySource '" + propertySource.getName() + "' with highest search precedence");
        }
        removeIfPresent(propertySource);
        this.propertySourceList.add(0, propertySource);
    }

    /**
     * Add the given property source object with lowest precedence.
     */
    public void addLast(PropertySource<?> propertySource) {
        if (logger.isDebugEnabled()) {
            logger.debug("Adding PropertySource '" + propertySource.getName() + "' with lowest search precedence");
        }
        removeIfPresent(propertySource);
        this.propertySourceList.add(propertySource);
    }

========================================我是分割线======================================

构建标准环境之后,返回到AbstractRefreshableConfigApplicationContext类中

getEnvironment().resolveRequiredPlaceholders(path);执行后半段resolveRequiredPlaceholders(path);

//此时,进入到AbstractEnvironment类中,将刚才获取的环境变量属性,赋值进去,刚才也就是propertySources
private final ConfigurablePropertyResolver propertyResolver =
            new PropertySourcesPropertyResolver(this.propertySources);
//然后,再调用resolveRequiredPlaceholders其他实现,text=BeanFactoryTest.xml
@Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        return this.propertyResolver.resolveRequiredPlaceholders(text);
    }

我们继续往下走。。。

往下就跑到了AbstractPropertyResolver类中

    @Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        if (this.strictHelper == null) {
            this.strictHelper = createPlaceholderHelper(false);
        }
        return doResolvePlaceholders(text, this.strictHelper);//配置文件占位符替换解析方法
    }

doResolvePlaceholders方法是这样子的,使用了lambda表达式

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
        return helper.replacePlaceholders(text, this::getPropertyAsRawString);//使用了lambda表达式
    }

replacePlaceholders方法是这样子的

public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
        Assert.notNull(value, "'value' must not be null");
        return parseStringValue(value, placeholderResolver, new HashSet<>());
    }

继续调用同类方法parseStringValue,其实就是递归查找占位符,然后return返回

protected String parseStringValue(
            String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

        StringBuilder result = new StringBuilder(value);

        int startIndex = value.indexOf(this.placeholderPrefix);
        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.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);
                // 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 + "\"");
                }
                visitedPlaceholders.remove(originalPlaceholder);
            }
            else {
                startIndex = -1;
            }
        }

        return result.toString();
    }

==========================================我是分割线=====================================

至此,resolvePath(String path)方法结束,但是完事了吗?no,还远远没有呢。

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

回头看ClassPathXmlApplicationContext这个类,到现在都没开始装载bean呢,前面只是spring容器的启动预热

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

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();//spring容器初始化的refresh
        }
    }

下一篇,深入分析refresh

小结 

spring容器在启动前,会进行一些系统设置,包括了构建标准环境,对指定的加载配置文件路径进行封装设置,替换占位符等。

猜你喜欢

转载自blog.csdn.net/u011709128/article/details/80849398
今日推荐