高级bean装配
相对上篇,这次内容有些不太常用,但有些还是非常有用,不能忽略。掌握之后对理解spring的源码也很有好处。
一、父bean和子bean
bean也可以继承,为此spring提供了两个属性:
parent: 指明bean的id.它对于<bean>的作用就相当于java关键字extends(但功能又有所不同,后面会看到子bean可以和父bean具有不同的class类型).
abstract: 如果设置为true,就表示<bean>是抽象的,不能被实例化,一般作为父bean.但父bean不一定就是抽象的。
通过使用parent属性可以指定父bean,子bean可以从父bean继承<property>和class属性,无法继承depends-on, autowire, dependency-check, singleton, scope, lazy-init等属性。
如:
<bean id=”fatherBean” class=”com.Instrumentalist” abstract=”true”>
<property name=”instrument” ref=”saxophone” />
<property name=”song” value=”jingle bells” />
</bean>
作为父bean,abstract=”true”不是必须的,这里只是说这个bean不能实例化,只提供公共属性。
<bean id=”kenny” parent=”fatherBean” />
<bean id=”david” parent=”fatherBean” />
这两个子bean都继承了父bean的属性。同时,子bean还可以覆盖父bean的属性,如:
<bean id=”david” parent=”fatherBean” >
<property name=”song” value=”young” /> ------这里就覆盖了song属性。
</bean>
Sping里,子bean还可以不必具有相同的父类型,两个class属性不同的bean仍然可以从一个父bean继承一组相同的属性。
如:
<bean id=”fatherBean” abstract=”true” >
<property name=”song” value=”rainbow” />
</bean>
<bean id=”taylor” class=”com.spring.Vocalist” parent=”fatherBean” />
<bean id=”stevie” class=”com.spring.Instrument” parent=”fatherBean” />
这两个子bean具有不同的class类型,只是继承了父类共同的属性。
二、Bean的方法注入
上一篇介绍了bean的常用注入方式,setter和constructor方式,这里介绍下第三种注入方式,方法注入。上一篇中提到了factory-method使用静态方法注入bean,下面介绍下静态工厂方法注入,实例工厂方法注入和Spring支持的另外两种形式的方法注入:
方法替换: 可以在运行时用新的实现替换现有方法(抽象或具体的)。
获取器注入:可以在运行时用新的实现替换现有方法(抽象或具体的),从spring上下文返回特定的bean。
1)静态工厂方法注入(factory-method)
? <bean id="foo" factory-method="getInstance"/>
对于bar这个bean, class指定静态方法工厂类,factory-method指定工厂方法名称,必须是static的,然后容器调用该静态方法工厂类的指定工厂方法(getInstance),并返回方法调用后的结果,即BarInterfaceImpl的实例。也就是说,为foo注入的 bar实际上是BarInterfaceImpl的实例,即方法调用后的结果,而不是静态工厂方法类 (StaticBarInterfaceFactory)。我们可以实现自己的静态工厂方法类返回任意类型的对象实例,但工厂方法类的类型与工厂方法返回 的类型没有必然的相同关系。
某些时候,有的工厂类的工厂方法可能需要参数来返回相应实例,而不一定非要像我们的getInstance()这样没有任何参数。对于这种情况,可以通过<constructor-arg>来指定工厂方法需要的参数
? <bean id="bar" factory-bean="barFactory" factory-method="getInstance"/>
NonStaticBarInterfaceFactory是作为正常的bean注册到容器的,而bar的定义则与静态工厂方法的定义有些不同。现在使用factory-bean属性来指定工厂方法所在的工厂类实例,而不是通过class属性来指定工厂方法所在类的类型。指定工厂方法名则相同,都是通过factory-method属性进行的,有了factory-bean指定的实例工厂类,这里factory-method指定的工厂方法就不需要一定是static的了。
如果非静态工厂方法调用时也需要提供参数的话,处理方式是与静态的工厂方法相似的,都可以通过<constructor-arg>来指定方法调用参数。
还可以参考:http://book.51cto.com/art/200908/147088.htm
3)方法替换(replaced-method)
<bean id=”magicBox” class=”com.MagicBoxImpl”>
<replaced-method name=”getContents” replacer=”tigerReplacer” />
</bean>
<bean id=”tigerReplacer” class=”com.TigerReplacer” />
本例中replaced-method的name属性指定magicBox的getContents()方法将被替换,替换成tigerReplacer的reimplement方法。tigerReplacer实现了spring的MethodReplacer接口,需要实现此接口的reimplement方法。
4) 获取器注入(lookup-method)
Spring利用<lookup-method>配置元素来实现获取器注入。
<bean id=”stevie” class=”com.spring.Instrumentalist”>
<lookup-method name=”getInstrument” bean=”guitar” />
</bean>
<bean id=”guitar” class=”com.Guitar” />
<lookup-method>的name属性指明了Instrumentalist的getInstrument()方法要被替换,bean=”guitar”指明getInstrument()方法替换后会返回bean(id=”guitar”)。任何返回值不是void的方法都可以被替换掉。
这个是利用cglib实现的,
可以参考:http://www.360doc.com/content/07/0327/13/18042_416051.shtml
当<bean id=”guitar” class=”com.Guitar” scope=”prototype” />bean是原型作用域时,lookup-mehtod替换掉的方法返回值每次获取的都是不同的guitar。这一点很有用,当singleton的bean依赖非singleton的bean时,用普通的注入方式则只会注入一次,每次获取的非singleton类型的bean也都一样了,这时用lookup-mehtod方法注入就对了。
5)factorybean
Spring中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean。工厂Bean跟普通Bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂Bean的getObject方法所返回的对象。在Spring框架内部,AOP相关的功能及事务处理中,很多地方使用到工厂Bean。
所谓FactoryBean就是指实现了spring的org.springframework.beans.factory.FactoryBean接口的bean类。配置该bean时跟普通bean一样,没有任何不同。
当配置文件中<bean>的class属性配置的实现类是FactoryBean时,通过getBean方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。这样就可以实现自定义功能的bean了,比如把一个特殊字符串解析并注入到一个对象属性里。
还可以参考:http://wenku.baidu.com/view/ad5b414ef7ec4afe04a1dfa7.html
三、 注入非sping创建的bean(使用@Configurable),省略不记录了。
四、注册自定义属性编辑器
举个普通的例子
<bean id=”aa” class=”…”>
<constructor-arg type=”int” value=”15” />
<constructor-arg type=”java.lang.String” value=”15” />
</bean>
这里两个属性值都是15,类型一个是int,一个是String,即使不指明type(如果bean的构造函数唯一),spring也会自动解析正确类型。但还有些复杂类型spring也能自动解析,比如java.net.URL。可以直接配置:
<property name=”wsdlDocUrl” value=http://www.service.wsdl />
这是因为spring内部实现了一些类,这些类实现了java自带的java.beans.ProperyEditor接口。Java.beans.PropertyEditorSupport类是java自己提供的实现了该接口的类,我们自己(包括spring也是)直接继承PropertyEditorSupport类(根据需要重写setAsText或getAsText方法,一般给属性注入值只需要重写setAsText方法)就可以实现一些自己的属性编辑器类,然后注册到spring,这样就可以提供一些自动解析复杂对象的功能了。
Spring提供了几个自定义属性编辑器:
自定义属性编辑器的步骤:
1.继承PropertyEditorSupport类,根据需要重写setAsText或getAsText方法/
2.注册到spring中。使用CustomEditorConfigurer注册,这是一个BeanFactoryPostProcessor。
还可以参考:
http://www.cnblogs.com/rollenholt/archive/2012/12/27/2835191.html
这个里面用到了spring自带的属性编辑器
org.springframework.beans.propertyeditors.CustomDateEditor。
http://blog.chinaunix.net/uid-21227800-id-65926.html
这个里面可以参考写出自己的属性编辑器。
注:自定义属性编辑器和factorybean有类似的功能,都可以以灵活的方式给bean注入复杂属性,但又有区别,这个可以根据实际使用场景决定使用哪个。
还可以参考:http://blog.csdn.net/intlgj/article/details/5662399
五、使用spring的特殊bean
有些特殊的bean被当作spring框架本身的一部分,可以:
@通过对bean配置的后处理来介入bean的创建和bean工厂的生命周期。
@从外部属性文件加载配置信息。
@从属性文件加载文本信息,包括国际化的消息。
@监听和响应由其他bean和spring容器本身发布的程序事件。
@知道它们在spring容器里的身份。
1.后处理bean
在第二篇《基本bean配置》里有bean的生命周期图,图中可以看到spring提供了两个机会让我们可以介入bean的生命周期(这两个机会是调用BeanPostProcessor的预初始化方法和后初始化方法。),查看或改变它的配置,这杯成为“后处理”。所谓后处理就是bean的创建和装配之后来修改。BeanPostProcessor接口为我们提供了这两个机会。
postProcessBeforeInitialization()方法是在bean初始化之前被调用(即InitializingBean的afterPropertiesSet()方法和bean的自定义init-method)之前被调用的。类似的,postProcessAfterinitialization()方法是在初始化之后立即被调用的。
实现步骤:
1)自定义类实现BeanPostProcessor接口,实现该接口的两个方法。
2)如果bean是在BeanFactory中,则需要编写代码,使用工厂的addBeanPostProcessor注册每个BeanPostProcessor.
如果是使用的上下文ApplicationContext,则只需要在容器里把自定义类定义为一个普通bean就可以了,容器会自动识别。这也是第一篇导读里说的ApplicationContext的更多的强大功能。
Spring框架在私下使用了BeanPostProcessor的多个实现,比如
ApplicationContextAwareProcessor就是一个BeanPostProcessor.
2.bean工厂的后处理
bean被加载后,当BeanPostProcessor对它进行后处理时,BeanFactoryPostProcessor(只能是针对上下文容器,不能是beanFactory容器)对整个spring容器进行后处理。
在全部bean定义被加载后(加载,是在实例化之前的步骤),在任何一个bean被实例化之前,spring容器会调用postProcessBeanFactory()方法。
前面出现过的自定义编辑器CustomEditorConfigurer就是BeanFactoryPostProcessor的一个实现。
BeanFactoryPostProcessor另一个很有用的实现是PropertyPlaceholderConfigurer,它从一个或多个外部属性文件(.properties文件)加载属性,还可以填充bean装配文件里的占位变量(如:${url}).
注:所有相关BeanFactoryPostProcessor的应用都只能在上下文容器(ApplicationContext,不是BeanFactory)中使用。
如:
可以配置成:
<bean id=”propertyConfigurer”
class=”org.springframework.beans.factory.config.PropertyPlaceholderConfigurer”>
<property name=”location” value=”jdbc.properties” />
</bean>
Location属性告诉spring到哪寻找属性文件。如果指定多个文件还可以使用:
<property name=”locations”>
<list>
<value> jdbc.properties</value>
<value> security.properties</value>
</list>
</property>
本例中,jdbc.properties包含下面信息:
Database.url=jdbc:hsqldb:Training
Datebase.driver=org.hsqldb.jdbcDriver
Database.user=appUser
Database.password=password
现在就可以定义dataSource了:
这样就可以不用硬编码了。
注:除了可以向上面那样指定属性文件,还可以使用Spring 2.5引进Context命名空间, 可以通过context:property-placeholder元素进行配置。
可以参考:http://www.2cto.com/kf/201211/167827.html
3.国际化信息支持
可以通过spring提供的MessageSource接口实现国际化信息。Spring提供了默认的MessageSource实现,ResourceBundleMessageSource等。
这里不详细记录了,后面有问题时再详细研究。
可参考:
http://beastiedog.blog.163.com/blog/static/23984625200642224329415/
http://hi.baidu.com/iduany/item/d52245ab11021611a9cfb771
http://blog.csdn.net/xiongyayun428/article/details/7063835
http://blog.csdn.net/xiongyayun428/article/details/7062772
4.发布事件和监听事件
依赖注入是Spring提高程序对象之间松耦合的主要手段,但并不唯一。对象之间交互的另一种方式是发布和监听程序事件。
在spring里,容器中的任何bean都可以作为事件监听者或发布者或两者都是。
1)创建事件,只需要实现ApplicationEvent类。
2)发布事件,利用ApplicationContext(这就要求bean能获取到上下文,后面会讲怎么获取)接口的publishEvent()方法可以发布ApplicationEvents。
除了bean要发布的事件,spring容器本身就会发布一些时间,比如:
ContextCloseEvent:程序上下文关闭事件。
ContextRefreshedEvent:程序上下文被初始化或刷新事件。
RequestHandleEvent:当一个请求被处理时,在web上下文里发布。
3)创建监听器,只需要实现ApplicationListener接口,这个接口要求bean实现onApplicationEvent()方法来响应事件。
4)注册监听器,只需要创建监听器的bean,如:
<bean id=”refreshListener” class=”com.spring.foo.ResfreshListener” />
当容器在程序上下文里加载这个bean时会注意到它实现了ApplicationListener,并会在时间被发布时调用它的onApplicationEvent()方法。
注:程序事件是同步处理的,因此对事件的处理要迅速,否则会降低程序的性能。
Bean必须要了解容器(即能获取到容器实例)才能发布事件。
5 让bean了解容器
1)让bean了解bean自己的名称
通过实现BeanNameAware接口,告诉bean它自己的名字。这个接口只有一个setBeanName()方法。只要实现了这个接口,bean被加载时,容器会检测到它实现了BeanNameAware,然后自动调用setBeanName,把<bean>元素里的id或name属性传递过去。
3)让bean了解所在的容器
跟上面类似,只要bean实现了ApplicationContextAware和BeanFactoryAware接口就可以了。
6.spring里还可以内嵌脚本化的bean,可以是非java实现的bean,没有研究。不梳理了。