3.1 Bean基本管理
1. BeanFactory接口定义了Object getBean(String, Class)方法,通过指定Bean定义文件中设置的名称,取得相应的Bean实例,并转换至指定的类。
2. ApplicationContext可以读取多个Bean定义文件,通过数组实现,如:
view sourceprint?
1 |
ApplicationContext context = new ClassPathXmlApplicationContext( new String[] { "beans-config.xml" , "beans-config2.xml" });
|
也可以指定*字符,下面的例子课读取Classpath下所有以"beans”开头的XML配置文件。但要注意此方法只在实际的文件系统中有用,如果是在.jar文件中,以下的指定无效:
view sourceprint?
1 |
ApplicationContext context = new ClassPathXmlApplicationContext( "beans*.xml" );
|
3. 当需要多个Bean定义文件时,可以使用<import>标签,如:
view sourceprint?
2 |
< import resource = "dao-config.xml" >
|
4 |
< bean id = "bean1" class = "..." >
|
5 |
< bean id = "bean2" class = "..." >
|
<import>标签必须放置在<bean>标签之前,定义文件必须放置在同一个目录或是Classpath之中,以相对路径指定Bean定义文件位置。
4. 指定Bean的别名有两种方法,其一是使用<alias>标签,如:
view sourceprint?
2 |
< bean id = "dataSource" class = "..." >
|
4 |
< alias name = "dataSource" alias = "device:dataSource" >
|
5 |
< alias name = "dataSource" alias = "user:dataSource" >
|
另一种方法是使用<bean>标签的name属性直接指定,多个别名用逗号隔开如:
view sourceprint?
2 |
< bean id = "dataSource" name = "device:dataSource,user:dataSource" class = "..." >
|
5. 使用静态工厂方法和工厂bean实例化bean
可以通过静态工厂方法实例化bean。假设有如下一个接口:
view sourceprint?
1 |
public interface IMusicBox {
|
创建MusicBoxFactory类,取得IMusicBox实例由静态方法creatMusicBox()负责:
view sourceprint?
1 |
public class MusicBoxFactory {
|
2 |
public static IMusicBox createMusicBox() {
|
3 |
return new IMusicBox() {
|
5 |
System.out.println( "Playing piano music" );
|
Bean的配置如下,注意此时musicBox的class属性值不是它本身的类,必须是能获得该类的实例的工厂类(在这个例子里是MusicBoxFactory)。
view sourceprint?
1 |
< bean id = "musicBox" class = "spring.chapter3.MusicBoxFactory" factory-method = "createMusicBox" />
|
此外,工厂方法也可以不是静态的,例如上例中的creatMusicBox()方法也可以是一个实例方法,此时若要实例bean必须先得到工厂类的bean,具体配置如下:
view sourceprint?
1 |
< bean id = "musicBoxFactoryBean" class = "spring.chapter3.MusicBoxFactory" />
|
2 |
< bean id = "musicBox" factory-bean = "musicBoxFactoryBean" factory-method = "createMusicBox" />
|
注意:factory-bean指定一个工厂类的实例,Spring会用factory-bean指定的实例调用factory-method指定的方法,从而得到一个实例bean。
6. Bean的生命周期
如果使用BeanFactory来生、管理Bean,会尽量支持一下的声明周期:
• Bean的建立
由BeanFactory读取Bean定义文件,并生成各个Bean实例。
• 属性注入
执行相关的Bean属性依赖注入。
• BeanNameAware的setBeanName()
如果Bean类有实现org.springframework.beans.factory.BeanNameAware接口,则执行它的setBeanName()方法。
• BeanFactoryAware的setBeanFactory()
如果Bean类有实现org.springframework.beans.factory.BeanFactoryAware接口,则执行它的setBeanFactory()方法。
• BeanPostProcessors的postProcessBeforeInitialization()
如果有任何的org.springframework.beans.factory.config.BeanPostProcessors实例与Bean实例相关联,则执行BeanPostProcessors实例的postProcessBeforeInitialization()方法。
• InitializingBean的afterPropertiesSet()
如果Bean类有实现org.springframework.beans.factory.InitializingBean,则执行它的afterPropertiesSet()方法。
• Bean定义文件中定义init-method
可以再Bean定义文件使用init-method属性设置方法名称。如果设置了该属性,当代码运行到这个阶段,就会执行init-method属性指定的方法。
• BeanPostProcessors的postProcessAfterInitialization()
如果有任何的BeanPostProcessors实例与Bean实例关联,则执行BeanPostProcessors实例的postProcessAfterInitialization()方法。
• DisposableBean的destroy()
在容器关闭时,如果Bean类有实现org.springframework.beans.factory.DisposableBean接口,则执行它的destroy()方法。
• Bean定义文件中定义destroy-method
在容器关闭时,可以在Bean定义文件使用destroy-method属性设置方法名称。如果设置了该属性,当代码运行到这个阶段,就会执行destroy-method属性指定的方法。
如果所有的Bean都有相同的初始化方法名称和销毁方法名称,例如都命名为init()和destroy(),则可以在<beans>上定义default-init-method和default-destroy-method属性,这样Spring会自动执行每个Bean的init()方法与destroy()方法。
7. 如果使用的是BeanFactory,那么只有在使用getBean()方法真正取得Bean是,才会实例化Bean。如果使用的是ApplicationContext,则会预先根据Bean定义文件将所有的Bean实例化。在这种模式下,可以在<bean>上设置lazy-init属性为true,这样ApplicationContext就不会再启动时实例化该Bean。
8. Bean的继承
Bean定义文件中,一个Bean可以继承另外一个Bean的配置,只要在该Bean定义时设置parent属性为要继承的Bean的id即可。被继承的Bean可以设置abstract属性为true,这样这个Bean就不能被实例化,只能作为其他Bean的父Bean被继承,这一点类似Java中的abstract类。另外,abstract属性为true的Bean不必指定class属性。
9. Lookup Method injection
Lookup Method injection主要是用在Singleton的Object中使用非Singleton的Bean时,通过lookup-method的那个方法来取得非Singleton的Bean。看下面的例子。
view sourceprint?
1 |
< bean id = "sysMessage" class = "test.Message" scope = "prototype" >
|
这里建立了一个普通的bean,sysMessage。相关的类代码如下:
view sourceprint?
03 |
import java.util.Date;
|
05 |
public class Message {
|
06 |
private String sysMessage;
|
09 |
sysMessage = "Now date: " + new Date().toString();
|
12 |
public String toString() {
|
Message对象的功能时是取得系统的日期。
现在设计一个MessageManager类,当调用它的display()方法时,会新建立一个Message对象并加以显示,注意这是个abstract类,其中包含一个abstract方法createMessage():
view sourceprint?
03 |
public class MessageManager {
|
04 |
public void display() {
|
05 |
Message message = createMessage();
|
06 |
System.out.println(message);
|
09 |
protected abstract Message createMessage();
|
下面是配置信息:
view sourceprint?
1 |
< bean id = "messageManager" class = "test.MessageManager" >
|
2 |
< lookup-method name = "createMessage" bean = "sysMessage" />
|
其中的lookup-method元素的name属性指定一个抽象方法,bean属性指定徐要传入的bean。每次调用name属性指定的方法时,会传入一个bean属性指定的bean。这样一来,虽然messageManager在容器建立初期便已经创建完毕,但是每次执行display方法的时候都能获得一个全新的message对象,从而获得新的系统日期信息。
如果用传统的方法,在MessageManager类中增加一个Message类型成员属性,并通过spring注入,每次调用display()方法时获得的日期都是同样的值,即使message对象的scope被指定为prototype。因为messageManager对象时在容器建立时就创建好的,已经耦合了一个固定的message对象,每次调用display()方法是,不会得到一个新的message对象。
10. BeanPostProcessor接口
如果这个接口的某个实现类被注册到某个容器,那么该容器的每个受管Bean在调用初始化方法之前,都会获得该接口实现类的一个回调。容器调用接口定义的方法时会将该受管Bean的实例和名字通过参数传入方法,进过处理后通过方法的返回值返回给容器。
注意,假如我们使用了多个的BeanPostProcessor的实现类,那么如何确定处理顺序呢?其实只要实现Ordered接口,设置order属性就可以很轻松的确定不同实现类的处理顺序了。
另外,BeanFactory和ApplicationContext对待bean后置处理器稍有不同。ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有bean,并把它们注册为后置处理器,然后在容器创建bean的适当时候调用它。部署一个后置处理器同部署其他的bean并没有什么区别。而使用BeanFactory实现的时候,bean 后置处理器必须通过下面类似的代码显式地去注册:
view sourceprint?
1 |
Resource resource = new FileSystemResource( "applicationContext.xml" );
|
3 |
ConfigurableBeanFactory factory = new XmlBeanFactory(resource);
|
6 |
BeanPostProcessorImpl beanPostProcessor = new BeanPostPrcessorImpl();
|
9 |
factory.addBeanPostProcessor(beanPostProcessor); |
11. 解析文字消息
ApplicationContext继承了org.spriingframework.context.support.MessageSource几口,可以使用getMessage()方法取得文字消息的资源文件,从而实现国际化和本地化消息的目的。
可以简单的通过MessageSource的一个实现org.springframework.context.support.ResourceBundleMessageSource来取得国际化消息。需要注意的是一定要在bean配置文件中配置一个该类的bean,并制定其basename属性,该属性值即为资源文件的basename。例如:
view sourceprint?
1 |
< bean id = "messageSource" class = "org.springframework.context.support.ResourceBundleMessageSource" >
|
2 |
< property name = "basename" value = "messages" />
|
上述配置说明资源文件的名字为messages开头,例如:messages_zh_CN.properties或者messages_en_US.properties。有了上述配置后,可以很方便的使用getMessage()方法取得国际化消息:
view sourceprint?
1 |
ApplicationContext context = |
2 |
new ClassPathXmlApplicationContext( "applicationContext.xml" );
|
4 |
Object[] arguments = { "Jack" , Calendar.getInstance().getTime()};
|
6 |
System.out.println(context.getMessage( "hello" , arguments, Locale.US));
|
7 |
System.out.println(context.getMessage( "hello" , arguments, Locale.PRC));
|
这里的getMessage()方法有三个参数,第一个参数指明资源文件(messages_*_*.properties)中的键值对的键名;第二个参数是一个object类型的数组,表示假如消息内容含有{0},{1}等运行时参数时(例如:hello=hello, {0}!),可以用此数组将相关参数传入;第三个参数是指定需要显示的Locale类型,若指定为Locale.PRC,则说明需要显示该消息的汉语版本。