Application 容器扩展点

通常,应用程序开发人员无需为ApplicationContext实现类提供子类。相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。接下来的几节描述了这些集成接口。

1.8.1 使用BeanPostProcessor自定义Bean

​ BeanPostProcessor接口定义了回调方法,您可以实施这些回调方法以提供自己的(或覆盖容器的默认值)实例化逻辑,依赖关系解析逻辑等。如果您想在Spring容器完成实例化,配置和初始化bean之后实现一些自定义逻辑,则可以插入一个或多个自定义BeanPostProcessor实现。

​ 您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例的运行顺序。仅当BeanPostProcessor实现Ordered接口时,才可以设置此属性。如果您编写自己的BeanPostProcessor,则也应该考虑实现Ordered接口。

BeanPostProcessor实例在bean(或对象)实例上运行。也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor实例完成其工作。

BeanPostProcessor实例是按容器划分作用域的。仅在使用容器层次结构时,这才有意义。如果在一个容器中定义BeanPostProcessor,它将仅对该容器中的bean进行后处理。换句话说,一个容器中定义的Bean不会被另一个容器中定义的BeanPostProcessor进行后处理,即使这两个容器是同一层次结构的一部分。

要更改实际的bean定义,您需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor自定义配置元数

​ org.springframework.beans.factory.config.BeanPostProcessor接口恰好由两个回调方法组成。将此类注册为容器的后处理器时,对于容器创建的每个bean实例,后处理器都会在容器初始化方法(例如InitializingBean.afterPropertiesSet()或任何声明的init方法),并在任何bean初始化回调之后被调用。后处理器可以对bean实例执行任何操作,包括完全忽略回调。 Bean后处理器通常检查回调接口,或者可以用代理包装Bean。一些Spring AOP基础结构类被实现为bean后处理器,以提供代理包装逻辑

​ ApplicationContext自动检测实现BeanPostProcessor接口的配置元数据中定义的所有bean。 ApplicationContext将这些bean注册为后处理器,以便以后在bean创建时可以调用它们。 Bean后处理器可以与其他Bean相同的方式部署在容器中。

​ 请注意,在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口。指示该bean的后处理器性质。否则,ApplicationContext无法在完全创建之前按类型自动检测它。由于BeanPostProcessor需要提前实例化以便应用于上下文中其他bean的初始化,因此这种早期类型检测至关重要

  • 以编程方式注册BeanPostProcessor实例

    尽管建议的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如前所述),但是您可以使用addBeanPostProcessor方法以编程方式针对ConfigurableBeanFactory注册它们。当您需要在注册之前评估条件逻辑,甚至需要跨层次结构的上下文复制Bean后处理器时,这将非常有用。但是请注意,以编程方式添加的BeanPostProcessor实例不遵守Ordered接口。在这里,注册的顺序决定了执行的顺序。还要注意,以编程方式注册的BeanPostProcessor实例始终在通过自动检测注册的实例之前进行处理,而不考虑任何明确的顺序。

  • BeanPostProcessor实例和AOP自动代理

    实现BeanPostProcessor接口的类是特殊的,并且容器对它们的处理方式有所不同。作为ApplicationContext特殊启动阶段的一部分,所有BeanPostProcessor实例和它们直接引用的Bean都会在启动时实例化。接下来,以排序方式注册所有BeanPostProcessor实例,并将其应用于容器中的所有其他bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以BeanPostProcessor实例或它们直接引用的bean都不符合自动代理的资格,因此没有编织的方面。 对于任何此类bean,您应该看到一条参考性日志消息:Bean someBean不适合所有BeanPostProcessor接口进行处理(例如:不适合自动代理)。 如果您通过使用自动装配或@Resource(可能会回退为自动装配)将Bean连接到BeanPostProcessor中,则Spring在搜索类型匹配的依赖项候选对象时可能会访问意外的Bean,因此使它们不符合自动代理或其他种类的条件豆后处理。例如,如果您有一个@Resource注释的依赖项,其中字段或设置器名称不直接与bean的声明名称相对应,并且不使用name属性,Spring将访问其他bean以按类型匹配它们。

