1.基本Bean配置
1.1Bean容器
1.2 Bean工廠-BeanFactory
BeanFactory factory=new XmlBeanFactory(new FileSystemResource("c:/beans.xml"));
Resource實現 | 目的 |
---|---|
org.springframework.core.io.ByteArray.ByteArrayResource | 由一組字節給定的資源 |
org.springframework.core.io.ClassPathResource | 從classpath提取的資源 |
org.springframework.core.io.DescriptiveResource | 定義包含資源描述符但是實際沒有可讀資源的資源 |
org.springframework.core.io.FileSystemResource | 從文件系統提取的資源 |
org.springframework.core.io.InputStreamResource | 從輸入流提取的資源 |
org.springframework.web.portlet.context.PortletContextResource | 在portlet上下文中的資源 |
org.springframework.web.context.support.ServletContextResource | 在servlet上下文中的資源 |
org.springframework.core.io.UrlResource | 從給定URL提取的資源 |
1.3 應用上下文-ApplicationContext
- 應用上下文提供了文本信息解析工具,包括對國際化(I18N)的支持
- 應用上下文提供了載入文件資源的通用方法,如載入圖片
- 應用上下文可以向註冊爲監聽器的Bean發送事件
- ClassPathXmlApplicationContext——從類路徑中的XML文件載入上下文定義信息,把上下文定義文件當成類路徑資源。
- FileSystemXmlApplicationContext——從文件系統中的XML文件載入上下文定義信息
- XmlWebApplicationContext——從Web系統中的XML文件載入上下文定義信息
ApplicationContext context=new FileSystemXmlApplicationContext("c:/foo.xml");
//or
ApplicationContext context=new ClassPathXmlApplicationContext("foo.xml");
1.4Bean的生命週期
步驟 | 說明 |
1.實例化 | Spring實例化Bean |
2.設置屬性 | Spring注入Bean的屬性 |
3.設置Bean名稱 | 如果Bean實現了BeanNameAware接口,Spring傳遞Bean工廠給setBeanFactory() |
4.預處理(在初始化之前) | 如果有多個BeanPostProcessor,Spring將調用postProcessBeforeInitialization()方法 |
5.初始化Bean | 如果Bean實現InitializingBean,其afterPropertiesSet()方法將被調用。如果Bean聲明瞭自定義的初始化方法,那麼將調用指定的初始化方法 |
6.預處理(在初始化之後) | 如果有多個BeanPostProcessor,Spring將調用postProcessAfterInitialization()方法 |
7.Bean已經準備好 | 此時Bean已經準備好,可以使用,並且將一直保留在Bean工廠中,直到不再需要它 |
8.銷燬Bean | 如果Bean實現了DisposableBean,將調用destroy()方法 如果Bean有自定義的銷燬方法,將調用指定的方法 |
1.5 Bean的創建
1.5.1 通過構造函數注入
<beans ...> <bean id="sonnet29" class="com.springinaction.springidol.Sonnet29" /> <bean id="duke" class="com.springinaction.springido1.PoeticJuggler"> <constructor-arg value="15" /> <!--注入基本類型和String類型--> <constructor-arg ref="sonnet29" /> <!--注入聲明的一個bean對象--> </bean> </beans>
1.5.2 通過setter方法注入
<beans ...> <bean id="kenny" class="com.springinaction.springido1.Instrumentalist"> <property name="song" value="Jingle Bells" /> <!--注入基本類型或String --> <property name="instrument" ref="saxophone" /> <!--注入已經聲明的bean --> </bean> </beans>
注入內部Bean:
<beans ...> <bean id="kenny" class="com.springinaction.springido1.Instrumentalist"> <property name="song" value="Jingle Bells" /> <!--注入基本類型或String --> <property name="instrument"> <bean class="org.springinaction.springido1.Saxophone" /> <!--不需要聲明id屬性 --> </property> </bean> </beans>
1.5.3 注入集合
集合元素 | 用途 |
---|---|
<list> | 裝配一列值,允許有重複值,可以與任意類型java.util.Collection或數組的屬性交換 |
<set> | 裝配值集,確保無重複值,可以與任意類型java.util.Collection中的屬性交換 |
<map> | 裝配鍵值對的集合,鍵和值可以是任意類型 |
<props> | 裝配鍵值對的集合,鍵和值都是String類型 |
1.5.3.1 <list>和<set>配置示例:
<bean id="bank" class="com.springinaction.springido1.OneManBand"> <property name="instruments"> <list><!--可以換用<set>元素 --> <ref bean="guitar" /> <ref bean="cymbal" /> <ref bean="harmonica" /> <ref bean="harmonica" /> </list> </property> </bean>
1.5.3.4 <map>配置示例:
<bean id="hank" class="com.springinaction.springido1.OneManBand"> <property name="instruments"> <map> <entry key="GUITAR" value-ref="guitar" /> <entry key="CYMBAL" value-ref="cymbal" /> <entry key="HARMONICA" value-ref="harmonica" /> </map> </property> </bean>
屬性 | 目的 |
---|---|
key | 指定map項的鍵爲String |
key-ref | 指定map項的鍵爲Spring上下文中Bean的引用 |
value | 指定map項的值爲String |
value-ref | 指定map項的值爲Spring上下文中Bean的引用 |
1.5.3.5 <props>配置示例:
<bean id="hank" class="com.springinaction.springido1.OneManBand"> <property name="instruments"> <props> <prop key="GUITAR">STRUM STRUM STRUM</prop> <prop key="CYMBAL">CRASH CRASH CRASH</prop> <prop key="HARMONICA">HUM HUM HUM</prop> </props> </property> </bean>
1.5.4 裝配空值
<property name="someNonNullProperty"><null /></property>
1.6 自動裝配
1.6.1 四種自動裝配類型
- byName——試圖在容器中尋找和需要自動裝配的屬性名相同的Bean(或ID)。如果沒有找到相符的Bean,這個屬性就沒有被裝配上。
- byType——試圖在容器中尋找一個與需要自動配置的屬性類型相同的Bean。如果沒有找到相符的Bean,這個屬性就沒有被裝配。如果找到超過一個相符的Bean,會拋出org.springframework.beans.factory.UnsatisfiedDependencyException異常
- constructor——試圖在容器中查找與需要自動裝配的Bean的構造函數參數一致的一個或多個Bean。如果存在不確定Bean或構造函數,容器會拋出異常org.springframework.beans.factory.UnsatisfiedDependencyException異常
- autodetect——首先嚐試使用constructor來自動裝配,然後使用byType方式。不確定性的處理與constructor方式和byType方式一樣。
<bean id="kenny" class="com.springinaction.springido1.Instrumentalist" autowire="byName" > <property name="song" value="Jingle Bells" /> <!--當沒有添加autowire的時候,需要顯示地聲明instrument屬性的值,現在添加了自動裝配功能,那麼instrument屬性便會自動地使用id爲instrument的bean來裝配instrument屬性 --> <!-- <property name="instrument" ref="instrument" /> --> </bean>
1.6.2 默認自動裝配
<beans default-autowire="byName"> ... </beans>
1.7控制Bean創建
1.7.1 Bean範圍
範圍 | 完成任務 |
---|---|
singleton | 定義Bean的範圍爲每個Spring容器一個實例(默認值) |
prototype | 允許Bean可以被多次實例化(使用一次就創建一個實例) |
request | 定義Bean的範圍是HTTP請求。只有在使用有Web能力的Spring上下文時纔有效 |
session | 定義Bean的範圍是HTTP會話。只有在使用有Web能力的Spring上下文時纔有效 |
global-session | 定義Bean的範圍是全局HTTP會話。只有在portlet上下文中有效 |
1.7.2 利用工廠方法創建Bean
<bean id="theStage" class="com.springinaction.springido1.Stage" factory-method="getInstance" />
1.7.3 初始化和銷燬Bean
<bean id="kenny" class="com.springinaction.springido1.Instrumentalist" init-method="tuneInstrument" destroy-method="cleanInstrument" > <property name="song" value="Jingle Bells" /> <property name="instrument" ref="saxophone" /> </bean>
默認的初始化和銷燬方法
<beans default-init-method="init" default-destroy-method="destroy"> ... </beans>
2 高級Bean裝配
2.1 Bean的繼承
- parent:指明Bean的id。它對於<bean>的作用就相當於關鍵字extends對於Java類的作用。
- abstract:如果設置爲true,就表示<bean>聲明是抽象的,不能被Spring實例化。
<bean id="baseSaxophonist" class="com.springinaction.springido1.Instrumentalist" abstract="true"> <property name="instrument" ref="saxophone" /> <property name="song" value="Jingle Bells" /> </bean> <bean id="kenny" parent="baseSaxophonist" /> <bean id="david" parent="baseSaxophonist"> <property name="song" value="Mary had a little lamb" /><!--覆蓋song屬性,不繼承baseSaxophonist中的song屬性--> </bean>
2.1.1 抽象共同屬性
<bean id="basePerformer" abstract="true"> <property name="song" value="Somewhere Over the Rainbow" /> </bean> <bean id="taylor" class="com.springinaction.springidol.Vocalist" parent="basePerformer"> <property name="instrument" ref="guitar" /> </bean> <bean id="stevie" class="com.springinaction.springido1.Instrumentalist" parent="basePerformer"> <property name="instrument" ref="saxophone" /> </bean>
2.2 方法注入
- 方法替換:可以在運行時用新的實現替換現有方法(抽象或具體的)。
- 獲取器注入:可以在運行時用新的實現代替現有方法(抽象或具體的),從Spring上下文中返回特定的Bean。
2.2.1 基本方法替換
package com.springinaction.springido1;
//魔術師
public class Magician implements Performer{
public Magician(){
}
public void perform() throws PerformanceException{
System.out.println(magicWords);
System.out.println("The magic box contains...");
System.out.println(magicBox.getContents());
}
private MagicBox magicBox;
public void setMagicBox(MagicBox magicBox){
this.magicBox=magicBox;
}
private String magicWords;
public void setMagicWords(String magicWords){
this.magicWords=magicWords;
}
}
package com.springinaction.springido1;
//魔法盒——魔法盒裏永遠都是一位美麗的助手
public class MagicBoxImpl implements MagicBox{
public MagicBoxImpl(){}
public String getContents(){
return "A beautiful assistant";
}
}
當前狀態下,這位魔法師永遠只能從魔法盒中變出一位美麗的助手,而不能變出一頭老虎。如何讓魔法師能變出老虎呢?這就需要用到Spring的基本方法替換了。package com.springinaction.springido1;
import java.lang.reflect.Method;
import org.springframework.beans.factory.support.MethodReplacer;
public class TigerReplacer implements MethodReplacer{
public Object reimplement(Object target,Method method,Object[] args) throws Throwable{
return "A ferocious tiger";
}
}
這裏TigerReplacer實現了Spring的MethodReplacer接口,該接口只需要實現reimplement方法。reimplement方法包含有三個參數:要替換方法的目標對象、要被替換的方法、傳遞給方法的參數。下面就是通過xml配置來實現基本方法替換:<bean id="magicBox" class="com.springinaction.springido1.MagicBoxImpl"> <replaced-method name="getContents" replacer="tigerReplacer" /> </bean> <bean id="tigerReplacer" class="com.springinaction.springido1.TigerReplacer" />
2.2.2 獲取器注入(getter 注入)
package com.springinaction.springido1;
public abstract class Instrumentalist implements Performer{
public Instrumentalist(){}
public void perform() throws PerformanceException{
System.out.print("Playing "+song+":");
//使用注入的getInstrument()方法
getInstrument().play();
}
private String song;
public void setSong(String song){
this.song=song;
}
public abstract Instrument getInstrument();//注入getInstrument()
}
那麼,現在就可以使用<bean>下的<lookup-method>子元素來聲明抽象的getter方法返回的是哪個bean,如下<bean id="stevie" class="com.springinaction.springido1.Instrumentalist">
<!--
name屬性指示的是要被替換的方法,bean屬性指示的是該方法返回的是上下文中的哪個bean
-->
<lookup-method name="getInstrument" bean="guitar" />
<property name="song" value="Greensleeves" />
</bean>
2.3 向非Spring Bean注入
<bean id="pianist" class="com.springinaction.springido1.Instrumentalist" abstract="true">
<property name="song" value="Chopsticks" />
<property name="instrument">
<bean class="com.springinaction.springido1.Piano" />
</property>
</bean>
另外,還得給Instrumentalist類級別上添加Configurable註解:
package com.springinaction.springido1;
import org.springframework.beans.factory.annotaion.Configurable;
@Configurable("pianist")
public class Instrumentalist implements Performer{
...
}
- 第一,它表示Instrumentalist實例即使是在Spring之外創建的,仍然可以由Spring進行配置
- 第二,它把Instrumentalist類與id爲pianist的Bean關聯起來。當Spring企圖配置Instumentalist實例時,會以pianist Bean作爲模板。
<aop:spring-configured />
2.4 註冊自定義屬性編輯器
<property name="wsdlDocumentUrl" value="http://www.xmethods.net/sd/BabelFishService.wsdl" />
實際上,這個功能並不是由Spring提供的,而是來自原始JavaBeans API的一個鮮爲人知的特性。Java.beans.PropertyEditor接口提供了一種手段,讓我們能夠自定義String如何映射到非String值。java.beans.PropertyEditorSupport是這個接口的一種簡便實現方式,它有兩個方法比較吸引人:
- getAsText()——返回屬性值的String表達形式。
- setAsText(String value)——把傳遞來的String值設置給Bean的屬性
屬性編輯器 | 功能 |
---|---|
ClassEditor | 從一個String值設置java.lang.Class屬性,前者包含一個完整描述的類名 |
CustomDateEditor | 從一個String值設置java.util.Date屬性,前者使用自定義的java.text.DateFormat對象 |
FileEditor | 從一個String值設置java.io.File屬性,前者包含文件的路徑 |
LocalEditor | 從一個String值設置java.util.Locale屬性,前者包含地域的文本表示(比如en_US) |
StringArrayPropertyEditor | 把逗號分隔的String轉化爲一個String數組屬性 |
StringTrimmerEditor | 對String屬性進行自動裁剪;設置一個選項後可以把空的String值轉化爲null |
URLEditor | 從一個String值設置java.net.URL屬性,前者包含一個URL |
public class PhoneNumber{
private String areaCode;
private String prefix;
private String number;
//getter and setter method
}
自定義PhoneEditor:public class PhoneEditor extends java.beans.PropertyEditorSupport{
public void setAsText(String textValue){
String stripped=stripNonNumeric(textValue);
String areaCode=stripped.substring(0,3);
String prefix=stripped.substring(3,6);
String number=stripped.substring(6);
PhoneNumber phone=new PhoneNumber(areaCode,prefix,number);
setValue(phone);
}
private String stripNonNumeric(String original){
//...
}
}
最後,我們得讓Spring在裝配Bean屬性時能夠知道我們的自定義編輯器,爲此,需要使用Spring的CustomEditorConfigurer,它是一個BeanFactoryPostProcessor,通過調用registerCustomEditor()方法把自定義編輯器加載到BeanFactory。(或者,在得到了Bean工廠的實例之後,我們可以自己在代碼裏調用registerCustomEditor()方法。)下面是XML的配置:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="com.springinaction.chapter03.propeditor.PhoneNumber">
<bean id="phoneEditor" class="com.springinaction.chapter03.propeditor.PhoneEditor"></bean>
</entry>
</map>
</property>
</bean>
現在,我們就可以用簡單的String值來配置Contact對象的phoneNumber屬性。
2.5 使用Spring的特殊Bean
- 通過對Bean配置的後處理來介入Bean的創建和Bean工廠的生命週期
- 從外部屬性文件加載配置信息
- 從屬性文件加載文本消息,包括國際化的消息
- 監聽和響應由其他Bean和Spring容器本身發佈的程序事件
- 知道它們在Spring容器裏的身份
2.5.1 後處理Bean(BeanPostProcessor)
public interface BeanPostProcessor{
Object postProcessBeforeInitialization(Object bean,String name) throws BeansException;
Object postProcessAfterInitialization(Object bean,String name) throws BeansException;
}
下面是對BeanPostProcessor應用的一個例子:public class Fuddifier implements BeanPostProcessor{
public Object postProcessAfterInitialization(Object bean,String name) throws BeansException{
Field[] fields = bean.getClass().getDeclaredFields();
try{
for(int i=0;i<fields.length;i++){
if(fields[i].getType().equals(java.lang.String.class)){
fields[i].setAccessible(true);
String original=(String)fields[i].get(bean);
fields[i].set(bean,fuddify(original));
}
}
}catch(IllegalAccessException e){
e.printStackTrace();
}
return bean;
}
private String fuddify(String orig){
//...
}
public Object postProcessBeforeInitialization(Object bean,String name) throws BeansException{
return bean;
}
}
最後,還得註冊Bean後處理器。
BeanPostProcessor fuddifier=new Fuddifier();
factory.addBeanPostProcessor(fuddifier);
如果程序運行在ApplicationContext,那麼只需要通過XML配置就可以自動註冊BeanPostProcessor:
<bean class="com.springinaction.chapter03.postprocessor.Fuddifier" />
2.5.2 Bean工廠的後處理(BeanFactoryPostProcessor)
public interface BeanFactoryPostProcessor{
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
在全部Bean定義被加載之後,但在任何一個Bean被實例化之前(包括BeanPostProcessor Bean),Spring容器會調用postProcessBeanFactory()方法。<bean id="beanFactoryPostProcessor" class="beanFactoryPostProcessorImpl" />
2.5.3 配置屬性的外在化——PropertyPlaceholderConfigurer
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="jdbc.properties" />
</bean>
jdbc.properties的配置信息如下:
database.url=jdbc:hsqldb:training
database.driver=org.hsqldb.jdbcDriver
...
如果需要把配置分散到多個屬性文件裏,應該使用PropertyPlaceholderConfigurer的locations屬性來設置屬性文件的List:
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>jdbc.properties</value>
<value>security.properties</value>
<value>application.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${database.url}" />
...
</bean>
2.5.4 國際化
Spring的ApplicationContext可以通過MessageSource接口把國際化消息提供給容器。Spring提供了MessageSource的一個實現——ResourceBundleMessageSource就是使用java自己的java.util.ResourceBundle來提取消息。配置如下:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename">
<value>trainingtext</value>
</property>
</bean>
Notic:這個Bean的名稱必須是messageSource,因爲ApplicationContext在建立其內部消息源時,會查找這個名稱的Bean。我們可以通過ApplicationContext.getMessage()方法來訪問消息。
Local local=...;
String text=context.getMessage("computer",new Object[0],locale);
在Spring的web模塊中,可以使用Spring的JSP標記<spring:message>獲取消息。
<spring:message code="computer" />
2.5.5 程序事件的解耦
在Spring裏,容器中的任何Bean都可以作爲事件監聽器、事件發佈者或兩者都是。
2.5.5.1 發佈事件
public class CourseFullEvent extends ApplicationEvent{
private Course course;
public CourseFullEvent(Object source,Course course){
super(source);
this.course=course;
}
public Course getCourse(){
return course;
}
}
接着通過ApplicationContext接口的publishEvent()方法可以發佈ApplicationEvents。在程序上下文裏註冊的任何一個ApplicationLIstener都會由它的onApplicationEvent()方法接收並處理事件:
ApplicationContext context=...;
Course course=...;
context.publishEvent(new CourseFullEvent(this,course));
爲了發佈事件,Bean需要訪問ApplicationContext,這意味着Bean必須瞭解其運行所在的容器。
2.5.5.2 監聽事件
- ContextCloseEvent:程序上下文被關閉時發佈。
- ContextRefreshedEvent:程序上下文被初始化或刷新時發佈。
- RequestHandleEvent:當一個請求被處理時,在Web程序上下文裏發佈。
public class RefreshListener implements ApplicationListener{
public void onApplicationEvent(ApplicationEvent event){
...
}
}
要註冊監聽器,只需要在xml中配置這個Bean:<bean id="refreshListener" class="com.springinaction.foo.RefreshListener" />
這時,當有事件發佈時便會調用onApplicationEvent()方法
2.6 讓Bean知道更多
2.6.1 讓Bean知道自己的名稱
public interface BeanNameAware{
void setBeanName(String name);
}