Spring IOC之容器扩展点

时间:2024-10-01 17:04:32

一般来说,一个应用开发者不需要继承ApplicationContext实现类。取而代之的是,Spring IoC容器可以通过插入特殊的整合接口的实现来进行扩展。下面的几点将要讲述这些整合的接口。

1.使用BeanPostProcessor来定制bean

BeanPostProcessor接口定义了你可以实现的回调方法去提供自己的实例化逻辑、依赖方案逻辑等等。如果你想在Spring 容器完成实例化配置和初始化一个bean之后实现一个自定义逻辑,你可以插入一个或者多个BeanPostProcessor实现。

你可以配置多个BeanPostProcessor实例,你可以通过设置order属性来控制这些BeanPostProcessor运行的顺序。如果你写自己的BeanPostProcessor你应该考虑实现ordered接口。

注意:BeanPostProcessors作用在一个bean实例上,也就是说,Spring IOC容器实例化一个bean实例然后BeanPostProcessors会在他们上面起作用。BeanPostProcessor在每一个容器中都是受限制域的。如果你使用容器分层结构的话这个是唯一有关系的。如果你在一个容器中定义一个BeanPostProcessor接口,它将知识处理在容器中的bean。换句话说,在一个容器中定义的bean并不能被定义在其他容器中的BeanPostProcessor来处理,即使两个容器时同一个层次的一部分。为了改变实际的bean定义,你需要使用一个BeanFactoryPostProcessor。

org.springframework.beans.factory.config.BeanPostProcessor 由两个回调方法组成。当这样的类在容器中被注册为一个post-processor时,对于任何一个在容器中创建的bean实例,post-processor从容器中获得一个回调,在容器初始化方面之前的两个方法(afterPropertiesSet和init)也被称为在任何bean初始化回调。post-processor能够对任何一个实例进行操作,包括完全忽略回调。一个bean post-processor一般会将会检查回调接口或者是用一个代理来封装bean。为了提供一个代理封装逻辑一些Spring AOP框架类作为bean post-processor来实现。

一个ApplicationContext能够发现实现了BeanPostProcessor接口的定义在配置中的任何bean。ApplicationContext注册这些bean作为post-processor以便他们在bean创建后可以被调用。bean post-processor就像其他bean一样被部署在容器中。

注意当在一个配置类中使用@Bean工厂方法声明一个BeanPostProcessor的时候,工厂方法的返回类型应该实现类自己或者至少是org.springframework.beans.factory.cofig.BeanPostProcessor接口,明确的表明了那个bean的是post-processor。否则,ApplicationContext在完全创建它之前不会通过类型自动发现它,因为为了在上下文中引用到其他bean的初始化一个BeanPostProcessor需要去实例化早些,而且这个早一点的声明是很关键的。

下面就是表明在一个ApplicationContext中定义注册和使用BeanPostProcessors.

1.1例子:Hello world,BeanPostProcessor风格的

第一个例子只是简单地使用。这个例子表明一个自定义的BeanPostProcessor实现当它被容器创建的时候调用了toString()方法,并且打印出结果到控制台。

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.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一样DI。

下面的简单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 messenger created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

1.2 例子:RequiredAnnotationBeanPostProcessor

使用回调接口或者注解和一个自定义的BeanPostProcessor实现联合在一起是常用的方法用来扩展Spring Ioc容器。这个例子是Spring的conjunction-一个BeanPostProcessor实现,它可以保证在bean中JavaBean属性被标记注解的能够用一个值来进行依赖注入。

2.使用BeanFactoryPostProcessor来自定义配置数据

下面我们来看下org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语法和那些BeanPostProcessor类似,有一点不同是:BeanFactoryPostProcessor作用在bean配置数据上;也就是说SpringIOC容器运行一个BeanFactoryPostProcessor去读取配置数据并且在实例化任何不适BeanFactoryPostProcessor的bean之前修改它的配置。

你可以配置多个BeanFactoryPostProcessors,而且你通过设置orde的顺序来控制BeanFactoryPostProcessors运行的顺序。但是,如果BeanFactoryPostProcessors实现了ordered接口你才能看到这个属性。你可以写自己的BeanFactoryPostProcessors,你应该考虑实现ordered接口。

一个bean工厂post-processor会被自动运行当在一个ApplicationContext内部声明的时候,这样做是为了将改变应用到定义容器的配置数据中。SPring包括很多提前定义好的bean 工厂 post-processors,比如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。一个自定义的BeanFactoryPostProcessor也是能够被使用的,例如为了注册定义的的属性编辑器。

一个ApplicationContext能够自动发现实现了BeanFactoryPostProcessor接口的部署在它里面的任何bean。它使用这些bean作为bean 工厂 post-processor在适当的时机。你可以

