(11)容器的拓展点

容器的扩展

通常来说,开发者不需要通过继承ApplicationContext来实现自己的子类扩展功能。但是Spring IoC容器确实可以通过实现接口来增加一些功能。下面将描述一下这些接口。
接下来主要分是哪个部分来讲解:

1.通过 BeanPostProcessor后置处理器来对一个bean(可以是监控bean或者增加bean)
2.通过BeanFactoryPostProcessor工厂后置bean处理器来定义配置元数据(容器实例初始话Bean之前就改变配置元数据)
3.自定义FactoryBean的初始化逻辑

1.通过 BeanPostProcessor后置处理器来对一个bean

BeanPostProcessor接口定义了一些回调方法,开发者可以通过实现来自己的实例化逻辑,依赖解析逻辑等等如果开发者只是想在Spring容器完成了实例化,配置以及初始化Bean之后来做一些操作的话,可以通过使用BeanPostProcessor来做到

开发者可以配置多个BeanPostProcessor实例,开发者可以通过配置order属性来指定配置的BeanPostProcessor的执行顺序。当然,想要配置顺序必须同时要实现Ordered接口。如果开发者写了自己的BeanPostProcessor,开发者最好同时考虑实现Ordered接口

BeanPostProcessors是操作Bean实例的,换言之,Spring IoC容器必须先初始化好Bean,然后BeanPostProcessors才开始工作
BeanPostProcessors作用范围是基于容器的。当然,只有当开发者使用容器的层级的时候才是需要考虑的。如果开发者在容器中定义了一个BeanPostProcessor,这个实例只会在它所在的容器来处理Bean初始化以后的操作。换言之,一个容器中的Bean不会被另一个容器中定义BeanPostProcessor的在初始化以后进行后续处理,甚至就算两个容器同属同一个容器的部分。

org.springframework.beans.factory.config.BeanPostProcessor接口包含了2个回调方法。当这个接口的实例注册到容器当中时,对于每一个由容器创建的实例,这个后置处理器都会在容器开始进行初始化之前获得回调的调用。后置处理器可以针对Bean实例采取任何的操作,包括完全无视回调函数。Bean的后置处理器通常检查回调接口或者将Bean用代理包装一下。一些诸如Spring AOP代理的基础类都是 通过Bean的后续处理器来实现的

ApplicationContext会自动检查配置的Bean是否有实现BeanPostProcessor接口,ApplicationContext会将这些Bean注册为后续处理器这样这些后续处理器就会在Bean创建之后调用Bean的后续处理器就像其他的Bean一样,由容器管理的

尽管Spring团队推荐的注册Bean的后续处理的方式是通过ApplicationContext的自动检查,但是Spring也支持通过编程的方式,通过addBeanPostProcessor方法。这种方式有的时候也很有用,当需要在注册前执行一些条件判断的时候,或者在结构化的上下文中复制Bean后续处理器的时候尤其有效。需要注意的是,通过编程实现的BeanPostProcessors是会忽略掉Ordered接口的:由编程注册的BeanPostProcessors总是在自动检查到的BeanPostProcessors之前来执行的,而回忽略掉明确的顺序定义。

容器会特殊对待那些实现了BeanPostProcessor接口的类。所有的BeanPostProcessors和他们所引用的Bean都会在启动时直接初始化作为ApplicationContext启动的一个特殊阶段。然后,所有的BeanPostProcessors会按顺序注册并应用到容器中的Bean

下面通过一个例子来演示一下

/**
 * @Project: spring
 * @description:  模拟一个普通的bean
 * @author: sunkang
 * @create: 2018-09-16 15:06
 * @ModificationHistory who      when       What
 **/
