Spring 框架和 Tomcat 容器扩展接口揭秘

前言

在 Spring 框架中,每个应用程序上下文(ApplicationContext)管理着一个 BeanFactory,BeanFactory 主要负责 Bean 定义的保存、Bean 的创建、Bean 之间依赖的自动注入等。应用程序上下文则是对 BeanFactory 和 Bean 的生命周期中的各个环节进行管理,并且提供扩展接口允许用户对 BeanFactory 和 Bean 的各个阶段进行定制,本文从以下三个点进行切入讲解。

  • refresh()是应用上下文刷新阶段。
  • getBean()是容器启动后从 BeanFactory 获取 Bean 过程。
  • close()是销毁应用程序上下文阶段。

refresh 阶段

应用程序上下文刷新操作最终调用的是 AbstractApplicationContext 的 refresh 方法,其核心执行步骤如下图所示。

无论是解析 XML 作为 Bean 来源的 ClassPathXmlApplicationContext 还是基于扫描注解类作为 Bean 来源的 AnnotationConfigApplicationContext,在刷新上下文的过程中最终都会走这个流程,不同在于这两者覆盖的该流程中的一些方法可能会有不同,其实这个属于设计模式里面的模板模式。

获取 BeanFactory

如上图中,步骤(1)获取一个 BeanFactory,对应 ClassPathXmlApplicationContext 应用程序上下文来说,这个步骤首先创建了一个 DefaultListableBeanFactory,然后解析配置 Bean 的 XML,并把 Bean 定义注册到 BeanFactory,内部主要函数为 refreshBeanFactory,代码如下。

    protected final void refreshBeanFactory() throws BeansException {        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);            synchronized (this.beanFactoryMonitor) {                this.beanFactory = beanFactory;
            }
        }        catch (IOException ex) {
...        }
    }

可以通过覆盖该步骤内的 refreshBeanFactory 方法,实现自己的 BeanFactory 创建和解析配置文件的 Bean 的策略。

标准初始化配置 BeanFactory

步骤(2)配置步骤(1)创建的 BeanFactory,比如设置 BeanFactory 工厂创建 Bean 时使用什么样的类加载器,默认情况下使用线程上下文类加载器(默认为 AppClassLoader)。

这里如果想实现不同的 BeanFactory 创建 Bean,使用不同的 classloader 来实现模块隔离,可以通过在不同的 ClassPathXmlApplicationContext 上调用 setClassLoader 方法来设置不同的 classloader 来实现。

另外步骤(2)还向 BeanFactory 添加了一个 BeanPostProcessor 的实现类 ApplicationContextAwareProcessor,这个后面会讲到,代码如下。

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        beanFactory.setBeanClassLoader(getClassLoader());
        ...
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
        ...
}

自上下文对 BeanFactory 进行个性化定制的扩展

步骤(3)是在步骤(2)对 BeanFactory 进行标准初始化配置后,留出的允许子上下文对 BeanFactory 进行个性化定制的扩展,这时候会加载所有的 Bean 的定义,但是这时候还没有 Bean 被实例化,这时允许注册一些 BeanPostProcessors 类型的 Bean 用来在 Bean 初始化前后做一些事情。

例如 XmlWebApplicationContext 上下文里面的 postProcessBeanFactory 的实现,代码如下。

    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
...
    }

注册了 ServletContextAwareProcessor,用来把 servletContext 设置到实现了 ServletContextAware 接口的 Bean。

用户注册 BeanFactoryPostProcessor 用来对 BeanFactory 进行扩展

步骤(4)执行用户注册的 BeanFactoryPostProcessor 扩展 Bean,用来对 BeanFactory 中的 Bean 定义进行修改,比如常见的是统一设置某些 Bean 的属性变量值。那么 BeanFactoryPostProcessor 为何物呢?

public interface BeanFactoryPostProcessor {    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

如上代码 BeanFactoryPostProcessor 是一个接口,有一个方法,该方法参数是 beanFactory,由于通过 beanFactory 可以访问所有的 Bean 的定义,所以当我们实现了该接口,并注入实现类到 Spring 容器后,就可以在实例化 Bean 前对指定的 Bean 定义进行修改或者注册新的 Bean。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

如上代码,BeanDefinitionRegistryPostProcessor 接口继承自 BeanFactoryPostProcessor,它新添加了一个接口,用来在BeanFactoryPostProcessor 实现类中 postProcessBeanFactory 方法执行前再注册一些 Bean 到 beanFactory 中。

基础知识普及完毕后,下面来看步骤(4)做了什么?

步骤(4) 首先执行实现了 BeanDefinitionRegistryPostProcessor 接口的 Bean 的 postProcessBeanDefinitionRegistry 方法,然后再执行实现了 BeanFactoryPostProcessor 接口的 Bean 的 postProcessBeanFactory 方法。由于接口的实现类可能会有多个,如果你想先执行某些接口的方法,可以通过实现 PriorityOrdered 或者 Ordered 接口给每个接口定义一个优先级,另外实现 PriorityOrdered 接口的优先级大于实现 Ordered 的优先级。

比如,基于扫描注解类作为 Bean 来源的 AnnotationConfigApplicationContext,会在 refresh 阶段前注册一个ConfigurationClassPostProcessor,它实现了 BeanDefinitionRegistryPostProcessor、PriorityOrdered 两个接口。

因为实现了第一接口,所以会在步骤(4)的时候执行 postProcessBeanDefinitionRegistry 方法,这个方法内部作用是使用ConfigurationClassParser 解析所有标注有 @Configuration 注解的类,并解析该类里面所有标注 @Bean 的方法和标注 @Import 的bean,并注入这些解析的 Bean 到 Spring上下文容器里面。

因为实现了第二个接口,所以该类有 getOrder 方法返回该类的优先级,这里实现为O rdered.LOWEST_PRECEDENCE,也就是优先级最低。

比如解析 ${...}占位符的 PropertyPlaceholderConfigurer 会在步骤(4)阶段执行 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 方法对 Bean 定义的属性值中 ${...} 进行替换,具体一个例子如下。

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location">
        <value>config.properties</value>
    </property>
    <property name="fileEncoding">
        <value>UTF-8</value>
    </property></bean><bean id="testPlaceholder" class="zlx.test.annotationConfigApplicationContext.TestImpl">
    <property name="name" value="${name}"></property></bean>

如上代码,首先注入了 propertyConfigurer 实例并且配置了属性值来源为 config.properties,并且在注入 TestImpl 实例的时候使用了占位符 "${name}" 来设置 name 属性,其中 config.properties 内容如下:

name=jiaduo

其中 TestImpl 代码如下:

public class TestImpl {    private String name;    public String getName() {        return name;
    }    public void setName(String name) {        this.name = name;
    }    public void say(){
        System.out.println("hello " +name);
    }
}

那么当我们通过运行以下代码:

