前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring 框架和 Tomcat 容器扩展接口揭秘

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

作者头像
CSDN技术头条
发布2018-07-30 11:47:14
4930
发布2018-07-30 11:47:14
举报
文章被收录于专栏:CSDN技术头条

前言

在 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,代码如下。

代码语言:javascript
复制
    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,这个后面会讲到,代码如下。

代码语言:javascript
复制
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 的实现,代码如下。

代码语言:javascript
复制
    @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 为何物呢?

代码语言:javascript
复制
public interface BeanFactoryPostProcessor {    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

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

代码语言:javascript
复制
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 定义的属性值中 ${...} 进行替换,具体一个例子如下。

代码语言:javascript
复制
<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 内容如下:

代码语言:javascript
复制
name=jiaduo

其中 TestImpl 代码如下:

代码语言:javascript
复制
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);
    }
}

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

代码语言:javascript
复制
        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 的接口定义,代码如下。

代码语言:javascript
复制
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 方法如下:

代码语言:javascript
复制
    protected void onRefresh() {        super.onRefresh();        try {
            createEmbeddedServletContainer();
        }        catch (Throwable ex) {            throw new ApplicationContextException("Unable to start embedded container",
                    ex);
        }
    }

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GitChat精品课 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 用户注册 BeanFactoryPostProcessor 用来对 BeanFactory 进行扩展
  • 注册 BeanPostProcessor 到 BeanFactory 的 beanPostProcessors 列表
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档