public class BeanDemo{
    public BeanDemo() {
        System.out.println("beanDemo已经初始化了");
    }
}
/**
 * 模拟 BeanPostProcessor来监控其他bean,进行相应的处理
 */
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor,Ordered {

    //在bean初始化之后调用
    public Object postProcessBeforeInitialization(Object bean,
            String beanName) throws BeansException {
        System.out.println("beforeBean ''" + beanName + "'' created : " + bean.toString());
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean,
            String beanName) throws BeansException {
        System.out.println("afterBean ''" + beanName + "'' created : " + bean.toString());
        return bean;
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

在spring-extensionPoint.xml的配置中如下:





    

    

测试的演示:

ApplicationContext  context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");

测试结果如下:可以发现BeanPostProcessor的两个回调方法在容器构造方法之后进行执行的

beanDemo已经初始化了
beforeBean ''com.spring.extensionPoint.BeanDemo#0'' created : com.spring.extensionPoint.BeanDemo@1753acfe
afterBean ''com.spring.extensionPoint.BeanDemo#0'' created : com.spring.extensionPoint.BeanDemo@1753acfe

spring的例子:RequiredAnnotationBeanPostProcessor
使用回调接口或者注解和BeanPostProcessor实现是常见的来扩展Spring IoC容器的方式。Spring中的一个例子就是RequiredAnnotationBeanPostProcessor就是用来确保JavaBean的标记有注解的属性确实注入了依赖

2.通过BeanFactoryPostProcessor定义配置元数据

org.springframework.beans.factory.config.BeanFactoryPostProcessor。语义上来说,这个接口有些类似BeanPostProcessor,只是有一个很大的区别:BeanFactoryPostProcessor操作Bean配置元数据,也就是说,Spring允许BeanFactoryPostProcessor来读取配置源数据,并且可能会在容器实例初始话Bean之前就改变配置元数据

如前文所述,开发者可以配置多个BeanFactoryPostProcessors,而且开发者可以控制其具体执行的顺序。当然,配置顺序是必须实现Ordered接口的。如果实现了自己的BeanFactoryPostProcessor,开发者也应该考虑实现Ordered接口

在ApplicationContext中声明了后续处理器,Bean的后续处理器就会自动的执行,来实现在配置中定义的行为。Spring包括一些预定义好的后续处理器都可以使用,比如PropertyOverrideConfigurerPropertyPlaceholderCOnfigurer,也能够使用自定义的BeanFactoryPostProcessor,比如,来注册自定义属性编辑器

例子,类名替换BeanFactoryPostProcessor
下面就开始演示一下

/**
 * @Project: spring
 * @description:   模拟PropertyPlaceholderConfigurer来进行测试
 * @author: sunkang
 * @create: 2018-09-16 16:15
 * @ModificationHistory who      when       What
 **/
public class DataSourceConfig {
    private String driverClassName;

    private  String url;

    private String username;

    private String password;

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "DataSourceConfig{" +
                "driverClassName='" + driverClassName + '\'' +
                ", url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

在extensionPoint目录下放置datasouce.properties文件,文件内容如下

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

在spring-extensionPoint.xml配置如下

  
        
    
  
        
        
        
        
    

测试方法:

ApplicationContext  context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
 DataSourceConfig config=   context.getBean("dataSource",DataSourceConfig.class);
System.out.println(config);

测试结果如下:

DataSourceConfig{driverClassName='com.mysql.jdbc.Driver', url='jdbc:mysql:mydb', username='sa', password='root'}

Spring 2.5后引入了context命名空间,可以专用的配置元素来配置属性占位符。多个地址可以使用逗号分隔符。


PropertyPlaceholderConfigurer不仅仅查看开发者指定的Properties文件。默认的话,它如果不能再指定的属性文件中找到属性的话,仍然会检查Java的System配置。开发者可以通过配置systemPropertiesMode属性来修改这个行为,这个属性有如下三种值:
nerver(0):从不检查系统属性
fallback(1):如果没有从指定的属性文件中找到特定的属性时检查系统属性,这个是默认值
override(2):优先查找系统属性,而后才试着检查指定配置文件。系统的属性会覆盖指定文件的属性。

接下来看看PropertyPlaceholderConfigurer的源码,先看下PropertyPlaceholderConfigurer的继承关系,发现有beanFactoryPostProcessor


(11)容器的拓展点_第1张图片
image.png

在PropertyResourceConfigurer实现了BeanFactoryPostProcessor,那么postProcessBeanFactory就是主入口了,后面的流程有兴趣可以自己看看。

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            //从特定的文件加载属性到mergedProps中
            Properties mergedProps = mergeProperties();
            
            // Convert the merged properties, if necessary.
            convertProperties(mergedProps);

            //这里的处理会properties属性,会生成一个解析器,然后注入到容器中,装载beand的时候再来回调这个解析器
            // Let the subclass process the properties.
            processProperties(beanFactory, mergedProps);
        }
        catch (IOException ex) {
            throw new BeanInitializationException("Could not load properties", ex);
        }
    }

另外一个例子是PropertyOverrideConfigurer
PropertyOverrideConfigurer,是另一个bean的后置处理器,有些类似于PropertyPlaceholderConfigurer,但是区别于后者,原有的定义可能有默认值,或者没有任何值。如果覆盖的Properties文件没有一个确切的Bean属性,就使用默认的定义

覆盖的属性如下,注意但是要求每一个路径上的组件,需要是非空的,也就是dataSource该组件不能为空

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

在具体的xml配置如下,那么dataSource.driverClassName和url将被覆盖,没有覆盖的值保持原样

   
        
    

在Spring 2.5引入的context命名空间中,也可以指定配置覆盖的文件属性如下:


3.自定义FactoryBean的初始化逻辑

FactoryBean接口是一种类似于Spring IoC容器的插件化的逻辑。如果当开发者的代码有复杂的初始化代码,在配置上使用Java代码比XML更有效时,开发者可以考虑创建自己的FacotoryBean对象,将复杂的初始化操作放到类中,将自定义的FactoryBean扩展到容器中

FacotryBean接口提供如下三个方法

Object getObject():返回一个工厂创建的对象。实例可被共享,取决于返回Bean的作用域为原型还是单例。
boolean isSingleton():如果FactoryBean返回单例,为True,否则为False
Class getObjectType():返回由getObject()方法返回的对象的类型,如果对象类型未知,返回null。
FactoryBean概念和接口广泛用预Spring框架,Spring本身就有多于50个FactoryBean的实现

当开发者需要一个FactoryBean实例而不是其产生的Bean的时候,在调用ApplicationContext的getBean()方法时,在其id之前加上&符号。也就是说,对于一个给定的FactoryBean,其id为myBean,调用getBean("myBean")返回其产生的Bean对象,而调用getBean("&myBean")返回FactoryBean实例本身

下面进行例子演示:

/**
 * @Project: spring
 * @description:  说明是一个factroyBean    是一个bean,但是是一个工厂bean
 * @author: sunkang
 * @create: 2018-09-16 16:34
 * @ModificationHistory who      when       What
 **/
public class FactoryBeanDemo implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        return "factoryBean";
    }

    @Override
    public Class getObjectType() {
        return String.class;
    }
}

在spring-extensionPoint.xml具体的配置如下:

    

测试如下:

ApplicationContext  context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
//当其id为factoryBean,调用getBean("factoryBean")返回其产生的Bean对象,
//而调用getBean("&factoryBean")返回FactoryBean实例本身。
FactoryBeanDemo factoryBeanDemo =   context.getBean("&factoryBean",FactoryBeanDemo.class);
System.out.println("得到工厂:factory"+factoryBeanDemo);
//得到bean对象,其实是工厂bean调用了getObject方法产生的对象
System.out.println("得到具体的bean:"+context.getBean("factoryBean"));

结果如下: 结果很显然

得到工厂:factorycom.spring.extensionPoint.FactoryBeanDemo@5025a98f
得到具体的bean:factoryBean

你可能感兴趣的:((11)容器的拓展点)