Spring 基礎語(yǔ)義
1,控制反轉(IoC = Inversion of Control) & 依賴(lài)注入(DI = Dependency Injection)
IoC:由容器控制程序之間的關(guān)系,而非傳統現實(shí)中,由程序代碼直接控制??刂茩嘤蓱么a中轉到了外部容器,控制權的轉移,即所謂反轉。
DI:即組件之間的依賴(lài)關(guān)系由容器在運行期決定,由容器動(dòng)態(tài)的將某種依賴(lài)關(guān)系注入到組件之中。
2, 依賴(lài)注入的幾種實(shí)現類(lèi)型
1)接口注入:
我們常常借助接口來(lái)將調用與實(shí)現者分離:
public class ClassA {
private InterfaceB clzB;
public doSomething() {
Ojbect obj = Class.forName(Config.BImplementation).newInstance();
clzB = (InterfaceB)obj;
clzB.doIt()
}
......
}
在上面的代碼中,ClassA依賴(lài)于InterfaceB的實(shí)現,如何獲得InterfaceB實(shí)現類(lèi)的實(shí)例?傳統的方法是在代碼中創(chuàng )建InterfaceB實(shí)現類(lèi)的實(shí)例,并將其賦予ClzB。
而這樣一來(lái),ClassA在編譯期即依賴(lài)于InterfaceB的實(shí)現。為了將調用者與實(shí)現者在編譯期分離,于是有了上面的代碼,我們根據預先在配置文件中設定的實(shí)現類(lèi)的類(lèi)名(Config.BImplementation),動(dòng)態(tài)加載實(shí)現類(lèi),并通過(guò)InterfaceB強制轉型后為ClassA所用。這就是接口注入的一個(gè)最原始的雛形。
而對于一個(gè)接口注入型IoC容器而言,加載接口實(shí)現并創(chuàng )建其實(shí)例的工作由容器完成。如:
public class ClassA {
private InterfaceB clzB;
public Object doSomething(InterfaceB b) {
clzB = b;
return clzB.doIt();
}
......
}
在運行期,InterfaceB實(shí)例將由容器提供。
2)設值注入
即通過(guò)類(lèi)的setter方法完成依賴(lài)關(guān)系的設置。
3)構造子注入
構造子注入,即通過(guò)構造函數完成依賴(lài)關(guān)系的設定,如:
public class DIByConstructor {
private final DataSource dataSource;
private final String message;
public DIByConstructor(DataSource ds, String msg) {
this.dataSource = ds;
this.message = msg;
}
......
}
在構造子注入的依賴(lài)注入機制中,依賴(lài)關(guān)系是通過(guò)類(lèi)構造函數建立,容器通過(guò)調用類(lèi)的構造方法,將其所需的依賴(lài)關(guān)系注入其中。
4)幾種依賴(lài)注入模式的對比總結
接口注入模式:由于其歷史悠久,在很多容器中都已經(jīng)得到了應用。但由于其在靈活性,易用性上不如其它兩種注入模式,因為在IOC的專(zhuān)題世界內并不被看好。
設值注入的優(yōu)勢:第一,對于習慣了傳統JavaBean開(kāi)發(fā)的程序員而言,通過(guò)setter方法設定依賴(lài)關(guān)系顯得更加直觀(guān),更加自然;第二,如果依賴(lài)關(guān)系(或繼承關(guān)系)較為復雜,那么構造子注入模式的構造函數也會(huì )相當龐大(我們需要在構造函數中設定所有的依賴(lài)關(guān)系),此時(shí)設值注入模式往往更為簡(jiǎn)潔;第三,對于某些第三方類(lèi)庫而演,可能要求我們的組件必須提供一個(gè)默認的構造函數,此時(shí)構造子注入模式的依賴(lài)注入機制就體現出其局限性,難以完成我們期望的功能。
構造子注入的優(yōu)勢:第一,"在構造期即創(chuàng )建一個(gè)完整合法的對象",對于這條java設計原則,構造子注入無(wú)疑是最好的響應者;第二,避免了繁瑣的setter方法的編寫(xiě),所有依賴(lài)關(guān)系均在構造函數中設定,依賴(lài)關(guān)系集中呈現,更加易讀;第三,由于沒(méi)有setter方法,所有依賴(lài)關(guān)系均在構造時(shí)由容器一次性設定,因此組件在被創(chuàng )建之后即處于相對"不變"的穩定狀態(tài),無(wú)需擔心上層代碼在調用過(guò)程中就執行setter方法對組件依賴(lài)關(guān)系產(chǎn)生破壞,特別是對于Singleton模式的組件而言,這可能對整個(gè)系統產(chǎn)生重大的影響;第四,同樣,對于關(guān)聯(lián)關(guān)系僅在構造函數中表達,只有組件創(chuàng )建者需要關(guān)心組件內部的依賴(lài)關(guān)系。對于調用者而言,組件中的依賴(lài)關(guān)系處于黑盒之中。對上層屏蔽不必要的信息,也為系統的層次清晰性提供了保證;第五,通過(guò)構造子注入,意味著(zhù)我們可以在構造函數中決定依賴(lài)關(guān)系的注入順序,對于一個(gè)大量依賴(lài)外部服務(wù)的組件而言,依賴(lài)關(guān)系的獲得順序可能非常重要,比喻某個(gè)依賴(lài)關(guān)系注入的先決條件是組件的DataSource及相關(guān)資源已經(jīng)被設定。
3,Spring Bean封裝機制
Spring從核心而言,是一個(gè)DI容器,其設計哲學(xué)是提供一個(gè)無(wú)侵入式的高擴展性框架。即無(wú)需代碼中涉及Spring專(zhuān)有類(lèi),即可將其納入Spring容器進(jìn)行管理。
作為對比,EJB則是一個(gè)高度侵入性的框架規范,它制定了眾多的接口和編碼規范,要求實(shí)現者必須遵從。侵入性的后果就是,一旦系統基于侵入性框架設計開(kāi)發(fā),那么之后任何脫離這個(gè)框架的企圖都將付出極大的代價(jià)。
為了避免這種情況,實(shí)現無(wú)侵入性的目標。Spring大量引入了java的Reflection機制,通過(guò)動(dòng)態(tài)調用的方式避免硬編碼方式的約束,并在此基礎上建立了其核心組件BeanFactory,以此作為其依賴(lài)注入機制的實(shí)現基礎。
org.springframework.beans包中包括了這些核心組件的實(shí)現類(lèi),核心中的核心為BeanWrapper和BeanFactory類(lèi)。這兩個(gè)類(lèi)從技術(shù)角度而言并不復雜,但對于Spring框架而言,卻是關(guān)鍵所在。
4,Bean Wrapper
所謂依賴(lài)注入,即在運行期由容器將依賴(lài)關(guān)系注入到組件之中。講得通俗點(diǎn),就是在運行期,由Spring根據配置文件,將其他對象的引用通過(guò)組件提供的setter方法進(jìn)行設定。
我們知道,如果動(dòng)態(tài)設置一個(gè)對象屬性,可以借助Java的Reflection機制完成:
Class cls = Class.forName("net.xiaxin.beans.User");
Method mtd = cls.getMethod("setName",new Class[]{String.class});
Object obj = (Object)cls.newInstance();
mtd.invoke(obj,new Object[]{"Erica"});
return obj;
上面我們通過(guò)動(dòng)態(tài)加載了User類(lèi),并通過(guò)Reflection調用了User.setName方法設置其name屬性。對于這里的例子而言,處于簡(jiǎn)潔,我們將類(lèi)名和方法名都以常量的方法硬編碼。假設這些常量都是通過(guò)配置文件讀入,那我們就實(shí)現了一個(gè)最簡(jiǎn)單的BeanWrapper。這個(gè)BeanWrapper的功能很簡(jiǎn)單,提供一個(gè)設置JavaBean屬性的通用方法(ApacheBeanUtils類(lèi)庫中提供了大量針對Bean的輔助工具,如果有興趣可以下載一份源代碼加以研讀)。
Spring BeanWrapper基于同樣的原理,提供了一個(gè)更加完善的實(shí)現??纯慈绾瓮ㄟ^(guò)Spring BeanWrapper操作一個(gè)JavaBean:
Object obj = Class.forName("net.xiaxin.beans.User").newInstance();
BeanWrapper bw = new BeanWrapperImpl(obj);
bw.setPropertyValue("name", "Erica");
System.out.println("User name=>"+bw.getPropertyValue("name"));
對比之前的代碼,相信大家已經(jīng)知道BeanWrapper的實(shí)現原理。
誠然,通過(guò)這樣的方式設定JavaBean屬性實(shí)在繁瑣,但它卻提供了一個(gè)通用的屬性設定機制,而這樣的機制,也正是Spring依賴(lài)注入機制所依賴(lài)的基礎。
通過(guò)BeanWrapper,我們可以無(wú)需在編碼時(shí)就指定JavaBean的實(shí)現類(lèi)和屬性值,通過(guò)在配置文件加以設定,就可以在運行期動(dòng)態(tài)創(chuàng )建對象并設定其屬性(依賴(lài)關(guān)系)。
上面的代碼中,我們僅僅指定了需要設定的屬性名"name",運行時(shí),BeanWrapper將根據JavaBean規范,動(dòng)態(tài)調用對象的"setName"方法進(jìn)行屬性設定。屬性名可以包含層次,如對于屬性名"address.zipcode",BeanWrapper會(huì )調用"getAddress().setZipcode"方法。
5,Bean Factory
顧名思義,負責創(chuàng )建并維護Bean實(shí)例。
Bean Factory負責根據配置文件創(chuàng )建Bean實(shí)例,可以配置的項目有:
1)Bean屬性值以及依賴(lài)關(guān)系(對其他Bean的引用)
2)Bean創(chuàng )建模式(是否Singleton模式,即是否只針對指定類(lèi)維持全局唯一的實(shí)例)
3)Bean初始化和銷(xiāo)毀方法
4)Bean的依賴(lài)關(guān)系
下面是一個(gè)較為完整的Bean配置示例:
Spring Bean Configuration Sample
id="TheAction" ⑴
class="net.xiaxin.spring.qs.UpperAction" ⑵
singleton="true" ⑶
init-method="init" ⑷
destroy-method="cleanup" ⑸
depends-on="ActionManager" ⑹
>
HeLLo ⑺
⑻
java:comp/env/jdbc/sample
(1)id:Java Bean在BeanFactory中的唯一標識,代碼中通過(guò)BeanFactory獲取JavaBean實(shí)例時(shí)需以此作為索引名稱(chēng)。
(2)class:Java Bean類(lèi)名。
(3)singleton:指定此JavaBean是否采用單例(singleton)模式,如果設置為"true",則在BeanFactory作用范圍內,只維護此JavaBean的一個(gè)實(shí)例,代碼通過(guò)BeanFactory獲得此JavaBean實(shí)例的引用。反之,如果設為"false",則通過(guò)BeanFactory獲取此JavaBean實(shí)例時(shí),BeanFactory每次都將創(chuàng )建一個(gè)新的實(shí)例返回。
(4)init-method:初始化方法,此方法將在BeanFactory創(chuàng )建JavaBean實(shí)例之后,在向應用層返回引用之前執行。一般用語(yǔ)一些資源的初始化工作。
(5)destroy-method:銷(xiāo)毀方法。此方法將在BeanFactory銷(xiāo)毀的時(shí)候執行,一般用于資源釋放。
(6)depends-on:Bean依賴(lài)關(guān)系。一般情況下無(wú)需設定。Spring會(huì )根據情況組織各個(gè)依賴(lài)關(guān)系的構建工作(這里示例中的depends-on屬性非必須)。只有在某些特殊情況下,如JavaBean中的某些靜態(tài)變量需要進(jìn)行初始化(這是一種BadSmell,應該在設計上避免)。通過(guò)depends-on指定其依賴(lài)關(guān)系可以保證在此Bean加載之前,首先對depends-on所指定的資源進(jìn)行加載。
(7):通過(guò)節點(diǎn)可指定屬性值。BeanFactory將自動(dòng)根據JavaBean對應的屬性類(lèi)型加以匹配。上面的"desc"屬性提供了一個(gè)null值的設定示例,需要注意的是代表一個(gè)空字符串,如果需要將屬性值設定為null,必須使用節點(diǎn)。
(8):指定了屬性對BeanFactory中其他Bean的引用關(guān)系。示例中,TheAction的dataSource屬性引用了id為dataSource的Bean。BeanFactory將在運行期創(chuàng )建dataSource bean實(shí)例,并將其引用傳入TheActionBean的dataSource屬性中。
下面的代碼演示了如何通過(guò)BeanFactory獲取Bean實(shí)例:
InputStream is = new FileInputStream("bean.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
Action action = (Action) factory.getBean("TheAction");
此時(shí)我們獲得的Action實(shí)例,由BeanFactory進(jìn)行加載,并根據配置文件進(jìn)行了初始化和屬性設定。
聯(lián)合上面關(guān)于BeanWrapper的內容,我們可以看到,BeanWrapper實(shí)現了針對單個(gè)Bean的屬性設定操作。而B(niǎo)eanFactory則是針對多個(gè)Bean的管理容器,根據給定的配置文件,BeanFactory從中讀取類(lèi)名,屬性名/值,然后通過(guò)Reflection機制進(jìn)行Bean加載和屬性指定。
6,ApplicationContext
BeanFactory提供了針對JavaBean的管理功能,而ApplicationContext提供了一個(gè)更為框架化的實(shí)現(從上面的示例中可以看出,BeanFactory的使用方式更加類(lèi)似一個(gè)API,而非Framework style)。
ApplicationContext覆蓋了BeanFactory的所有功能,提供了更為開(kāi)放式的實(shí)現(如對于Web應用,我們可以在web.xml中對ApplicationContext進(jìn)行配置)。
相對于BeanFactory而言,ApplicationContext提供了以下擴展功能:
1)國際化支持:
我們可以在Beans.xml文件中,對程序中的語(yǔ)言信息(如提示信息)進(jìn)行定義,將程序中的提示信息抽取到配置文件中加以定義,為我們進(jìn)行應用的各語(yǔ)言版本轉換提供了極大的靈活性。
2)資源訪(fǎng)問(wèn)
支持對文件和URL的訪(fǎng)問(wèn)
3)事件傳播
事件傳播特性為系統中狀態(tài)改變時(shí)的檢測提供了良好的支持。
4)多實(shí)例加載
可以在同一個(gè)應用中加載多個(gè)Context實(shí)例。
下面分別對這些特性進(jìn)行介紹:
1)國際化支持
國際化支持在實(shí)際開(kāi)發(fā)中可能是最常用的特性。對于一個(gè)需要支持不同語(yǔ)言環(huán)境的應用而言。我們所采取的最常用的策略一般是通過(guò)一個(gè)獨立的資源文件(如一個(gè)properties文件)完成所有語(yǔ)言信息(如界面上的提示信息)的配置。Spring對這種傳統的方式進(jìn)行了封裝,并提供了更加強大的功能,如信息的自動(dòng)裝配以及熱部署功能(配置文件修改后自動(dòng)讀取,而無(wú)需重新啟動(dòng)程序),下面是一個(gè)典型的示例:
Spring Quick Start
class="org.springframework.context.support.ResourceBundleMessageSource">
messages
這里聲明了一個(gè)名為messageSource的Bean(注意對于Message定義,BeanID必須為messageSource,這是目前Spring的編碼規約),對應類(lèi)為ResourceBundleMessageSource,目前Spring中提供了兩個(gè)MessageSource接口的實(shí)現,即ResourceBundleMessageSource和ReloadableResourceBundleMessageSource,后者提供了無(wú)需重啟即可重新加載配置信息的特性。
在配置節點(diǎn)中,我們指定了一個(gè)配置名"messages"。Spring會(huì )自動(dòng)在CLASSPATH根路徑中按照如下順序搜尋配置文件并進(jìn)行加載(以L(fǎng)ocale為zh_CH為例):
messages_zh_CN.properties
messages_zh.properties
messages.properties
messages_zh_CN.class
messages_zh.class
messages.class
(Spring實(shí)際上調用了JDK的ResourceBundle讀取配置文件)
示例中包含了兩個(gè)配置文件,內容如下:
messages_zh_CN.properties:
userinfo=當前登陸用戶(hù):[{0}] 登陸時(shí)間:[{1}]
messages_en_US.properties:
userinfo=Current Login user:[{0}] Login time:[{1}]
我們可以通過(guò)下面的語(yǔ)句進(jìn)行測試:
ApplicationContext ctx=new
FileSystemXmlApplicationContext("bean.xml");
Object[] arg = new Object[]{"Erica", Calendar.getInstance().getTime()};
//以系統默認Locale加載信息(對于中文WinXP而言,默認為zh_CN)
String msg = ctx.getMessage("userinfo", arg);
System.out.println("Message is ===> "+msg);
代碼中,我們將一個(gè)Object數組arg作為參數傳遞給ApplicationContext.getMessage方法,這個(gè)參數中包含了出現在最終文字信息中的可變內容,ApplicationContext將根據參數中的Locale信息對其進(jìn)行處理(如針對不同Locale設定日期輸出格式),并用其替換配置文件中的{n}標識(n代表參數數組中的索引,從1開(kāi)始)。
根據當前默認Locale"zh_CH",getMessage方法自動(dòng)加載了message_zh_CH.properties文件。JVM會(huì )根據當前系統的Locale設定進(jìn)行相應處理??梢酝ㄟ^(guò)在JVM啟動(dòng)參數中追加"-Duser.language=en"來(lái)設定當前JVM語(yǔ)言類(lèi)型,通過(guò)JVM級的設定,結合國際化支持功能,我們可以較為簡(jiǎn)單的實(shí)現多國語(yǔ)言系統的自動(dòng)部署切換。
getMessage還有一個(gè)中指定Locale參數的版本,直接指定加載對應的properties文件:
String msg = ctx.getMessage("userinfo", arg, Locale.US);
?。玻┵Y源訪(fǎng)問(wèn)
ApplicationContext.getResource方法提供了對資源文件訪(fǎng)問(wèn)支持,如:
Resource rs = ctx.getResource("classpath:config.properties");
File file = rs.getFile();
上例從CLASSPATH根路徑中查找config.properties文件并獲取其文件句柄。getResource方法的參數為一個(gè)資源訪(fǎng)問(wèn)地址,如:
file:C:/config.properties
/config.properties
classpath:config.properties
注意getResource返回的Resource并不一定實(shí)際存在,可以通過(guò)Resource.exists()方法對其進(jìn)行判斷。
3)事件傳播
ApplicationContext基于Observer模式(java.util包中有對應實(shí)現),提供了針對Bean的事件傳播功能。通過(guò)Application.publishEvent方法,我們可以將事件通知系統內所有的ApplicationListener。
事件傳播的一個(gè)典型應用是,當Bean中的操作發(fā)生異常(如數據庫連接失?。?,則通過(guò)事件傳播機制通知異常監聽(tīng)器進(jìn)行處理。
在目前版本的Spring中,事件傳播部分的設計還有待改進(jìn)。同時(shí),如果能夠進(jìn)一步支持異步事件處理機制,無(wú)疑更具吸引力。
org.springframework.context.event.ApplicationEventMulticasterImpl實(shí)現了事件傳播機制,目前還相當簡(jiǎn)陋。
在運行期,ApplicationContext會(huì )自動(dòng)在當前的所有Bean中尋找ApplicationListener接口的實(shí)現,并將其作為事件接收對象。當Application.publishEvent方法調用時(shí),所有的ApplicationListener接口實(shí)現都會(huì )被激發(fā),每個(gè)ApplicationListener可根據事件的類(lèi)型判斷是否是自己需要處理的事件。
7,Web Context
對于Web應用,Spring提供了可配置的ApplicationContext加載機制。
加載器目前有兩種選擇:ContextLoaderListener和ContextLoaderServlet。這兩者在功能上完全等同,只是一個(gè)是基于Servlet2.3版本中新引入的Listener接口實(shí)現,而另一個(gè)基于Servlet接口實(shí)現,開(kāi)發(fā)中可根據目標Web容器的實(shí)際情況進(jìn)行選擇。
配置非常簡(jiǎn)單,在web.xml中增加:
org.springframework.web.context.ContextLoaderListener
或:
context
org.springframework.web.context.ContextLoaderServlet
1
通過(guò)以上配置,Web容器會(huì )自動(dòng)加載/WEB-INF/applicationContext.xml初始化ApplicationContext實(shí)例,如果需要指定配置文件位置,可通過(guò)context-param加以指定:
contextConfigLocation
/WEB-INF/myApplicationContext.xml
配置完成之后,即可通過(guò):WebApplicationContextUtils.getWebApplicationContext方法在Web應用中獲取ApplicationContext引用。
8,Aspect Oriented Programming
OOP(面向對象編程)針對業(yè)務(wù)處理過(guò)程的實(shí)體及其屬性和行為進(jìn)行抽象封裝,以獲得更加清晰高效的邏輯單元劃分。
AOP(面向切面編程)針對業(yè)務(wù)處理過(guò)程中的切面進(jìn)行提取,它所面對的是處理過(guò)程中的某個(gè)步驟和階段,以獲得邏輯過(guò)程中各部分之間低耦合性的隔離效果。
AOP還有另外一個(gè)重要特點(diǎn):源碼組成無(wú)關(guān)性。倘若應用中通過(guò)某個(gè)具體的業(yè)務(wù)邏輯類(lèi)實(shí)現了獨立的權限檢查,而請求調度方法通過(guò)預編碼調用這個(gè)權限模塊實(shí)現權限管理,那么這也不算是AOP。對于A(yíng)OP組件而言,很重要的一點(diǎn)就是源碼組成無(wú)關(guān)性,所謂源碼組成無(wú)關(guān)性,體現在具體設計中就是AOP組件必須與應用代碼無(wú)關(guān),簡(jiǎn)單來(lái)講,就是應用代碼可以脫離AOP組件獨立編譯。
為了實(shí)現源碼組成無(wú)關(guān)性,AOP往往通過(guò)預編譯方式(如AspectJ)和運行期動(dòng)態(tài)代理模式(如Spring AOP和JBoss AOP)實(shí)現。
下面先來(lái)看AOP中幾個(gè)比較重要的概念:
1)切面(Aspect)
通過(guò)切面,我們可以將系統中各個(gè)不同層次上的問(wèn)題隔離開(kāi)來(lái),實(shí)現統一集約式處理。各個(gè)切面只需要集中于自己領(lǐng)域內的邏輯實(shí)現。這一方面使得開(kāi)發(fā)邏輯更加清晰,專(zhuān)業(yè)化分工更加易于進(jìn)行;另一方面,由于切面的隔離,降低了偶合性,我們就可以在不同的應用中將各個(gè)切面組合使用,從而使得代碼可重用性大大增強。
2)連接點(diǎn)(JoinPoint)
程序運行過(guò)程中的某個(gè)階段點(diǎn)。如某個(gè)方法調用,或者某個(gè)異常被拋出。
3)處理邏輯(Advice)
在某個(gè)連接點(diǎn)所采用的處理邏輯。處理邏輯調用模式通常有三種:
第一:Around,在連接點(diǎn)前后插入預處理過(guò)程和后處理過(guò)程;
第二:Before,僅在連接點(diǎn)之前插入預處理過(guò)程;
第三:Throw,在連接點(diǎn)拋出異常時(shí)進(jìn)行異常處理。
4)切點(diǎn)(PointCut)
一系列連接點(diǎn)的集合,它指明處理方式(Advice)將在何時(shí)被觸發(fā)。
9,AOP in Spring
Spring中提供的內置AOP支持,是基于動(dòng)態(tài)AOP機制實(shí)現。從技術(shù)角度來(lái)講,所謂動(dòng)態(tài)AOP,即通過(guò)動(dòng)態(tài)Proxy模式,在目標對象的方法調用前后插入相應的處理代碼。Spring AOP中的動(dòng)態(tài)Proxy模式,則是基于Java DynamicProxy(面向Interface)和CGLIB(面向Class)實(shí)現。
聯(lián)系客服