​ 下面的示例演示如何在ApplicationContext中编写,注册和使用BeanPostProcessor实例。

  • 示例:Hello World,BeanPostProcessor

    ​ 该示例显示了一个自定义BeanPostProcessor实现,该实现调用由容器创建的每个bean的toString()方法,并将结果字符串打印到系统控制台。

    ​ 以下清单显示了自定义BeanPostProcessor实现类的定义:

    public class CustomBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if(bean instanceof TestBean){
                ((TestBean)bean).setAge(100);
                System.out.println("这是在Bean回调方法(init方法等)执行之前执行的");
            }
            System.out.println("bean的class类型是否为TestBean"+TestBean.class.isAssignableFrom(bean.getClass()));
            //如果返回null 后续的其他BeanPostProcessor都不会执行了
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("这是在Bean回调方法(init方法等)执行之前执行的");
            System.out.println("bean:"+beanName+",toString:"+bean.toString());
            //如果返回null 后续的其他BeanPostProcessor都不会执行了
            return bean;
        }
    
        @Override
        public int getOrder() {
            //实现PriorityOrdered接口的方法
            //定义在application容器中的执行顺序,值越小越早执行
            return 4;
        }
    }
    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans default-init-method="init"
           default-destroy-method="destroy" xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:p="http://www.springframework.org/schema/p"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
            <context:annotation-config/>
            <bean id="parentAbstract" abstract="true">
                <property name="age" value="18"/>
            </bean>
            <bean id="testBean"
                  parent="parentAbstract"
                  class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.TestBean">
            </bean>
            <bean class="springioc.coretechnologic.ioc.annotationbasecontainerconfig.CustomBeanPostProcessor"/>
    </beans>
    
  • 示例:RequiredAnnotationBeanPostProcessor

    ​ 将回调接口或批注与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常用方法。 Spring的RequiredAnnotationBeanPostProcessor是一个示例,它是Spring发行版附带的BeanPostProcessor实现,可以确保标有(任意)批注的bean上的JavaBean属性实际上(被配置为)依赖注入了一个值。

1.8.2 使用BeanFactoryPostProcessor自定义配置元数据

​ org.springframework.beans.factory.config.BeanFactoryPostProcessor。该接口的语义与BeanPostProcessor的语义相似,但有一个主要区别:BeanFactoryPostProcessor对Bean配置元数据进行操作。也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并有可能在容器实例化除BeanFactoryPostProcessor实例以外的任何bean之前更改它。

​ 您可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。但是,仅当BeanFactoryPostProcessor实现Ordered接口时才能设置此属性。如果您编写自己的BeanFactoryPostProcessor,则也应该考虑实现Ordered接口。

​ 如果要更改实际的bean实例(即从配置元数据创建的对象),则需要使用BeanPostProcessor(在前面的使用BeanPostProcessor自定义Bean中进行了介绍)。从技术上讲,可以在BeanFactoryPostProcessor中使用Bean实例(例如,通过使用BeanFactory.getBean()),但这样做会导致Bean实例化过早,从而违反了标准容器的生命周期。这可能会导致负面影响,例如绕过bean后处理。 同样,BeanFactoryPostProcessor实例是按容器划分作用域的。仅在使用容器层次结构时才有意义。如果在一个容器中定义BeanFactoryPostProcessor,它将仅应用于该容器中的Bean定义。一个容器中的Bean定义不会由另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使两个容器都属于同一层次结构也是如此。

​ Bean工厂后处理器在ApplicationContext中声明时会自动运行,以便将更改应用于定义容器的配置元数据。 Spring包含许多预定义的bean工厂后处理器,例如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor,例如注册自定义属性编辑器。

​ ApplicationContext自动检测实现BeanFactoryPostProcessor接口的部署到其中的所有Bean。它在适当的时候将这些bean用作bean工厂的后处理器。您可以像部署其他任何bean一样部署这些后处理器bean。