        ClassPathXmlApplicationContext cpxa = new ClassPathXmlApplicationContext("bean.xml");
        cpxa.getBean("testPlaceholder",TestImpl.class).say();

会输出以下结果:

hello jiaduo

占位符替换的时机就是在步骤(4)执行 PropertyPlaceholderConfigurer 类的 postProcessBeanFactory 方法时候,该方法用 config.properties文件 中 key 为 name 的属性值替换 BeanFactory 里面 Bean 的属性值为 "${name}"的属性。需要注意的是这时候 Bean 还没有被实例化,只是静态的进行属性值替换。

小结:BeanFactoryPostProcessor 后置处理器扩展接口是在 Bean 进行实例化前执行的,它的作用是对 BeanFactory 中 Bean 的定义做修改(比如新增 Bean 的定义,修改已有 Bean 定义,修改 Bean 的属性值等)。

注册 BeanPostProcessor 到 BeanFactory 的 beanPostProcessors 列表

相比 BeanFactoryPostProcessor 是在 Bean 实例化前对 BeanFactory 进行扩展,BeanPostProcessor 是在 Bean 实例化后对 Bean 进行扩展,下面看看 BeanPostProcessor 的接口定义,代码如下。

public interface BeanPostProcessor {    //在Bean实例化后,初始化前进行一些扩展操作
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        return bean;
    }    //在Bean实例化后,初始化后进行一些扩展操作
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        return bean;
    }
}

本阶段就是把用户注册的实现了该接口的 Bean 进行收集,然后放入到 BeanFactory 的 beanPostProcessors 属性里面,待后面使用。

为应用上下文子类初始化一些特殊类留出的扩展

refresh 核心执行步骤(6)是为应用上下文子类初始化一些特殊类留出的扩展,例如 SpringBoot 中 AbstractApplicationContext 的子类 EmbeddedWebApplicationContext 应用程序上下文,重写的 onRefresh 方法如下:

    protected void onRefresh() {        super.onRefresh();        try {
            createEmbeddedServletContainer();
        }        catch (Throwable ex) {            throw new ApplicationContextException("Unable to start embedded container",
                    ex);
        }
    }

如上代码在重写的 onRefresh 方法内创建了内嵌 Web 容器。

原文发布于微信公众号 - GitChat精品课(CSDN_Tech)

原文发表时间:2018-04-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏飞雪无情的博客

Android注解支持(Support Annotations)

原文地址 http://tools.android.com/tech-docs/support-annotations

12130
来自专栏Java与Android技术栈

构建一个给爬虫使用的代理IP池总结

做网络爬虫时,一般对代理IP的需求量比较大。因为在爬取网站信息的过程中,很多网站做了反爬虫策略,可能会对每个IP做频次控制。这样我们在爬取网站时就需要很多代理I...

14910
来自专栏青蛙要fly的专栏

Android技能树 — 多进程相关小结

这次是讲Android存储路径及IO的基本操作。因为我们在开发的时候会经常这种方便的需求。这篇文章的内容我写的可能很少,都没有细写。别吐槽。o( ̄︶ ̄)o

8510
来自专栏Eugene's Blog

SQL手工注入语法分类目录文章标签友情链接联系我们

14150
来自专栏微服务生态

Flume-NG源码分析-整体结构及配置载入分析

终于开始Flume源码的分析研究工作了,我也是边学边和大家分享,内容上难免有不足之处,望大家见谅。

15640
来自专栏Ryan Miao

在dropwizard中使用feign,使用hystrix

前言 用惯了spring全家桶之后,试试dropwizard的Hello World也别有一帆风味。为了增强对外访问API的能力,需要引入open feign...

423120
来自专栏开发之途

Android AIDL使用详解

57720
来自专栏xiaoheike

Spring MVC 上下文(ApplicationContext)初始化入口

应该来说是很少使用这种方法用于生产开发,常常在学习Spring做demo的时候会使用到。更有可能出现在Spring项目的代码测试,不过呢,单元测试的框架(比如 ...

71710
来自专栏刘望舒

理解 Android Hook 技术以及简单实战

作者:落英坠露 链接:https://www.jianshu.com/p/4f6d20076922 著作权归作者所有,转载请联系作者获得授权。 1. 什么是 H...

949150
来自专栏杂烩

Eclipse下Hadoop的MapReduce开发之单Map编写

    先说下业务需求吧,有个系统日志文件,记录系统的运行信息,其中包含DEBUG、INFO、WARN、ERROR四个级别的日志,现在想要看到所有WARN级别的...

9030

扫码关注云+社区

领取腾讯云代金券