在这一部分,将学习Spring IOC容器的高级特性和内部机制,这将帮助我们提高开发Spring应用的效率。尽管这些特性可能不是经常使用,但它是全面和强大的容器必备的。也是Spring框架的其他模块的基础。
调用静态工厂方法创建Bean
问题
当你打算调用一个静态工厂方法在Spring IOC容器创建一个Bean,静态工厂方法的目的是在静态方法中封装创建过程。请求一个对象的客户只要调用这个方法,不需要了解创建的细节。
解决方案
Spring支持调用一个静态工厂方法创建Bean,这个方法应该在factory-method属性中指定。
工作原理
例如,可以编写ProductCreator.createProduct()静态工厂方法从一个预先定义的产品ID常见一种产品。
package com.zy.IOC;
/**
* Created by leon on 2017/4/13.
*/
public class ProductCreator {
public static Product createProduct(String productId){
if("aaa".equals(productId)){
return new Battery("AAA",2.5);
}else if("cdrw".equals(productId)){
return new Disc("CD-RW",1.5);
}
throw new IllegalArgumentException("Unknown product");
}
}
声明一个静态工厂方法的创建的Bean,需要在Class属性中指定工厂方法的宿主类,在factory-method属性中指定工厂方法名称。最后,使用 <constructor-arg>
元素传递方法的参数。
<bean id="aaa" class="com.zy.IOC.ProductCreator" factory-method="createProduct">
<constructor-arg value="aaa"/>
</bean>
<bean id="cdrw" class="com.zy.IOC.ProductCreator" factory-method="createProduct">
<constructor-arg value="cdrw"/>
</bean>
如果工厂方法抛出异常,Spring将用BeanCreationException对其进行封装。
调用一个实例工厂方法创建Bean
解决方案
Bean实例在factory-bean 属性中指定,而工厂方法应该在factory-method属性中指定。
public class ProductCreator {
private Map<String,Product> map;
public void setMap(Map<String, Product> map) {
this.map = map;
}
public Product createProduct1(String productId){
Product product = map.get(productId);
if(product != null){
return product;
}
throw new IllegalArgumentException("Unknown product");
}
}
<bean id="productCreator" class="com.zy.IOC.ProductCreator">
<property name="map">
<map>
<entry key="aaa">
<bean class="com.zy.IOC.Battery">
<property name="productId" value="AAA"/>
<property name="price" value="2.5"/>
</bean>
</entry>
<entry key="cdrw">
<bean class="com.zy.IOC.Disc">
<property name="productId" value="CD-RW"/>
<property name="price" value="1.5"/>
</bean>
</entry>
</map>
</property>
</bean>
<bean id="aaa" class="com.zy.IOC.ProductCreator" factory-bean="productCreator"
factory-method="createProduct1">
<constructor-arg value="aaa"/>
</bean>
<bean id="cdrw" class="com.zy.IOC.ProductCreator" factory-bean="productCreator"
factory-method="createProduct1">
<constructor-arg value="cdrw"/>
</bean>
从静态字段中声明Bean
问题
当你打算从一个静态字段中声明IOC容器的一个Bean。在java中,常量值旺旺声明为静态字段。
解决方案
从静态字段声明Bean,可以使用内奸的工厂BeanFieldRetrievingFactoryBean, 或者<util:content>
标记。
首先在Product类定义两个产品常量
public class Product {
public static final Product AAA = new Battery("AAA",2.5);
public static final Product CDRW = new Disc("CD-RW",1.5);
}
使用内建的工厂BeanFieldRetrievingFactoryBean,在static属性中指定完全限定名。
<bean id="aaa" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField">
<value>com.zy.IOC.Product.AAA</value>
</property>
</bean>
<bean id="cdrw" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField">
<value>com.zy.IOC.Product.CDRW</value>
</property>
</bean>
或者使用
<util:constant id="aaa" static-field="com.zy.IOC.Product.AAA"/>
<util:constant id="cdrw" static-field="com.zy.IOC.Product.CDRW"/>
从对象属性中声明Bean
问题
当你打算从一个对象属性或嵌套属性(也就是属性路径)中声明Spring IOC容器中的一个Bean。
解决方案
可以使用内建的BeanPropertyPathFactoryBean或者<util:property-path>
标记
创建ProductRanking类
public class ProductRanking {
private Product bestSeller;
public Product getBestSeller() {
return bestSeller;
}
public void setBestSeller(Product bestSeller) {
this.bestSeller = bestSeller;
}
}
按以下方式
<bean id="productRanking" class="com.zy.IOC.ProductRanking">
<property name="bestSeller">
<bean class="com.zy.IOC.Disc">
<property name="productId" value="CD-RW"/>
<property name="price" value="2.5"/>
</bean>
</property>
</bean>
<bean id="bestSeller" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<property name="targetBeanName" value="productRanking"/>
<property name="propertyPath" value="bestSeller"/>
</bean>
<!--或-->
<!--<util:property-path id="bestSeller" path="productRanking.bestSeller"/>-->
可采用代码测试
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Product bestSeller = (Product) context.getBean("bestSeller");
System.out.println(bestSeller);
}
}
设置Bean作用域
问题
当你在配置文件声明Bean时,实际定义了Bean创建的一个模板,而不是实际的Bean实例。当getBean()方法或者其他Bean的一个引用请求Bean时,Spring将根据Bean作用域(Scope)确定应该放回的Bean实例,有时候,必须为Bean设置正确的作用域而不是默认的作用域。
解决方案
Bean的作用域在bean标签的scope属性中设置。默认情况,Spring为IOC容器中声明的每个Bean创建一个实例,这个实例在整个IOC容器范围内共享。所有后续的getBean()调用和Bean引用都将放回这个独特的Bean实例,这种作用域(默认)称为singleton。下表列出所有有效的Bean作用域:
作用域 | 描述 |
---|---|
Singleton | 每个Spring IOC容器中创建一个Bean实例 |
Prototype | 每次请求时创建一个新的Bean实例 |
Request | 为每个Http请求创建一个Bean实例,仅在Web应用上下文中有效 |
Session | 为每个Http会话创建一个Bean实例,仅在Web应用上下文中有效 |
GlobalSession | 为全局Http会话创建一个Bean实例,仅在Web应用上下文中有效 |
以考虑购物车为例,首先创建了ShoppingCar类,在IOC容器中声明为Bean
前面Bean的声明的结果是,两个顾客取得是用一个购物车实例:
只需将bean的scope属性设为”prototype”即可解决以上问题,即不用的顾客获得不同的购物车实例。
自定义Bean初始化和析构
问题
许多现实中的组件的使用之前都必须进行某种初始化任务。这种任务包括打开文件、打开网络/数据库连接、分配内存等。等自检的生命周期结束时,也必须执行相应的析构任务。所以,就有在Spring IOC容器中定义Bean初始化和析构的需求。
解决方案
除了Bean的注册之外,Spring IOC容器还负责管理Bean的生命周期,允许你在它们的生命期特定时点执行自定义任务。你的任务应该封装在回调方法中,由Spring IOC容器在合适的时候调用。
下面的列表展示了IOC容器管理bean的周期步骤。这个列表将随着IOC容器更多特性的引入而扩展。
1. 构造程序或者工厂方法创建Bean实例
2. 向Bean属性设置值和Bean引用
3. 调用初始化回调方法
4. Bean就绪
5. 容器关闭时,调用析构回调方法
Spring有三种识别初始化和析构回调方法的方式。
第一,你的Bean可以实现InitalizingBean和DisposableBean生命周期接口,并实现用于初始化和析构的afterPropertiesSet()和destory()方法。
第二,可以Bean声明中设置init-method和destory-method属性,指定回调方法名称。
第三,在Spring2.5或更高版本中,还可以使用 生命周期注解@PostConstruct和@PreDestroy注解初始化和析构回调方法。然后可以再IOC容器注册CommonAnnotationBeanPost Processor实例来调用这些回调方法。
<bean id="cashier1" class="com.zy.IOC.Cashier">
<property name="name" value="cashier1"/>
<property name="path" value="/Users/leon/test/1.txt"/>
</bean>
public class Cashier implements InitializingBean,DisposableBean{
private String name;
private String path;
private BufferedWriter writer;
public void afterPropertiesSet() throws Exception {
openFile();
}
public void destroy() throws Exception {
closeFile();
}
* 设置init-method 和 destory-method属性 *
推荐使用这用方法,此时Cashier类就不在需要实现InitializingBean和DisposableBean接口
<bean id="cashier1" class="com.zy.IOC.Cashier" init-method="openFile" destroy-method="closeFile">
<property name="name" value="cashier1"/>
<property name="path" value="/Users/leon/test/1.txt"/>
</bean>
* 使用@PostContstruct 和 @PreDestory注解 *
同样可以使用JSR-250生命周期注解@PostContstruct 和 @PreDestory注解初始化和析构回调方法。
@PostConstruct
private void openFile(){
System.out.println("A file opened");
}
@PreDestroy
private void closeFile(){
System.out.println("The file closed");
}
接下来,要在IOC容器中注册一个CommonAnnotationPostProcessor实例,调用带有生命周期实例注解的初始化和析构回调方法。这样就不需要指定Bean的init-method和destory-method属性了。
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
<bean id="cashier1" class="com.zy.IOC.Cashier">
<property name="name" value="cashier1"/>
<property name="path" value="/Users/leon/test/1.txt"/>
</bean>
也可以简单的在Bean配置文件中包含<context:annotation-config/>
元素,自动注册CommonAnnotationPostProcessor实例。