【原创】《Spring 2.0 技术手册》学习笔记——第三章 Bean、消息、事件
3.1 Bean基本管理
1. BeanFactory接口定义了Object getBean(String, Class)方法,通过指定Bean定义文件中设置的名称,取得相应的Bean实例,并转换至指定的类。
2. ApplicationContext可以读取多个Bean定义文件,通过数组实现,如:
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"beans-config.xml", "beans-config2.xml"});
也可以指定*字符,下面的例子课读取Classpath下所有以"beans”开头的XML配置文件。但要注意此方法只在实际的文件系统中有用,如果是在.jar文件中,以下的指定无效:
ApplicationContext context = new ClassPathXmlApplicationContext("beans*.xml");
3. 当需要多个Bean定义文件时,可以使用<import>标签,如:
<beans> <import resource="dao-config.xml"> <bean id="bean1" class="..."> <bean id="bean2" class="..."> </beans>
<import>标签必须放置在<bean>标签之前,定义文件必须放置在同一个目录或是Classpath之中,以相对路径指定Bean定义文件位置。
4. 指定Bean的别名有两种方法,其一是使用<alias>标签,如:
<beans> <bean id="dataSource" class="..."> <alias name="dataSource" alias="device:dataSource"> <alias name="dataSource" alias="user:dataSource"> ... </beans>
另一种方法是使用<bean>标签的name属性直接指定,多个别名用逗号隔开如:
<beans ...> <bean id="dataSource" name="device:dataSource,user:dataSource" class="..."> </beans>
5. 使用静态工厂方法和工厂bean实例化bean
可以通过静态工厂方法实例化bean。假设有如下一个接口:
public interface IMusicBox { public void play(); }
创建MusicBoxFactory类,取得IMusicBox实例由静态方法creatMusicBox()负责:
public class MusicBoxFactory { public static IMusicBox createMusicBox() { return new IMusicBox() { public void play() { System.out.println("Playing piano music"); } }; } }
Bean的配置如下,注意此时musicBox的class属性值不是它本身的类,必须是能获得该类的实例的工厂类(在这个例子里是MusicBoxFactory)。
<bean id="musicBox" class="spring.chapter3.MusicBoxFactory" factory-method="createMusicBox"/>
此外,工厂方法也可以不是静态的,例如上例中的creatMusicBox()方法也可以是一个实例方法,此时若要实例bean必须先得到工厂类的bean,具体配置如下:
<bean id="musicBoxFactoryBean" class="spring.chapter3.MusicBoxFactory"/> <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。看下面的例子。
<bean id="sysMessage" class="test.Message" scope="prototype">
这里建立了一个普通的bean,sysMessage。相关的类代码如下:
pakage test; import java.util.Date; public class Message { private String sysMessage; public Message() { sysMessage = "Now date: " + new Date().toString(); } public String toString() { return sysMessage; } }
Message对象的功能时是取得系统的日期。
现在设计一个MessageManager类,当调用它的display()方法时,会新建立一个Message对象并加以显示,注意这是个abstract类,其中包含一个abstract方法createMessage():
package test; public class MessageManager { public void display() { Message message = createMessage(); System.out.println(message); } protected abstract Message createMessage(); }
下面是配置信息:
<bean id="messageManager" class="test.MessageManager"> <lookup-method name="createMessage" bean="sysMessage"/> </bean>
其中的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 后置处理器必须通过下面类似的代码显式地去注册:
Resource resource = new FileSystemResource("applicationContext.xml"); ConfigurableBeanFactory factory = new XmlBeanFactory(resource); //实例化一个BeanPostProcesser接口实现类BeanPostProcessorImpl的实例 BeanPostProcessorImpl beanPostProcessor = new BeanPostPrcessorImpl(); //对于BeanFactory,必须显式地注册BeanPostProcessor factory.addBeanPostProcessor(beanPostProcessor);
11. 解析文字消息
ApplicationContext继承了org.spriingframework.context.support.MessageSource几口,可以使用getMessage()方法取得文字消息的资源文件,从而实现国际化和本地化消息的目的。
可以简单的通过MessageSource的一个实现org.springframework.context.support.ResourceBundleMessageSource来取得国际化消息。需要注意的是一定要在bean配置文件中配置一个该类的bean,并制定其basename属性,该属性值即为资源文件的basename。例如:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="messages"/> </bean>
上述配置说明资源文件的名字为messages开头,例如:messages_zh_CN.properties或者messages_en_US.properties。有了上述配置后,可以很方便的使用getMessage()方法取得国际化消息:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Object[] arguments = {"Jack", Calendar.getInstance().getTime()}; System.out.println(context.getMessage("hello", arguments, Locale.US)); System.out.println(context.getMessage("hello", arguments, Locale.PRC));
这里的getMessage()方法有三个参数,第一个参数指明资源文件(messages_*_*.properties)中的键值对的键名;第二个参数是一个object类型的数组,表示假如消息内容含有{0},{1}等运行时参数时(例如:hello=hello, {0}!),可以用此数组将相关参数传入;第三个参数是指定需要显示的Locale类型,若指定为Locale.PRC,则说明需要显示该消息的汉语版本。