欢迎您的访问
专注于Java技术系列文章的Java技术分享网站

Spring 源码解析(十九)容器的功能扩展概览

摘要: 本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。

经过前面几章的分析,相信大家已经对 Spring 中的容器功能有了简单的了解,在前面的章节中我们一直以 BeanFactory 接口以及它的默认实现类 XmlBeanFactory 为例进行分析,但是, Spring还提供了另一个接口 ApplicationContext,用于扩展 BeanFactory 现有的功能。

ApplicationContext 和 BeanFactory两者都是用于加载 Bean 的,但是相比之下,ApplicationContext 提供了更多的扩展功能,简单一点说: ApplicationContext 包含 BeanFactory 的所有功能。通常建议比 BeanFactory 优先,除非在一些限制的场合,比如字节长度对内存有很大的影响时 ( Applet )。绝大多数“典型的”企业应用 和系统, ApplicationContext 就是你需要使用的。

那么究竟 ApplicationContext 比BeanFactory 多出了哪些功能呢?还需要我们进一步的探索。首先我们来看看使用两个不同的类去加载配置文件在写法上的不同。

  • 使用 BeanFactory 方式加载 XML。
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring/spring-test.xml"));
  • 使用 ApplicationContext 方式加载 XML。
ApplicationContext context = new ClassPathXmlApplicationContext("spring/lookup-test.xml");

同样,我们还是以ClassPathXmlApplicationContext作为切入点,开始对整体功能进行分析。

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
    this(new String[] {configLocation}, true, null);
}

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

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

设置路径是必不可少的步骤,ClassPathXmlApplicationContext可以将配置文件路径以数组的方式传入,ClassPathXmlApplicationContext可以对数组进行解析并进行加载。而对于解析及功能实现都在refresh()中实现。

一、设置配置路径

ClassPathXmlApplicationContext支持多个配置文件以数组的方式同时传入:

/**
 * 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;
    }
}

此函数主要用于解析给定的路径数据,当然,如果数组中包含特殊符号,如${var},那么在resolvePath中会搜索匹配的系统变量并替换。

二、扩展功能

设置了路径之后,便可以根据路径做配置文件的解析以及各种功能的实现了。可以说 refresh 函数中包含了几乎 ApnlicationContext 中提供的全部功能,而且此函数中逻辑非常清晰明了,使我们很容易分析对应的层次及逻辑。

@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.
        // 初始化BeanFactory,并进行XML文件读取
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        // 对BeanFactory进行各种功能填充
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            // 子类覆盖方法做额外的处理
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            // 激活各种BeanFactory处理器
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            // 注册拦截Bean创建Bean处理器,这里只是注册,真正的调用是在getBean的时候
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            // 为上下文初始化Message源,即不同语言的消息体,国际化处理
            initMessageSource();

            // Initialize event multicaster for this context.
            // 初始化应用消息广播器,并放入applicationEventMulticaster bean中
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            // 留给子类来初始化其他的Bean
            onRefresh();

            // Check for listener beans and register them.
            // 在所有注册的bean中查找Listener bean,注册到消息广播器中
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            // 初始化剩下的单实例(非惰性的)
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            // 完成刷新过程,通知生命周期处理器 lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
            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();
        }
    }
}

下面概括一下ClassPathXmlApplicationContext初始化的步骤,并从中解释一下它为我们提供的功能。

(1)初始化前的准备工作,例如对系统属性或者环境变最进行准备及验证。

在某种情况下项目的使用需要读取某些系统变量,而这个变量的设置很可能会影响着系统的正确性,那么ClassPathXmlApplicationContext为我们提供的这个准备函数就显得非常必要,它可以在Spring 启动的时候提前对必须的变量进行存在性验证。

( 2 )初始化BeanFactory,并进行XML文件读取。

之前有提到 ClassPathXmlApplicationContext包含着 BeanFactory所提供的一切特征,那么在这一步骤中将会复用BeanFactory 中的配置文件读取解析及其他功能,这一步之后, ClassPathXmlApplicationContext实际上就已经包含了 BeanFactory所提供的功能,也就是可以进行 Bean 的提取等基础操作了。

(3)对 BeanFactory 进行各种功能填充。

@Qualifier与@Autowired 应该是大家非常熟悉的注解,那么这两个注解正是在这一步骤中增加的支持。

(4)子类覆盖方法做额外的处理。

Spring 之所以强大,为世人所推崇,除了它功能上为大家提供了便利外,还有一方面是它的完美架构,开放式的架构让使用它的程序员很容易根据业务需要扩展已经存在的功能。这种开放式的设计在 Spring 中随处可见,例如在本例中就提供了一个空的函数实现 postProcessBeanFactory 来方便程序员在业务上做进一步扩展。

(5)激活各种BeanFactory处理器。

(6)注册拦截 bean 创建的 bean 处理器,这甩只是注册,真正的调用是在 getBean 时候。

(7)为上下文初始化 Message 源,即对不同语言的消息体进行国际化处理。

(8)初始化应用消息广播器,并放入applicationEventMulticaster bean 中。

(9)留给子类来初始化其他的 bean 。

(10)在所有注册的bean中查找Listener bean,注册到消息广播器中。

(11)初始化剩下的单实例(非惰性的)。

(12)完成刷新过程,通知生命周期处理器 lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人。

文章永久链接:https://tech.souyunku.com/?p=15689

赞(62) 打赏



版权归原创作者所有,任何形式转载请联系作者;搜云库技术团队 » Spring 源码解析(十九)容器的功能扩展概览

本站:免责声明!

评论 抢沙发

一个专注于Java技术系列文章的技术分享网站

觉得文章有用就打赏一下文章作者

微信扫一扫打赏

微信扫一扫打赏