2.8 Spring Framework 5.x 之Container 扩展点

1.8 Container 扩展点

通常,应用程序开发人员不需要子类化ApplicationContext 实现类。相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。接下来的几节将介绍这些集成接口。

1.8.1 通过一个 BeanBeanPostProcessorCustomizing Beans

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

您可以配置多个BeanPostProcessor实例,并且可以BeanPostProcessor通过设置order属性来控制这些实例的执行顺序。只有在BeanPostProcessor实现Ordered 接口时才能设置此属性。如果你自己编写BeanPostProcessor,你也应该考虑实现这个Ordered接口。有关更多详细信息,请参阅BeanPostProcessor 和Ordered接口的javadoc 。另见关于实例的程序化登记BeanPostProcessor的说明。

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

BeanPostProcessor实例是每个容器的范围。仅当您使用容器层次结构时,这才是相关的。如果BeanPostProcessor在一个容器中定义一个容器,它只会对该容器中的bean进行后处理。换句话说,BeanPostProcessor即使两个容器都是同一层次结构的一部分,在一个容器中定义的bean也不会被另一个容器中定义的bean进行后处理。

要更改实际的bean定义(即定义bean的蓝图),您需要使用a BeanFactoryPostProcessor,如 使用a 定制配置元数据中所述BeanFactoryPostProcessor。

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

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

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

以编程方式注册BeanPostProcessor实例
虽然推荐的BeanPostProcessor注册方法是通过 ApplicationContext自动检测(如前所述),但您可以ConfigurableBeanFactory使用该addBeanPostProcessor 方法以编程方式对其进行注册。当您需要在注册之前评估条件逻辑或甚至跨层次结构中的上下文复制Bean post处理器时,这非常有用。但请注意,以BeanPostProcessor编程方式添加的实例不尊重Ordered接口。这里,注册顺序决定了执行的顺序。另请注意,以BeanPostProcessor编程方式注册的实例始终在通过自动检测注册的实例之前处理,而不管任何显式排序。

BeanPostProcessor 实例和AOP自动代理
实现BeanPostProcessor接口的类是特殊的,容器会对它们进行不同的处理。BeanPostProcessor他们直接引用的所有实例和bean都会在启动时实例化,作为特殊启动阶段的一部分ApplicationContext。接下来,所有BeanPostProcessor实例都以排序方式注册,并应用于容器中的所有其他bean。因为AOP自动代理是作为一个BeanPostProcessor自身实现的,所以BeanPostProcessor 实例和它们直接引用的bean都不符合自动代理的条件,因此没有编入方法。

对于任何此类bean,您应该看到一条信息性日志消息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)。

如果您BeanPostProcessor通过使用自动装配或@Resource(可能会回退到自动装配)将bean连接到您的手中 ,Spring可能会在搜索类型匹配依赖项候选项时访问意外的bean,从而使它们不符合自动代理或其他类型的bean post -处理。例如,如果您有一个依赖项,@Resource其中字段或setter名称与bean的声明名称没有直接对应,并且没有使用name属性,则Spring会访问其他bean以按类型匹配它们。

以下示例显示如何在一个中编写,注册和使用BeanPostProcessor实例ApplicationContext。

示例:Hello World,BeanPostProcessor-style

第一个例子说明了基本用法。该示例显示了一个自定义 BeanPostProcessor实现,该实现调用toString()容器创建的每个bean 的方法,并将生成的字符串输出到系统控制台。

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

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

以下beans元素使用InstantiationTracingBeanPostProcessor:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        http://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

请注意它InstantiationTracingBeanPostProcessor是如何定义的。它甚至没有名称,并且,因为它是一个bean,它可以像任何其他bean一样依赖注入。(前面的配置还定义了一个由Groovy脚本支持的bean。在动态语言支持一章中详细介绍了Spring 动态语言支持。)

以下Java应用程序运行上述代码和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}

上述应用程序的输出类似于以下内容:

Bean'sensenger'create:org.springframework.scripting.groovy.GroovyMessenger@272961 
org.springframework.scripting.groovy.GroovyMessenger@272961

示例: RequiredAnnotationBeanPostProcessor
将回调接口或注释与自定义BeanPostProcessor实现结合使用 是扩展Spring IoC容器的常用方法。一个例子是Spring RequiredAnnotationBeanPostProcessor…- 一个 BeanPostProcessor随Spring发行版一起提供的实现,它确保标记有(任意)注释的bean上的JavaBean属性实际上(配置为)依赖注入值。

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

我们看到的下一个扩展点是 org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语义与那些接口的语义相似BeanPostProcessor,只有一个主要区别:BeanFactoryPostProcessor对bean配置元数据进行操作。也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据并可能在容器实例化除实例之外的任何bean 之前更改它BeanFactoryPostProcessor。

您可以配置多个BeanFactoryPostProcessor实例,并且可以BeanFactoryPostProcessor通过设置order属性来控制这些实例的运行顺序。但是,如果BeanFactoryPostProcessor实现 Ordered接口,则只能设置此属性。如果你自己编写BeanFactoryPostProcessor,你也应该考虑实现这个Ordered界面。有关更多详细信息,请参阅BeanFactoryPostProcessor和Ordered接口的javadoc

