现在的位置: 首页 > 综合 > 正文

Spring学习笔记(二)Bean

2012年07月13日 ⁄ 综合 ⁄ 共 15540字 ⁄ 字号 评论关闭

二、Bean、消息、事件

Spring的核心是一个容器,它实现了IoC的概念,可以协助管理各个对象的生命周期,以及对象之间的依赖关系。熟悉使用BeanFactoryApplicationContext的运用是了解Spring的重点所在。

作为一个应用程序框架,ApplicationContext除了具备BeanFactory基本的容器管理功能之外,还能支持更多应用程序框架的特性,像资源的取得、文字消息的解析、事件的处理与传播等特性。

2.1 Bean基本管理

2.1.1 BeanFactoyApplicationContext

BeanFactory负责读取Bean定义文件;管理对象的加载、生成;维护BeanBean对象之间的依赖关系;负责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();

}

}

若要通过MusicBoxFactorycreateMusicBox()方法来取得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 BeanScope

Spring中,从BeanFactoryApplicationContext取得的实例被默认为Singleton,也就是默认为每一个Bean名称只维持一个实例。

为防止多线程同时存取公用资源所引发的资料不同步问题,通常SingletonBean都是无状态的(Stateless)。

然而可以设置每次从容器中取得的都是一个新的实例,例如:

<bean id="helloBean" class="..." scope="prototype">

.......

也可以通过设置<bean>singleton属性为truefalse,来设置是否以Singleton的方式产生实例。

scope除了可以设置singletonprototype之外,针对Web应用环境,还可以设置requestsessionglobalSession,分别表示请求阶段、会话阶段与基于PortletWeb应用程序会话阶段。

2.1.5 Bean的生命周期

l Bean的建立

BeanFactory读取Bean定义文件,并生成各个Bean实例。

l 属性注入

执行相关的Bean属性依赖注入。

l BeanNameAwaresetBeanName()

如果Bean类有实现org.springframework.beans.factory.BeanNameAware接口,则执行它的setBeanName()方法。

l BeanFactoryAwaresetBeanFactory()

如果Bean类有实现org.springframework.beans.factory.BeanNameAware接口,则执行它的setBeanName()方法。

l BeanPostProcessorsprocessBeforeInitialization()

如果有任何的org.springframework.beans.factory.config.BeanPostProcessors实例与Bean实例关联,则执行BeanPostProcessors实例的processBeforeInitialization()方法。

l InitializationBeanafterPropertiesSet()

如果Bean类有实现org.springframewor.beans.factory.InitializingBean,则执行它的afterPropertiesSet()方法。

l Bean定义文件中定义init-method

可以在Bean定义文件使用"init-method"属性设置方法名称,例如:

.....

<bean id="helloBean" class="..." init-method="initBean" >

.....

l BeanPostProcessorsprocessAfterInitialization()

如果有任何的BeanPostProcessors实例与Bean实例关联,则执行BeanPostProcessors实例的processAfterInitialization()方法。

l DisposableBeandestroy()

在容器关闭时,如果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-methoddestroy()不会耦合SpringAPI

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>

someBeanabstract="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 InjectionConstructor 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>

SpringIoc容器会自动生成Date实例,并通过setDate()方法将Date实例设置给bean

在取得某个bean之前,如果它依赖于另一个beanSpring就会先去实例化被依赖的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还可以隐式的自动绑定,通过byTypebyName。例如:

<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>

helloBeansetDate()将会自动绑定。

构造方法中也可以自动绑定。

<bean ..... autowire="constructor">

Spring容器会对比容器中的Bean实例类型和构造函数上的参数类型,若相符则选择用该构造函数来建立bean实例。

若想偷懒,还可以设置autowire="autodetect"。这种方式会先autowire="constructor"。若没有则autowire="byType" 

bean标签中使用dependency-check,可以有4中依赖方式:simpleobjectsallnone

simple只检测简单的属性是否完成依赖关系。object则检测对象类型的属性是否完成依赖关系。all是全部。none是默认值,不检测。

2.2.4 集合对象

数组、java.util.Listjava.util.Setjava.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的弹性。但是配置文件中单纯的将Messagescope属性设置为prototype是行不通的,因为只有在通过BeanFactoryApplicationContextgetBean()来取得Message时,才会重新实例化一个Message

这时可以使用SpringLookup 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

属性文件中helloBeanbean的别名设置,即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知道自己在容器中是被哪个别名管理的,或者让它知道BeanFactoryApplicationContext的存在,也就是让Bean可以取得目前运行中的BeanFactoryApplicaitonContext的实例。

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文件的指定被设置在configBeanlocation属性中,hello.properties的内容如下:

onlyfun.caterpillar.helloWord=welcome

helloBeanhelloWord属性中,${onlyfun.caterpillar.helloWord}将会被hello.propertiesonlyfun.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发布时通知实现了ApplicationListenerbean实例。

实现了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的实例,可以使用ApplicationContextpublicshEvent()方法。例如:

ApplicationContext context = new ClassPathXmlApplicationContext("conf.xml");

context.publishEvent(new ContextClosedEvent(context));

在定义文件中:

<bean id="listener" class="org.springframework.context.event.ConsoleListener"/>

则在控制台会显示事件发生时的相关信息。

自定义ApplicationListenerApplicationEvent

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

抱歉!评论已关闭.