2.1例子:类名称代替PropertyPlaceholderConfigurer

你能够从使用标准Java 属性格式的定义在其他文件中的bean定义中使用PropertyPlaceholderConfigurer去具体化属性值。这样做,可以使用户在部署应用的时候自定义环境变量的属性,比如数据库url 和密码,而不用修改XML定义的文件。

看下面的基于XML格式配置的片段,这里面DataSource使用placeholder值来定义的。这个例子表明从外部属性文件中配置的属性使用。在运行的时候,一个PropertyPlaceholderConfigurer被应用在将替换数据源属性的元数据。需要替换的值需要被指明是placeholders的格式${property-name},这个和Ant / log4j / JSP EL 风格类似。

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

在其他文件中标准Java属性格式的实际值:

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

因此,字符串 ${jdbc.username}在运行时被替换成值sa,这个也会应用在属性文件中其他的匹配的值上。PropertyPlaceholderConfigurer在大部分的属性和bean定义的 属性中会检查placeholders的。更多的话placeholders的前缀和后缀也能够被自定义。

在Spring 2.5中被引用的context 命名空间,这样可以使用指定的配置元素来定义属性placeholders。一个多个位置可以用逗号分隔成一个列表的位置属性。

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

PropertyPlaceholderConfigurer不仅仅寻找你知道的属性文件中的属性。默认情况下,它还好检查Java系统属性如果在指定的属性文件中找不到的属性的话。你可以通过设置配置器的systemPropertiesMode为下面三个之一支持的属性整数值:

  • never(0):永远不要检查系统属性

  • fallback(1):如果在指定的属性文件中没有可供解析的话检查系统属性值。这个是默认选项。

  • override(2):在你查询指定的属性文件之前首先查询系统属性。这样运行系统属性能够覆盖任何其他的属性源。

    注意:你可以使用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</v alue>
    </property>
    </bean>
    <bean id="serviceStrategy" class="${custom.strategy.class}"/>

    如果在运行时类不能够解析为一个合法的类,当它将要被创建时bean的解析就会失败,这个是在一个非懒加载bean的在一个ApplicationContext的preInstantiateSingletons()的相位之间。

2.2例子:PropertyOverrideConfigurer

PropertyOverrideConfigurer,另外的bean 工厂 post-processor,和PropertyPaceholderConfigurer类似,但是又不像,因为原始的定义有默认值或者对于bean属性没有值。如果一个覆盖的Properties文件对于特定的bean属性没有输入值的话,缺省的上下文定义就会被使用了。

注意Bean定义不知道被覆盖了,所以在覆盖的配置在被使用的时候,XML定义的文件不是立即就知道的。在有多个PropertyOverrideConfigurer实例的清下中,可以为同一个bean属性值定义不同的值,最后一个起作用,这个取决于覆盖机制。

属性文件配置像下面的格式:

beanName.property=value

例如:

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

这个例子在一个包含一个叫做数据源的bean的容器定义中使用,数据源包括驱动和url属性。

混合属性名字也是支持的,只要每一个路径中的组件希望的最后的属性被覆盖的不是非空。这个例子有:

foo.fred.bob.sammy=123

foo bean的fred属性的 bob属性的 sammy属性值是123。

使用Spring 2.5中的context命名空间,可以去配置属性覆盖通过一个致命的配置元素:

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

3.使用一个工厂bean来定制实例化逻辑

实现了org.springframework.beans.factory.FactoryBean接口的对象都是他们自己的工厂。

FactoryBean接口可以插入到Sring IOC 容器实例化逻辑的一个点。如果你有复杂的处理话代码,那么最好在Java代码中表示出来不要使用大量冗杂的XML,你可以创建自己的FactoryBean,写类内部写负责的初始化,然后将自己定制的FactoryBean放入容器中。

FactoryBean接口提供了三个方法:

  • Object getObject() : 返回这个工厂茶UN该敬爱年的 对象实例。这个实例能够被共享,这个取决于工厂返回的是单例还是原型。
  • boolean isSingleton() :如果FactoryBean返回单例就是返回true否则就是false;
  • Class getObjectType() : 返回通过getObject() 方法返回的对象类型,如果类型是未知的那么会返回null.

FactoryBeande 的接口在Spring框架中的很多地方都用到了,Spring自己有查过50个FactoryBean的接口实现。

当你需要去向容器请求一个它自己的真正的FactoryBean实例而不是它产生的bean,在bean的 id前面加上&在调用ApplicationContext的getBean()方法时,加入现在给定一个FactoryBean的id是myBean,调用getBean("myBean")将返回FactoryBean的产生的对象;但是调用getBean("&myBean")将会返回FactoryBean自己的实例