如果要更改实际的bean实例(即,从配置元数据创建的对象),则需要使用a BeanPostProcessor (如前面的使用a定制Bean中所述BeanPostProcessor)。虽然技术上可以在a中使用bean实例BeanFactoryPostProcessor(例如,通过使用 BeanFactory.getBean()),但这样做会导致过早的bean实例化,从而违反标准的容器生命周期。这可能会导致负面影响,例如绕过bean后期处理。

此外,BeanFactoryPostProcessor实例的范围是每个容器的范围。仅当您使用容器层次结构时,这才有意义。如果BeanFactoryPostProcessor在一个容器中定义一个容器,则它仅应用于该容器中的bean定义。BeanFactoryPostProcessor即使两个容器都是同一层次结构的一部分,一个容器中的Bean定义也不会被另一个容器中的实例进行后处理。

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

一个ApplicationContext自动检测部署在它实现了任何豆BeanFactoryPostProcessor接口。它在适当的时候使用这些bean作为bean工厂后处理器。您可以像处理任何其他bean一样部署这些后处理器bean。

与BeanPostProcessors一样,您通常不希望BeanFactoryPostProcessor为延迟初始化配置 s。如果没有其他bean引用a Bean(Factory)PostProcessor,则该后处理器根本不会被实例化。因此,将其标记为延迟初始化将被忽略, Bean(Factory)PostProcessor会急切地实例化,即使你设定的 default-lazy-init属性true对你的声明元素。

示例:类名替换 PropertyPlaceholderConfigurer

您可以使用PropertyPlaceholderConfigurer标准Java Properties格式在单独的文件中使用bean定义中的外部化属性值。这样做可以使部署应用程序的人员自定义特定于环境的属性,例如数据库URL和密码,而不会出现修改主XML定义文件或容器文件的复杂性或风险。

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

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <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>

该示例显示了从外部Properties文件配置的属性。在运行时,a PropertyPlaceholderConfigurer将应用于替换DataSource的某些属性的元数据。要替换的值被指定为表单的占位符${property-name},它遵循Ant和log4j以及JSP EL样式。

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

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

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

使用contextSpring 2.5中引入的命名空间,您可以使用专用配置元素配置属性占位符。您可以在location属性中提供一个或多个位置作为逗号分隔列表,如以下示例所示:

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

在PropertyPlaceholderConfigurer不仅将查找在属性Properties 指定的文件。默认情况下,如果它在指定的属性文件中找不到属性,它还会检查Java System属性。您可以通过systemPropertiesMode使用以下三个受支持的整数值之一设置configurer 的属性来自定义此行为:

  • never (0):永远不检查系统属性。
  • fallback(1):如果在指定的属性文件中无法解析,则检查系统属性。这是默认值。
  • override(2):在尝试指定的属性文件之前,首先检查系统属性。这使系统属性可以覆盖任何其他属性源。

有关PropertyPlaceholderConfigurer更多信息,请参阅javadoc。
您可以使用PropertyPlaceholderConfigurer替换类名称,这在您必须在运行时选择特定实现类时有时很有用。以下示例显示了如何执行此操作:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <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的分辨率,当它即将被创造,这是在失败preInstantiateSingletons() 的阶段ApplicationContext对非延迟实例化的bean。

示例: PropertyOverrideConfigurer
在PropertyOverrideConfigurer另一个bean工厂后置处理器,类似 PropertyPlaceholderConfigurer,但不同的是后者,原来的定义可以有缺省值或者根本没有值的bean属性。如果覆盖 Properties文件没有某个bean属性的条目,则使用默认上下文定义。

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

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

beanName.property=value

以下清单显示了格式的示例:

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

此示例文件可以与包含名为dataSourcehas has driver和urlproperties 的bean的容器定义一起使用 。

复制属性名称也受支持,只要路径的每个组件(重写的最终属性除外)都已经非空(可能由构造函数初始化)。在下面的例子中,sammy所述的属性bob的财产fred的财产tom豆被设置为标量值123:

tom.fred.bob.sammy = 123

指定的覆盖值始终是文字值。它们不会被翻译成bean引用。当XML bean定义中的原始值指定bean引用时,此约定也适用。

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

<context:property-override location="classpath:override.properties"/>

1.8.3 使用a自定义实例化逻辑FactoryBean

您可以org.springframework.beans.factory.FactoryBean为自己工厂的对象实现接口。

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

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

  • Object getObject():返回此工厂创建的对象的实例。实例可以共享,具体取决于此工厂是返回单例还是原型
  • boolean isSingleton():true如果FactoryBean返回单例或false其他方式返回
  • Class getObjectType():返回getObject()方法返回的对象类型,或者null如果事先不知道类型。

该FactoryBean概念和接口被一些Spring框架内的场所。超过50个FactoryBean接口的实现与Spring本身一起提供。

当您需要向容器询问实际的FactoryBean实例本身而不是它生成的bean时,在调用ApplicationContext的getBean()方法时,使用&符号(&)作为bean的id前缀。 因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产品,而调用getBean(“&myBean”)则返回FactoryBean实例本身。

猜你喜欢

转载自blog.csdn.net/hadues/article/details/86303607
今日推荐