​ 与BeanPostProcessors一样,您通常不想为延迟初始化配置BeanFactoryPostProcessors。如果没有其他bean引用Bean(Factory)PostProcessor,则该后处理器将完全不会被实例化。因此,将其标记为延迟初始化将被忽略,即使您在元素的声明中将default-lazy-init属性设置为true,Bean(Factory)PostProcessor也会被实例化。

  • 示例:类名替换PropertySourcesPlaceholderConfigurer

    ​ 您可以使用PropertySourcesPlaceholderConfigurer来通过使用标准Java属性格式将来自Bean定义的属性值外部化到单独的文件中。这样做使部署应用程序的人员可以自定义特定于环境的属性,例如数据库URL和密码,而不必为修改容器的主要XML定义文件而复杂或有风险。

    ​ 考虑以下基于XML的配置元数据片段,其中定义了具有占位符值的DataSource:

    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="locations" value="classpath:com/something/jdbc.properties"/>
    </bean>
    
    <bean id="dataSource" destroy-method="close"
            class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    

    ​ 该示例显示了从外部属性文件配置的属性。在运行时,将PropertySourcesPlaceholderConfigurer应用于替换数据源某些属性的元数据。将要替换的值指定为$ {property-name}格式的占位符,该格式遵循Ant和log4j和JSP EL样式。

    ​ 实际值来自标准Java属性格式的另一个文件:

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

    ​ 因此,$ {jdbc.username}字符串在运行时将被替换为值“ sa”,并且其他与属性文件中的键匹配的占位符值也将被替换。 PropertySourcesPlaceholderConfigurer检查Bean定义的大多数属性和属性中的占位符。此外,您可以自定义占位符前缀和后缀。

    ​ 借助Spring 2.5中引入的上下文名称空间,您可以使用专用配置元素配置属性占位符。您可以在location属性中以逗号分隔的列表形式提供一个或多个位置,如以下示例所示:

    <context:property-placeholder location="classpath:com/something/jdbc.properties"/>
    

    ​ PropertySourcesPlaceholderConfigurer不仅在您指定的属性文件中查找属性。默认情况下,如果无法在指定的属性文件中找到属性,则会检查Spring Environment属性和常规Java System属性。

    ​ 您可以使用PropertySourcesPlaceholderConfigurer替换类名称,这在您必须在运行时选择特定的实现类时有时很有用。以下示例显示了如何执行此操作:

    <bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <value>classpath:com/something/strategy.properties</value>
        </property>
        <property name="properties">
            <value>custom.strategy.class=com.something.DefaultStrategy</value>
        </property>
    </bean>
    
    <bean id="serviceStrategy" class="${custom.strategy.class}"/>
    

    ​ 如果无法在运行时将类解析为有效的类,则将要创建的bean解析将失败,这是在非延迟初始化bean的ApplicationContext的preInstantiateSingletons()阶段期间进行的。

  • 示例:PropertyOverrideConfigurer

    ​ 另一个bean工厂后处理程序PropertyOverrideConfigurer与PropertySourcesPlaceholderConfigurer相似,但是与后者不同,原始定义可以具有默认值,也可以完全没有bean属性的值。如果覆盖的属性文件没有某个bean属性的条目,则使用默认的上下文定义。

    ​ 注意,bean定义不知道会被覆盖,因此从XML定义文件中不能立即看出正在使用覆盖配置器。如果有多个PropertyOverrideConfigurer实例为同一个bean属性定义了不同的值,则由于覆盖机制,最后一个实例将获胜。

    ​ 属性文件配置行采用以下格式:

    beanName.property=value
    

    ​ 下面的清单显示了格式的示例:

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

    该示例文件可以与包含一个名为dataSource的bean的容器定义一起使用,该bean具有驱动程序和url属性。

    只要路径的每个组成部分(最终属性除外)都已经为非空(可能是由构造函数初始化),则也支持复合属性名。在下面的示例中,tom bean的fred属性的bob属性的sammy属性设置为标量值123:

    tom.fred.bob.sammy=123
    

    指定的替代值始终是文字值。它们不会转换为bean引用。当XML bean定义中的原始值指定bean引用时,此约定也适用。

    使用Spring 2.5中引入的上下文名称空间,可以使用专用配置元素配置属性覆盖,如以下示例所示:

    <context:property-override location="classpath:override.properties"/>
    
1.8.3 使用FactoryBean自定义实例化逻辑

​ 您可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。

​ FactoryBean接口是可插入Spring IoC容器的实例化逻辑的一点。如果您拥有复杂的初始化代码,而不是(可能)冗长的XML量,可以用Java更好地表达,则可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将自定义FactoryBean插入容器。

​ FactoryBean接口提供了三种方法:

  • Object getObject():返回此工厂创建的对象的实例。实例可以共享,具体取决于该工厂是否返回单例或原型。
  • boolean isSingleton():如果此FactoryBean返回单例则返回true,否则返回false。
  • class getObjectType():返回由getObject()方法返回的对象类型;如果类型未知,则返回null。

Spring框架中的许多地方都使用了FactoryBean概念和接口。 Spring附带了50多个FactoryBean接口实现。

当您需要向容器要求一个实际的FactoryBean实例本身而不是由它产生的bean时,请在调用ApplicationContext的getBean()方法时在该bean的ID前面加上一个&符号(&)。因此,对于给定的myBean id为FactoryBean的FactoryBean,在容器上调用getBean(“ myBean”)将返回FactoryBean的getObject()返回的实例,而调用getBean(“&myBean”)将返回FactoryBean实例本身。

参考文献

【https://docs.spring.io/spring-framework/docs/current/reference/html/core.html】【1.8. Container Extension Points】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值