二、Bean、消息、事件
Spring的核心是一个容器,它实现了IoC的概念,可以协助管理各个对象的生命周期,以及对象之间的依赖关系。熟悉使用BeanFactory和ApplicationContext的运用是了解Spring的重点所在。
作为一个应用程序框架,ApplicationContext除了具备BeanFactory基本的容器管理功能之外,还能支持更多应用程序框架的特性,像资源的取得、文字消息的解析、事件的处理与传播等特性。
2.1 Bean基本管理
2.1.1 BeanFactoy、ApplicationContext
BeanFactory负责读取Bean定义文件;管理对象的加载、生成;维护Bean与Bean对象之间的依赖关系;负责Bean的生命周期。
Spring的创始者Rod Johnson建议使用ApplicationContext来取代BeanFactory,在实现ApplicationContext的类中,最常用的有以下三种:
org.springframework.context.support.FileSystemXmlApplicationContext
可指定XML定义文件的相对路径或绝对路径读取定义文件。
org.springframeword.context.support.ClassPathXmlApplicationContext
从Classpath设置路径中读取XML定义文件。
org.springframeword.context.support.XmlWebApplicationContext
在Web应用程序的文件架构中,指定相对位置读取定义文件。
例如:
ApplicationContext context = new ClassPathXmlApplicationContext
("beans-config.xml");
HelloBean hello = context.getBean("helloBean");
ApplicationContext也可以读取多个配置文件。以数组指定配置文件的位置。
ApplicationContext = new ClassPathXmlApplicationContext(new String[]
{"beans-config.xml","beans-config2.xml"});
也可以指定*字符,但只能在实际的文件系统中有效,在.jar文件中无效。
ApplicationContext = new ClassPathXmlApplicationContext("beans*.xml");
当需要多个Bean定义文件时,建议使用ApplicationContext的方式来读取,好处是Bean定义文件之间是各自独立的。另一个替代的方式是使用<import>标签。如:
<beans .....>
<import resource = "dao-config.xml" />
<import resource = "resource/messageSource.xml" />
<bean id = "bean1" class="..." />
</beans>
<import>标签必须放在<bean>标签之前,定义文件必须放置在同一个目录或者是Classpath之中,以相对路径指定Bean定义文件的位置。而每个定义文件的内容都必须包括<beans>根标签。
2.1.2 Bean的识别名称和别名
在定义文件中使用<bean>标签中的id指定Bean的识别名称,当需要多个Bean定义文件时最好规范id的名称命名,以免冲突。当需要给一个实例设置多个名称引用时可以设置别名。例如:
<beans....>
<bean id="dataSource" class="...." />
<alias name = "dataSource" alias="device:dataSource" />
<alias name = "dataSource" alias = "user:dataSource" />
</beans>
在其他定义文件中,可以直接参考别名:
<beans...>
<bean id= "device:someBean" class="....">
<property>
<ref bean="device:dataSource" >
</property>
</bean>
</beans>
除了使用alias设置别名外,还可以直接使用<bean>标签的"name"属性来设置别名,多个别名之间以逗号隔开。
<beans....>
<bean id="dataSource" name="device:dataSource,user:dataSource" class="..." />
</beans>
2.1.3 bean的实例化
最基本的方式:
<bean id = "writer" class="...." />
像这样的方式,Spring会使用无参构造方法。
在设计上可以使用静态工场方法来取得某个对象,好处是不用了解对象建立的细节。例如:
public interface IMusicBox
{
public void play();
}
public class PianoMusicBox implements IMusicBox{
public void play(){
System.out.println("播放钢琴音乐");
}
public class violinMusicBox implements IMusicBox{
public void play(){
System.out.println("播放小提琴音乐");
}
设计一个静态工场,使用createMusicBox负责创建实例对象。
public class MusicBoxFactory{
public static IMusicBox createMusicBox(String name){
if(name.equals("piano")){
return new PianoMusicBox();
}
else{
return new ViolinMusicBox();
}
}
若要通过MusicBoxFactory的createMusicBox()方法来取得IMusicBox的实例,则可以设置<bean>的"factory-mothed"属性,例如:
<beans ....>
<bean id = "musicBox" class="MusicBoxFactory" factory-method = "createMusicBox">
<construct-arg value="piano" />
</bean>
</beans>
客户程序部分代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
IMusicBox musicBox= (IMusicBox)ac.getBean("musicBox");
musicBox.play();
}
使用实例工场:
<beans ....>
<!-- 先实例化一个工场类 -->
<bean id="musicBox" class="MusicBoxFactory" ></bean>
<!-- factory-bean为已经实例化的工场实例 -->
<bean id = "getMusicBox" factory-bean="musicBox" factory-method = "createMusicBox">
<construct-arg value="piano" />
</bean>
</beans>
2.1.4 Bean的Scope
在Spring中,从BeanFactory或ApplicationContext取得的实例被默认为Singleton,也就是默认为每一个Bean名称只维持一个实例。
为防止多线程同时存取公用资源所引发的资料不同步问题,通常Singleton的Bean都是无状态的(Stateless)。
然而可以设置每次从容器中取得的都是一个新的实例,例如:
<bean id="helloBean" class="..." scope="prototype">
.......
也可以通过设置<bean>的singleton属性为true或false,来设置是否以Singleton的方式产生实例。
scope除了可以设置singleton与prototype之外,针对Web应用环境,还可以设置request、session、globalSession,分别表示请求阶段、会话阶段与基于Portlet的Web应用程序会话阶段。
2.1.5 Bean的生命周期
l Bean的建立
由BeanFactory读取Bean定义文件,并生成各个Bean实例。
l 属性注入
执行相关的Bean属性依赖注入。
l BeanNameAware的setBeanName()
如果Bean类有实现org.springframework.beans.factory.BeanNameAware接口,则执行它的setBeanName()方法。
l BeanFactoryAware的setBeanFactory()
如果Bean类有实现org.springframework.beans.factory.BeanNameAware接口,则执行它的setBeanName()方法。
l BeanPostProcessors的processBeforeInitialization()
如果有任何的org.springframework.beans.factory.config.BeanPostProcessors实例与Bean实例关联,则执行BeanPostProcessors实例的processBeforeInitialization()方法。
l InitializationBean的afterPropertiesSet()
如果Bean类有实现org.springframewor.beans.factory.InitializingBean,则执行它的afterPropertiesSet()方法。
l Bean定义文件中定义init-method
可以在Bean定义文件使用"init-method"属性设置方法名称,例如:
.....
<bean id="helloBean" class="..." init-method="initBean" >
.....
l BeanPostProcessors的processAfterInitialization()
如果有任何的BeanPostProcessors实例与Bean实例关联,则执行BeanPostProcessors实例的processAfterInitialization()方法。
l DisposableBean的destroy()
在容器关闭时,如果Bean类有实现
org.springframework.beans.factory.DisposableBean接口,则执行它的destroy()方法。则执行它的destroy()方法。
l Bean定义文件中定义destroy-method
在容器关闭时,可以在Bean定义文件使用"destroy-method"属性设置方法名称,例如:
<bean id="helloBean" class="..." destroy-method="destroyBean" >
.....
如果有以上设置,当代码执行至这个阶段时,就会执行destroyBean()方法。
采用<bean>中定义的init-method和destroy()不会耦合Spring的API。
2.1.6 Bean定义的继承
<bean id="someBean" abstract="true">
<property name="name">
<value>guest</value>
</property>
<property name="age">
<value>18</value>
</property>
</bean>
<bean id="some" class="...." parent="someBean">
<property name="name">
<value>SH</value>
</property>
</bean>
someBean中abstract="true"说明这个是抽象的Bean定义,Spring不会去实例化。parent="someBean",说明他将继承someBean的设置。输出为SH,18。
也可以从一个完整的bean中继承。这时都会被实例化。
<bean id="someBean" class="....">
<property name="name">
<value>guest</value>
</property>
<property name="age">
<value>18</value>
</property>
</bean>
<bean id="some" class="...." parent="someBean">
<property name="name">
<value>SH</value>
</property>
</bean>
2.2 Bean的依赖设置
在Spring中两种基本的注入方式是Setter Injection及Constructor Injection,另外针对非Singgleton的依赖注入,提供了Method Injection。
2.2.1 两种注入方式
1、构造器注入
省去POJO。
配置文件:
<bean id = "helloBean" class="...." >
<constructor-arg>
<value>SH<value>
</constructor-arg>
<constructor-arg>
<value>Hello</value>
</constructor-arg>
</bean>
在配置文件中,会根据POJO中构造函数的参数的数序依次注入。还可以使用index属性指定构造方法中的哪一个属性:
<constructor-arg index="0">
..............
如果有非基本类型的注入:
<bean id="date" class="java.util.Date" />
<bean id="helloBean" calss="...">
<constructor-arg >
<ref bean="date" />
..............
编译器会解析ref注入的类型并且找到对应的构造函数。也可以显示声明注入的类型。
<bean id="date" class="java.util.Date" />
<bean id="helloBean" calss="...">
<constructor-arg type="java.util.Date">
<ref bean="date" />
..............
使用构造器注入和Setter方法注入的区别是在对象建立时就准备好所有的资源,还是在对象建立好后。
2.2.2 依赖的值设置与参考
在<property>或<constructor>中直接使用<value>标签,来指定一个基本类型给属性或构造方法。例如:
<bean ....>
<property name="age">
<value>18</value>
</property>
<constructor-arg>
<value>SH</value>
</constructor-arg>
</bean>
或:
<bean ....>
<property name="age" value="18" />
<constructor-arg value="SH" />
</bean>
若为空使用<null />标签。
引用其他已有的Bean实例:
<constructor-arg>
<ref bean="date" />
</constructor-arg>
或者:
<constructor-arg ref="date" />
<constructor-arg ref local="date" />
如果某个Bean实例只被某个属性参考过一次,之后在定义文件中不再被其他bean属性参考,可以直接在属性定义时使用<bean> 标签。例如:
.....
<property name="date">
<bean class = "java.util.Date" />
</property>
Spring的Ioc容器会自动生成Date实例,并通过setDate()方法将Date实例设置给bean。
在取得某个bean之前,如果它依赖于另一个bean。Spring就会先去实例化被依赖的bean并进行注入。如果某个Bean在生成前要求另一个bean必须先实例化,则可以指定depends-on属性来设置,例如:
<bean id="beanOne"
class="...." depends-on = "beanTwo" />
<bean id="beanTwo" class="...." />
Spring会先将beanTwo实例化,如果有两个以上的bean要设置在depends-on中,以逗号隔开。
2.2.3 自动绑定
bean还可以隐式的自动绑定,通过byType或byName。例如:
<bean id="dateBean" class="java.util.Date" />
<bean id="helloBean" class="...." autowire="byType" >
<property name="helloWord" value="Hello" />
</bean>
定义文件中没有指定<helloBean>的Date属性,是通过自动绑定。byType会查找是否定义了类似的类型对象。
byName:
判断id的名和setter名是否一致来进行绑定。例如:
<bean id="date" class="java.util.Date" />
<bean id="helloBean" class="...." autowire="byName" >
<property name="helloWord" value="hello" />
</bean>
helloBean的setDate()将会自动绑定。
构造方法中也可以自动绑定。
<bean ..... autowire="constructor">
Spring容器会对比容器中的Bean实例类型和构造函数上的参数类型,若相符则选择用该构造函数来建立bean实例。
若想偷懒,还可以设置autowire="autodetect"。这种方式会先autowire="constructor"。若没有则autowire="byType" 。
在bean标签中使用dependency-check,可以有4中依赖方式:simple、objects、all、none。
simple只检测简单的属性是否完成依赖关系。object则检测对象类型的属性是否完成依赖关系。all是全部。none是默认值,不检测。
2.2.4 集合对象
数组、java.util.List、java.util.Set、java.util.Map等。
public class SomeBean
{
private String[] strArray;
private Some[] someObjArray;
private List someList;
private Map someMap;
..............//get set
}
对于数组或是List类的依赖注入,在编写定义文件时使用<list>标签,并通过<value>来指定字符串,或是通过ref来指定其他的bean实例。对于Map类则使用<map>,Map必须指定<key-value>,需要使用<entry>标签指定key,然后使用<value>指定字符串,或ref指定其他bean实例。例如:
<bean id="..." class="...">
<property name="strArray">
<list>
<value>hello</value>
<value>welcome</value>
</list>
</property>
<property name="someObjArray">
<list>
<ref bean="some1" />
<ref bean="some2" />
</list>
</property>
<property name="someList">
<list>
<value>ListTeset</value>
<ref bean="some1" />
<ref bean="some2" />
</list>
</property>
<property name="someMap">
<map>
<entry key="MapTest">
<value>Hello,SH</value>
</entry>
<entry key="MapTest2">
<ref bean="some1" />
</entry>
</map>
</property>
</bean>
若是Set:
<set>
<value>set</set>
<ref bean="some1" />
</set>
若是java.util.Properties类型,则使用<props>与<prop>标签,例如:
<property name="someProperties">
<props>
<prop key="someKey1">someValue</prop>
<prop key ="someKey2>someValue2</prop>
</props>
</property>
3.2.6 Lookup Method Injection
例如下例:当调用display方法时,会取得新建立的Message对象加以显示。
public abstract class MessageManager{
public void display(){
Message message = createMessage();
System.out.print(message.toString());
}
public abstract Message createMessage();
}
public class Message{
private String sysMessage;
public Message(){
sysMessage = " 新信息: " + new Date().toString();
}
public String toString(){
return sysMessage;
}
}
这里不直接实现createMessage()方法,使之new 一个Message对象。而是通过Spring注入Message对象。以获得替换Message的弹性。但是配置文件中单纯的将Message的scope属性设置为prototype是行不通的,因为只有在通过BeanFactory或ApplicationContext的getBean()来取得Message时,才会重新实例化一个Message。
这时可以使用Spring的Lookup Method Injection,使用<lookup-method>标签,可指定使用某个Bean的方法,产生新的对象并进行注入,例如:
<beans..........>
<bean id="sysMessage" class="...." scope="prototype" />
<bean id="messageManager class="MessageManager">
<lookup-method name="createMessage" bean="sysMessage" />
</bean>
</beams>
如此设置,Spring将使用CGLIB产生一个MessageManager的子类实现(所以要将CGLIB的.jar加入classpath中),并且在每次调用createMessage方法的时候,建立一个Message对象并传回。
2.3 Bean的高级管理
2.3.1 非XML定义文件的配置方式
可以使用.properties定义bean。
HelloBean类省略,有一个String字段helloWord。
在这里编写一个beans-config.properties来定义bean与依赖注入等内容:
helloBean.(class) = xxxx.HelloBean
helloBean.helloWord= welcome
属性文件中helloBean是bean的别名设置,即id。.(class)用于指定类来源,例如还有.(abstract) 、.(parent)、.(lazy-init)、.(singleton)等都可以设置。.(helloWord即为helloBean的属性名。如果要参照已存在的Bean,则使用.helloWord(ref)。可以使用org.springframework.beans.factory.support.PropertiesBeanDefinitionReader来读取属性文件。例如:
BeanDefinitionRegistry reg = new DefaultListableBeanFactory();
PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(reg);
reader.loadBeanDefinitions(new ClassPathResource("xxxx.properties"));
BeanFactory factory = (BeanFactory)reg;
HelloBean hello = (HelloBean)factory.getBean("helloBean");
System.out.println(hello.getHelloWord());
会输出welcome。
2.3.2 Aware相关接口
虽然不让应用程序意识到Spring的存在,但是有时候会需要Spring提供的一些功能。比如让Bean知道自己在容器中是被哪个别名管理的,或者让它知道BeanFactory、ApplicationContext的存在,也就是让Bean可以取得目前运行中的BeanFactory或ApplicaitonContext的实例。
2.3.3 BeanPostProcessor
在Bean的依赖关系由Spring容器建立并设置之后,,还有机会自定义一些bean的修正动作来修正相关的属性,方法是让bean类实现org.springframework.beans.factory.config.BeanPostProcessor接口。
2.3.4 BeanFactoryPostProcessor
在BeanFactroy载入bean定义文件的所有内容,但还没正式产生Bean实例之前,可以对该BeanFactroy进行一些处理,方式是让bean类实现org.springframework.beans.factory.config.beanFacroyPostProcessor接口。
2.3.5 PropertyPlaceholderConfigurer
Spring提供了一个BeanFactoryPostProcessor接口的实现类:org.springframework.beans.factory.config.PropertyPlaceholderConfigurer。通过这个类,可以将一些配置信息移出至一个或多个.properties文件中,如此的安排可以让XML定义文件负责系统的相关配置,而.properties文件可以让客户根据实际应用时的需求,自定义一些相关的参数。例如:
<bean.id="configBean"class="org.springframework.beans.factory.config.PropertiesPlaceholderConfigurer">
<property name="location" value="hello.properties" />
</bean>
<bean id= "helloBean" class = "onlyfun.caterpillar.HelloBean">
<property name="helloWord" value="${onlyfun.caterpillar.helloWord}" />
</bean>
假设helloBean中有很多依赖注入的属性,这些大都是不常变动的属性,而其中helloWord会经常变动,可以编写一个hello.propertier文件来设置这个属性,而hello.properties文件的指定被设置在configBean的location属性中,hello.properties的内容如下:
onlyfun.caterpillar.helloWord=welcome
在helloBean的helloWord属性中,${onlyfun.caterpillar.helloWord}将会被hello.properties的onlyfun.caterpillar.helloWord的设置值取代。
如果有多个.properties文件,则可以通过locations属性来设置,例如:
......
<properties name="locations">
<list>
<value>hello.properties</value>
<value>other.properties</value>
</list>
</properties>
2.3.6 PropertyOverrideConfigurer
Spring提供了一个BeanFacrotyPsotProcessor接口的实现类:org.springframework.beans.factory.config.PropertyOverrideConfigurer。通过这个类,可以在.properties中设置一些优先的属性,当.properties文件中的某个设置与XML中的某个设置重复时,则XML中的设置会被覆盖。例如:
<bean.id="configBean"class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="hello.properties" />
</bean>
<bean id= "helloBean" class = "onlyfun.caterpillar.HelloBean">
<property name="helloWord" value="Hello" />
</bean>
在hello.properties的编写上,内容如下:
helloBean.helloWord=welcome
则输出值为welcome,而不是Hello。
同样的,多个通过locations设置。
2.3.7 CustomEditorConfigurer
Spring框架提供了一个BeanFactoryPostProcessor接口的实现类:org.springframework.beans.factory.config.CustomEditorConfigurer。这个类可以读取实现java.beans.PropertyEditor接口的类,并按其中的实现,将字符串转换为指定类型的对象。
2.4 资源、消息、事件
ApplicaitonContext除了具备如BeanFactory基本的容器管理功能之外,还支持更多应用程序框架的特性,像资源的取得、消息解析、事件的处理与传播,可以基于Spring框架打造属于自己的应用程序或框架。
2.4.1 资源的取得
Spring提供了对资源文件的泛型存取,ApplicationContext继承了org.springframework.core.io.ResourceLoader接口,您可以使用getResource()方法并指定资源文件资源文件的URL来取得一个实现Resource接口的实例,例如:
Resource resource = context.getResource("classpath:admin.properties");
"classpath:"是Spring自制的URL虚拟协定,这会取回一个org.springframework.core.io.ClassPathResource的实例,代表一个具体的资源文件。在上面的代码中,admin.properties是位于Classpath根目录的。也可以指定一个标准的URL,像file:或http:。
取得后可以使用getFile()、getInputStream()等方式来操作,可用exists()方法来测试指定的文件是否存在。
2.4.2 解析文字消息
ApplicationContext继承了org.springframework.context.MessageSource接口,可以使用getMessage()来取得文字消息的资源文件,从而实现国际化。
2.4.3 监听事件
在Spring应用程序执行中,ApplicatiionContext会发布一连串的事件,所有发布的事件都是抽象类org.springframework.context.ApplicationEvent的子类,例如:
l ContextClosedEvetnt
在ApplicaitonContext关闭时发布事件。
l ContextRefreshedEvent
在ApplicationContext初始或Refresh时发布事件。
l RequestHandledEvent
在Web应用程序中,当请求被处理时,ApplicationContext会发布此事件。
可以实现org.springframework.context.ApplicationListener接口,并在定义文件中定义实现该接口的一个bean实例。ApplicationContext会在ApplicationEvent发布时通知实现了ApplicationListener的bean实例。
实现了ApplicationListener接口的类有:
l ConsoleListener
仅在Debug时使用,会在输出狂中记录ApplicationContext的相关信息。
l PerformanceMonitorListener
在Web应用程序中,搭配WebApplicationContext使用,可记录请求的回应时间。
例如:
<beans.....>
<bean..id="listener"class="org.springframework.context.event.ConsoleListener" />
</beans>
在运行Spring应用程序时,ApplicationEvent相关事件会发布,可在输出栏中观察。
2.4.4 事件传播
如果打算通知ApplicationListener的实例,可以使用ApplicationContext的publicshEvent()方法。例如:
ApplicationContext context = new ClassPathXmlApplicationContext("conf.xml");
context.publishEvent(new ContextClosedEvent(context));
在定义文件中:
<bean id="listener" class="org.springframework.context.event.ConsoleListener"/>
则在控制台会显示事件发生时的相关信息。
自定义ApplicationListener与ApplicationEvent:
public class SomeEvent extends ApplicationEvent{
public SomeEvent(Object source)
{
super(source);
}
}
在构造SomeEvent时传递一个事件源,在SomeEvent被发布时,可以接收到并处理该事件。
public class CustomeListener implements ApplicationListener{
public void onApplicationEvent(ApplicationEvent event){
if(event instanceof SomeEvent){
System.out.print(event.getSource());
}
}
}
在bean定义文件中,简单定义下CustomListener:
<bean id="listener" class="...CustomListener" />
再写一个示范程序:Test.java
............
ApplicationContext context = new ClassPathXmlApplicationContext("conf.xml");
context.publishEvent(new SomeEvent(Test.class));
输出结果:
class (包名).Test