自动装配和模版装配
使用自动装配
在之前的XML配置文件中,总是显式地注入属性。如果使用自动装配,可以减少XML配置文件的部分内容。
Spring支持好几种自动装配,分别是byName、byType、constructor和no。
若指定某个Bean采用byName方式的自动装配:
<bean id="" class="" autowire="byName" />
Spring容器负责自动注入该Bean的所有属性。容器将查找与属性名相同的Bean,然后自动注入到属性中,如果没有被找到,该属性将不会被注入。
<bean id="american" class="com.bean.AmericanImpl" autowire="byName">
<property name="name" value="托尼"></property><!-- 基本属性显式注入 -->
<property name="age" value="24"></property>
<!-- <property name="book" ref="book">
</property> 该属性将自动注入-->
</bean>
bean id="book" class="com.bean.Book" >
<property name="bookName"><value>书名</value></property>
</bean>
若采用byType方式的自动装配,注入过程与byName类似,不同的是,容器查找的是与属性的设置方法参数类型兼容的Bean。
若采用constructor方式的自动装配,则容器将试图找出所有与构造方法的参数类型兼容的Bean,然后确定某个合适的构造方法。如果没有符合调用任何构造方法所需的Bean,则容器将抛出异常。
//增加如下构造方法
public AmericanImpl(Book book){
this.book = book;
System.out.println("book");
}
//修改配置:autowire="constructor"
默认的autowire设定是”no”,即不采用自动装配。如果在配置文件的根节点 < beans >中指定了自动装配的方式,则所有的Bean都将采用指定的自动装配,除非显示设置了autowire。
配置文件的大多配置都可以通过提醒看出其是干什么的,只需要多实验就好了。
使用模版装配
如果需要配置大量的同一类型的Bean,则这些Bean都有一些相同的属性,可以考虑使用模版来装配Bean,这样能简化XML配置文件的编写。
在Bean的定义中,指定abstract=”true”表示这个Bean仅作为模版使用,Spring容器不会为标记为abstract的Bean创建实例。
<bean id="abstractAmerican" class="com.bean.AmericanImpl" abstract="true">
<property name="name" value="托尼"></property>
<!-- <property name="age" value="24"></property> -->
<property name="book" ref="book">
</property>
</bean>
<bean id="american" parent="abstractAmerican">
<!-- 通过指定parent,将自定继承abstractAmerican的name和book属性 -->
<property name="age" value="24"></property>
</bean>
可以在继承的Bean中覆盖其parent的设置,继承的Bean如果与其parent的class完全相同,则class可以省略;如果不同,则继承的Bean必须是其parent的子类,这样才能确保parent中的设置也能应用于子类。
定制Bean
在Spring容器中,还有一些Bean具有特殊身份,Spring容器处理这些特殊Bean和其它普通Bean是区别对待的,这些特殊Bean可以实现一些特殊功能以满足特殊的需求。
- 获得自身在Spring容器中的配置信息
- 获得容器的实例
- 可以切入到Bean的生命周期中,以便检查或修改Bean的属性
- 监听并处理Spring容器和其它Bean发布的消息
获取Bean的信息
如果Bean需要从容器获得自身的某些信息,则可以实现相应的Spring专有接口。
对于实现了BeanNameAware的Bean,该Bean在初始化时可以获得Spring容器传给它的名字(Spring容器总是优先使用id属性,在id属性不存在的情况下才使用name属性)。
public class AmericanImpl implements Person,BeanNameAware{
public void init(){
this.age = 12;
System.out.println("init方法 执行");
}
@Override
public void setBeanName(String name) {
// TODO Auto-generated method stub
System.out.println("BeanName= "+name);
}
}
//applicationContext.xml配置
<bean id="americanId" class="com.bean.AmericanImpl" init-method="init">
<property name="name" value="托尼"></property>
<property name="age" value="24"></property>
<property name="book" ref="book">
</property>
</bean>
//测试类代码部分代码:
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
Person person= (AmericanImpl)bf.getBean("americanId");
person.speak();
//运行结果:
BeanName= americanId
init方法 执行
name= 托尼
age= 12
bookName= 书名
分析:setBeanName方法在初始化方法之前执行,初始化的属性赋值会覆盖配置文件中的属性赋值。
获取容器
如果Bean希望得到Spring容器的引用,可以实现ApplicationContextAware 或 BeanFactoryAware接口,它们分别对应使用ApplicationContext 容器和使用基本的BeanFactory容器的情况。
//应根据你实例化了哪个容器来实现相应的接口才能获得该容器的引用,否则会得到null
//AmericanImpl类实现BeanFactoryAware接口,增加以下方法:
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// TODO Auto-generated method stub
System.out.println(beanFactory);
}
//运行结果:
BeanName= americanId
org.springframework.beans.factory.xml.XmlBeanFactory@3cbbc1e0: defining beans [chinese,abstractAmerican,american,book,am,systemStartTime,americanId]; root of factory hierarchy
init方法 执行
name= 托尼
age= 12
bookName= 书名
分析:如果此处我实现了ApplicationContextAware接口,会得到一个null的该ApplicationContext,因为我实例化的是BeanFactory容器。
使用BeanPostProcessor
在Bean的初始化阶段,Spring容器也提供了一种切入机制,允许检查或者修改Bean的属性。要实现这一功能,需要编写一个特殊的Bean,它实现了Spring的专有接口BeanPostProcessor。
对于使用基本的BeanFactory和使用ApplicationContext 这两种容器来说,要将我们定义的这个特俗的MyBeanPostProcessor置入容器,方法有所不同。
对于基本的BeanFactory,由于Bean是延迟创建的,因此在获得一个BeanFactory实例后,必须立刻以编程的方式置入MyBeanPostProcessor。
//修改测试类
ClassPathResource resource = new ClassPathResource("applicationContext.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
factory.addBeanPostProcessor(new MyBeanPostProcessor());
Person person= (AmericanImpl)factory.getBean("americanId");
person.speak();
/* 这里解释下为什么不使用XmlBeanFactory,在Spring3.1以后,该类就被注解为@Deprecated(过时),
文档原文是:For advanced needs, consider using a DefaultListableBeanFactory with an XmlBeanDefinitionReader. The latter allows for reading from multiple XML resources and is highly configurable in its actual XML parsing behavior.
*/
//MyBeanPostProcessor类代码
package com.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
/*初始化前执行
通过传入的参数判断当前正在初始化的Bean的类型和名称
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// TODO Auto-generated method stub
System.out.println("postProcessBeforeInitialization方法执行");
AmericanImpl ac = null;
if(beanName.equals("americanId")){
ac = (AmericanImpl) bean;
ac.setName(ac.getName().toUpperCase());
}
return bean;
}
/*初始化后执行*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
// TODO Auto-generated method stub
System.out.println("postProcessAfterInitialization方法执行");
AmericanImpl ac = null;
if(beanName.equals("americanId")){
ac = (AmericanImpl) bean;
ac.setAge(10);
}
return bean;
}
}
//运行结果:
postProcessBeforeInitialization方法执行
postProcessAfterInitialization方法执行 //加载id为book的Bean
BeanName= americanId
org.springframework.beans.factory.support.DefaultListableBeanFactory@3551a94: defining beans [chinese,abstractAmerican,american,book,am,systemStartTime,americanId]; root of factory hierarchy
postProcessBeforeInitialization方法执行
init方法 执行
postProcessAfterInitialization方法执行 //加载id为americanId的Bean
name= TUONI //将配置文件中的中文改成了英文后的生成结果
age= 10
bookName= 书名
在ApplicationContext中定义MyBeanPostProcessor只需在XML配置文件中定义,和普通Bean的定义没有任何区别。(因为在初始化ApplicationContext容器时,它会加载所有的Bean)
<bean id="myBeanPostProcessor" class="com.bean.MyBeanPostProcessor" />
使用@Required检查依赖注入
Spring2.0还提供了一个非常有用的 BeanPostProcessor: RequiredAnnotationBeanPostProcessor,它负责检查一个Bean的某个被标记为@Required注解的属性是否被注入了。如果忘记注入这个属性,RequiredAnnotationBeanPostProcessor将会抛出BeanCreationException。
//修改AmericanImpl部分代码
@Required
public void setBook(Book book){
this.book = book;
}
/*在配置文件中,将
<property name="book" ref="book">
</property>
注释掉。*/
/*同样,使用BeanFactory和ApplicationContext是不一样的,参照上一小节即可*/
factory.addBeanPostProcessor(new RequiredAnnotationBeanPostProcessor());
//运行结果:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'americanId' defined in class path resource [applicationContext.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanInitializationException: Property 'book' is required for bean 'americanId'
//分析:
避免了在运行期由于忘记了注入某个属性而导致的NullPointerException。
使用BeanFactoryPostProcessor
BeanFactoryPostProcessor允许我们在Spring容器对所有的Bean初始化之前对Bean的定义做一些修改,列如,更改Bean的scope,甚至class的类型。
//两个简单的Bean
package com.bean;
public class EnImpl implements Person {
@Override
public void speak() {
// TODO Auto-generated method stub
System.out.println("EnImpl speak!");
}
}
package com.bean;
public class JpImpl implements Person{
@Override
public void speak() {
// TODO Auto-generated method stub
System.out.println("JpImpl speak!");
}
}
//在配置文件中定义
<bean id="jp" class="com.bean.JpImpl"></bean>
//编写一个ChangeTypeBeanFactoryPostProcessor,将一种类型的Bean变为另一种类型的Bean
package com.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
public class ChangeTypeBeanFactoryPostProcessor implements
BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
// TODO Auto-generated method stub
String[] beanNames = beanFactory.getBeanDefinitionNames();
for(String name : beanNames){
System.out.println(name);
BeanDefinition bd = beanFactory.getBeanDefinition(name);
if(bd!=null && bd.getBeanClassName()!=null && bd.getBeanClassName().equals("com.bean.JpImpl"))
bd.setBeanClassName("com.bean.EnImpl");
}
}
}
//测试文件部分修改为:
new ChangeTypeBeanFactoryPostProcessor().postProcessBeanFactory(factory);
Person person= (Person)factory.getBean("jp");
person.speak();
//运行结果为:
abstractAmerican
american
book
am
systemStartTime
americanId
jp //打印的BeanId
postProcessBeforeInitialization方法执行
postProcessAfterInitialization方法执行 //BeanPostProcessor处理
EnImpl speak! //结果得到实例是EnImpl。
BeanFactoryPostProcessor有能力动态修改XML配置文件中的原始定义。下面要介绍的将XML配置文件中的某些常量提取到外部properties 文件的PropertyPlaceholderConfigurer 就是通过BeanFactoryPostProcessor 实现的,它能动态地读取properties文件的内容,然后替换掉相应的占位符。
使用外部属性文件
对于常量,通常我们是直接在XML配置文件中直接注入的,不过,某些时候,将常量放到一个外部的properties 属性文件中也许更合适。最典型的例子就是装配一个DataSource。
下面是一个简单的列子,来展示如何在XML文件中“使用”外部属性文件。
//person.properties
name=\u5C0F\u660E
age=5
//修改配置文件,使用占位符来赋值,引用properties文件中的值采用"${变量名}"的形式,
<bean id="amer" class="com.bean.AmericanImpl" >
<property name="name" value="${name}"></property>
<property name="age" value="${age}"></property>
<property name="book" ref="book">
</property>
</bean>
//修改测试类(使用BeanFactory容器)
/*在Spring容器读取配置文件之后,初始化Bean之前对Bean的定义进行修改,使用properties文件中相应的值代替"${变量名}"表达式*/
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setLocation(new ClassPathResource("person.properties"));
ppc.postProcessBeanFactory(factory);
//使用ApplicationContext容器
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="person.properties" />
</bean>
//运行结果为:
postProcessBeforeInitialization方法执行
postProcessAfterInitialization方法执行
BeanName= amer
org.springframework.beans.factory.support.DefaultListableBeanFactory@6cc4c815: defining beans [chinese,abstractAmerican,american,book,am,systemStartTime,americanId,jp,amer]; root of factory hierarchy
postProcessBeforeInitialization方法执行
postProcessAfterInitialization方法执行
name= 小明
age= 5 //name和age成功赋值
bookName= 书名
只应该在针对应用程序部署的特定配置信息才有必要将其单独放到外部 properties 文件中(数据库配置信息、邮件服务器配置信息等),以便于修改。