`

BeanPostProcessor PropertyPlaceholderConfigurer

阅读更多

3.7. 容器扩展点

Spring框架的IoC容器被设计为可扩展的。通常我们并不需要子类化各个BeanFactoryApplicationContext实现类。而通过plugin各种集成接口实现来进行扩展。下面几节专门描述这些不同的集成接口。

3.7.1. 用BeanPostProcessor定制bean

我们关注的第一个扩展点是BeanPostProcessor接口。它定义了几个回调方法,实现该接口可提供自定义(或默认地来覆盖容器)的实例化逻辑、依赖解析逻辑等。如果你想在Spring容器完成bean的实例化、配置和其它的初始化后执行一些自定义逻辑,你可以插入一个或多个的BeanPostProcessor实现。

如果配置了多个BeanPostProcessor,那么可以通过设置'order'属性来控制BeanPostProcessor的执行次序(仅当BeanPostProcessor实现了Ordered接口时,你才可以设置此属性,因此在编写自己的BeanPostProcessor实现时,就得考虑是否需要实现Ordered接口);请参考BeanPostProcessorOrdered接口的JavaDoc以获取更详细的信息。

注意

BeanPostProcessor可以对bean(或对象)的多个实例进行操作;也就是说,Spring IoC容器会为你实例化bean,然后BeanPostProcessor去处理它。

如果你想修改实际的bean定义,则会用到BeanFactoryPostProcessor(详情见第 3.7.2 节 “用BeanFactoryPostProcessor定制配置元数据”)。

BeanPostProcessor的作用域是容器级的,它只和所在容器有关。如果你在容器中定义了BeanPostProcessor,它仅仅对此容器中的bean进行后置处理。BeanPostProcessor将不会对定义在另一个容器中的bean进行后置处理,即使这两个容器都处在同一层次上。

org.springframework.beans.factory.config.BeanPostProcessor接口有两个回调方法可供使用。当一个该接口的实现类被注册(如何使这个注册生效请见下文)为容器的后置处理器(post-processor)后,对于由此容器所创建的每个bean实例在初始化方法(如afterPropertiesSet和任意已声明的init方法)调用前,后置处理器都会从容器中分别获取一个回调。后置处理器可以随意对这个bean实例执行它所期望的动作,包括完全忽略此回调。一个bean后置处理器通常用来检查标志接口,或者做一些诸如将一个bean包装成一个proxy的事情;一些Spring AOP的底层处理也是通过实现bean后置处理器来执行代理包装逻辑。

重要的一点是,BeanFactoryApplicationContext对待bean后置处理器稍有不同。ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有bean,并把它们注册为后置处理器,然后在容器创建bean的适当时候调用它。部署一个后置处理器同部署其他的bean并没有什么区别。而使用BeanFactory实现的时候,bean 后置处理器必须通过下面类似的代码显式地去注册:

ConfigurableBeanFactory factory = new XmlBeanFactory(...);
            
// now register any needed BeanPostProcessor instances
MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
factory.addBeanPostProcessor(postProcessor);

// now start using the factory

因为显式注册的步骤不是很方便,这也是为什么在各种Spring应用中首选ApplicationContext的一个原因,特别是在使用BeanPostProcessor时。

BeanPostProcessors和AOP自动代理(auto-proxying)

实现了BeanPostProcessor 接口的类是特殊的, 会被容器特别对待. 所有 BeanPostProcessors和直接引用的bean 会作为ApplicationContext一部分在启动时初始化, 然后所有的BeanPostProcessors会注册入一个列表并应用于之后的bean。AOP自动代理实现了BeanPostProcessor,所以BeanPostProcessors或bean的直接引用不会被自动代理(因此不会被aspects"织入")。

对这些bean来说,你可能看到下面的日志信息:Bean 'foo' is not eligible for getting processed by all BeanPostProcessors (如:不能被auto_proxying)

关于如何在ApplicationContext中编写、注册并使用BeanPostProcessor,会在接下的例子中演示。

3.7.1.1. 使用BeanPostProcessor的Hello World示例

第一个实例似乎不太吸引人,但是它适合用来阐述BeanPostProcessor的基本用法。我们所有的工作是编写一个BeanPostProcessor的实现,它仅仅在容器创建每个bean时调用bean的toString()方法并且将结果打印到系统控制台。它是没有很大的用处,但是可以让我们对BeanPostProcessor有一个基本概念。

下面是BeanPostProcessor具体实现类的定义:

package scripting;

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

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

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

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}
<?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-2.5.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.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,Spring2.0动态语言支持的细节请见第 24 章 动态语言支持)。

下面是测试代码:

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 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

3.7.1.2. RequiredAnnotationBeanPostProcessor示例

在Spring的BeanPostProcessor实现中调用标志接口或使用注解是扩展Spring IoC容器的常用方法。对于注解的用法详见第 25.3.1 节 “@Required,这里没有做深入的说明。通过定制BeanPostProcessor实现,可以使用注解来指定各种JavaBean属性值并在发布的时候被注入相应的bean中。

3.7.2. 用BeanFactoryPostProcessor定制配置元数据

我们将看到的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口跟BeanPostProcessor类似,BeanFactoryPostProcessor可以对bean的定义(配置元数据)进行处理。也就是说,Spring IoC容器允许BeanFactoryPostProcessor在容器实际实例化任何其它的bean之前读取配置元数据,并有可能修改它。

如果你愿意,你可以配置多个BeanFactoryPostProcessor。你还能通过设置'order'属性来控制BeanFactoryPostProcessor的执行次序(仅当BeanFactoryPostProcessor实现了Ordered接口时你才可以设置此属性,因此在实现BeanFactoryPostProcessor时,就应当考虑实现Ordered接口);请参考BeanFactoryPostProcessorOrdered接口的JavaDoc以获取更详细的信息。

注意

如果你想改变实际的bean实例(例如从配置元数据创建的对象),那么你最好使用BeanPostProcessor(见上面第 3.7.1 节 “用BeanPostProcessor定制bean”中的描述)

同样地,BeanFactoryPostProcessor的作用域范围是容器级的。它只和你所使用的容器有关。如果你在容器中定义一个BeanFactoryPostProcessor,它仅仅对此容器中的bean进行后置处理。BeanFactoryPostProcessor不会对定义在另一个容器中的bean进行后置处理,即使这两个容器都是在同一层次上。

bean工厂后置处理器可以手工(如果是BeanFactory)或自动(如果是ApplicationContext)地施加某些变化给定义在容器中的配置元数据。Spring自带了许多bean工厂后置处理器,比如下面将提到的PropertyResourceConfigurerPropertyPlaceholderConfigurer以及BeanNameAutoProxyCreator,它们用于对bean进行事务性包装或者使用其他的proxy进行包装。BeanFactoryPostProcessor也能被用来添加自定义属性编辑器。

在一个BeanFactory中,应用BeanFactoryPostProcessor的过程是手工的,如下所示:

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);

因为显式注册的步骤不是很方便,这也是为什么在不同的Spring应用中首选ApplicationContext的原因,特别是在使用BeanFactoryPostProcessor时。

ApplicationContext会检测部署在它之上实现了BeanFactoryPostProcessor接口的bean,并在适当的时候会自动调用bean工厂后置处理器。部署一个后置处理器同部属其他的bean并没有什么区别。

注意

正如BeanPostProcessor的情况一样,请不要将BeanFactoryPostProcessors标记为延迟加载。如果你这样做,Spring容器将不会注册它们,自定义逻辑就无法实现。如果你在<beans/>元素的定义中使用了'default-lazy-init'属性,请确信你的各个BeanFactoryPostProcessor标记为'lazy-init="false"'

3.7.2.1. PropertyPlaceholderConfigurer示例

PropertyPlaceholderConfigurer是个bean工厂后置处理器的实现,可以将BeanFactory定义中的一些属性值放到另一个单独的标准Java Properties文件中。这就允许用户在部署应用时只需要在属性文件中对一些关键属性(例如数据库URL,用户名和密码)进行修改,而不用对主XML定义文件或容器所用文件进行复杂和危险的修改。

考虑下面的XML配置元数据定义,它用占位符定义了DataSource。我们在外部的Properties文件中配置一些相关的属性。在运行时,我们为元数据提供一个PropertyPlaceholderConfigurer,它将会替换dataSource的属性值。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/foo/jdbc.properties</value>
    </property>
</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>

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

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

在Spring 2.5中,context名字空间可能采用单一元素属性占位符的方式(多个路径提供一个逗号分隔的列表)

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

PropertyPlaceholderConfigurer如果在指定的Properties文件中找不到你想使用的属性,它还会在Java的System类属性中查找。这个行为可以通过设置systemPropertiesMode属性来定制,它有三个值:让配置一直覆盖、让它永不覆盖及让它仅仅在属性文件中找不到该属性时才覆盖。请参考PropertiesPlaceholderConfigurer的JavaDoc以获得更多的信息。

类名替代

PropertyPlaceholderConfigurer可以在必须在运行时选择一个特性实现类时可以用来替代类名。例如:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/foo/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.foo.DefaultStrategy</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

如果类在运行时无法变为有效。则这个bean会创建失败(当对非延迟实例化bean执行ApplicationContextpreInstantiateSingletons()方法的情况下)。

3.7.2.2. PropertyOverrideConfigurer示例

另一个bean工厂后置处理器PropertyOverrideConfigurer类似于PropertyPlaceholderConfigurer。但是与后者相比,前者对于bean属性可以有缺省值或者根本没有值。如果起覆盖作用的Properties文件没有某个bean属性的内容,那么将使用缺省的上下文定义。

bean工厂并不会意识到被覆盖,所以仅仅察看XML定义文件并不能立刻知道覆盖配置是否被使用了。在多个PropertyOverrideConfigurer实例中对一个bean属性定义了不同的值时,最后定义的值将被使用(由于覆盖机制)。

Properties文件的配置应该是如下的格式:

beanName.property=value

An example properties file might look like this:

一个properties文件可能是下面这样的:

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

这个示例文件可用在这样一个bean容器:包含一个名为dataSource的bean,并且这个bean有driverurl属性。

注意它也支持组合的属性名称,只要路径中每个组件除了最后要被覆盖的属性外全都是非空的(比如通过构造器来初始化),在下例中:

foo.fred.bob.sammy=123

... the sammy property of the bob property of the fred property of the foo bean is being set to the scalar value 123.

foo bean的fred属性的bob属性的sammy属性被设置为数值123。

3.7.3. 使用FactoryBean定制实例化逻辑

工厂bean需要实现org.springframework.beans.factory.FactoryBean接口。

FactoryBean接口是插入到Spring IoC容器用来定制实例化逻辑的一个接口点。如果你有一些复杂的初始化代码用Java可以更好来表示,而不是用(可能)冗长的XML,那么你就可以创建你自己的FactoryBean,并在那个类中写入复杂的初始化动作,然后把你定制的FactoryBean插入容器中。

FactoryBean接口提供三个方法:

  • Object getObject():返回一个由这个工厂创建的对象实例。这个实例可能被共享(取决于isSingleton()的返回值是singleton或prototype)。

  • boolean isSingleton():如果要让这个FactoryBean创建的对象实例为singleton则返回true,否则返回false。

  • Class getObjectType():返回通过getObject()方法返回的对象类型,如果该类型无法预料则返回null。

在Spring框架中FactoryBean的概念和接口被用于多个地方;在本文写作时,Spring本身提供的FactoryBean接口实现超过了50个。

最后,有时需要向容器请求一个真实的FactoryBean实例本身,而不是它创建的bean。这可以通过在FactoryBean(包括ApplicationContext)调用getBean方法时在bean id前加'&'(没有单引号)来完成。因此对于一个假定id为myBeanFactoryBean,在容器上调用getBean("myBean")将返回FactoryBean创建的bean实例,但是调用getBean("&myBean")将返回FactoryBean本身的实例。

分享到:
评论

相关推荐

    Spring 总结(1) 自用

    BeanPostProcessor允许在bean初始化前后进行自定义操作,而BeanFactoryPostProcessor则在bean加载但未实例化之前执行特殊处理,例如PropertyPlaceholderConfigurer可以用于处理属性文件中的占位符替换。 总的来说,...

    Spring源码学习四:BeanDefinition装载前奏曲1

    例如,`PropertyPlaceholderConfigurer`就是在这里处理属性占位符的替换。 `registerBeanPostProcessors(beanFactory)`注册了BeanPostProcessors,这些处理器会在Bean初始化时介入,执行额外的操作,如AOP代理、...

    spring学习笔记2

    - `PropertyPlaceholderConfigurer`:用于解析属性占位符,如`${property}`,将其替换为实际的属性值。 - `CustomEditorConfigurer`:允许注册自定义编辑器,用于将字符串转换为特定类型的数据。 3. **事件处理**...

    Spring In Action笔记100例

    可以使用`PropertyPlaceholderConfigurer`来载入属性文件,并在其他地方使用`${database.url}`等方式引用属性值。 #### 11. `CustomEditorConfigurer`的使用 通过`CustomEditorConfigurer`可以注册自定义的`...

    spring-reference

    `PropertyPlaceholderConfigurer`是一个BeanFactoryPostprocessor,用于在运行时替换配置文件中的占位符。 ##### 3.8.2 PropertyOverrideConfigurer `PropertyOverrideConfigurer`也是BeanFactoryPostprocessor的一...

    SPRING中文开发参考手册

    - **PropertyPlaceholderConfigurer**:用于解析配置文件中的占位符。 - **PropertyOverrideConfigurer**:用于覆盖 Bean 的属性值。 ##### 3.7 注册附加的定制 PropertyEditor - **PropertyEditor**:可以注册...

    《Spring in Action》学习点滴

    - `BeanPostProcessor`接口的两个主要方法`postProcessBeforeInitialization`和`postProcessAfterInitialization`分别在bean初始化前后被调用。 - 实现此接口的类需要注册到`BeanFactory`上,通常可以通过代码注册...

    Spring管理的Bean的生命周期

    2. **BeanPostProcessor接口**:实现这个接口的类可以定义两个方法`postProcessBeforeInitialization()`和`postProcessAfterInitialization()`,分别在Bean初始化前和后进行处理。 3. **@PostConstruct注解**:在...

    Spring_0200_IOC_Introduction setter注入

    这一过程涉及到Spring的`PropertyPlaceholderConfigurer`、`BeanPostProcessor`、`InstantiationAwareBeanPostProcessor`等接口和类。 **工具的使用** Spring提供了多种方式来加载配置,例如XML配置文件、Java配置...

    spring-reference.pdf

    2. **BeanPostProcessor**:BeanPostProcessor是一个特殊的Bean,它可以在Bean的实例化前后进行拦截操作,常用于自定义Bean的初始化逻辑。 3. **BeanFactoryPostProcessor**:BeanFactoryPostProcessor是在...

    spring

    - **PropertyPlaceholderConfigurer** 和 **PropertyOverrideConfigurer**:用于在运行时动态替换Bean配置中的占位符。 ### 结论 Spring框架以其强大的功能和灵活性,成为Java企业级应用开发的首选框架之一。通过...

    spring-reference1.2.pdf

    - **使用BeanPostProcessors自定义Bean**:BeanPostProcessor接口允许开发者自定义Bean的初始化过程。 #### 3.7 使用BeanFactoryPostProcessors自定义Bean工厂 - **PropertyPlaceholderConfigurer**:用于替换配置...

    开源框架 Spring Gossip

    不使用XML定义档进行 Bean设置 Aware 相关介面 BeanPostProcessor BeanFactoryPostProcessor PropertyPlaceholderConfigurer PropertyOverrideConfigurer CustomEditorConfigurer ...

    SPRING API 2.0.CHM

    PropertyPlaceholderConfigurer PropertyResourceConfigurer PropertyValue PropertyValues PropertyValuesEditor PrototypeAspectInstanceFactory PrototypeTargetSource ProxyConfig ProxyFactory ...

    Spring-Reference_zh_CN(Spring中文参考手册)

    PropertyPlaceholderConfigurer示例 3.7.2.2. PropertyOverrideConfigurer示例 3.7.3. 使用FactoryBean定制实例化逻辑 3.8. ApplicationContext 3.8.1. 利用MessageSource实现国际化 3.8.2. 事件 3.8.3. 底层资源的...

    spring-framework-reference4.1.4

    Not Using Commons Logging ................................................................... 12 Using SLF4J ..............................................................................................

    spring-framework-reference-4.1.2

    Not Using Commons Logging ................................................................... 12 Using SLF4J ..............................................................................................

Global site tag (gtag.js) - Google Analytics