#
http://www.zhuicha.com/zhuicha/onlineHB/linuxcmd/ 此網(wǎng)站列舉了常用命令
文件傳輸
備份壓縮
文件管理
磁盤管理
磁盤維護(hù)
系統(tǒng)設(shè)置
系統(tǒng)管理
文檔編輯
網(wǎng)絡(luò)通訊
電子郵件與新聞組
X WINDOWS SYSTEM
對于這個系列里的問題,每個學(xué)Java的人都應(yīng)該搞懂。當(dāng)然,如果只是學(xué)Java玩玩就無所謂了。如果你認(rèn)為自己已經(jīng)超越初學(xué)者了,卻不很懂這些問題,請將你自己重歸初學(xué)者行列。內(nèi)容均來自于CSDN的經(jīng)典老貼。
問題一:我聲明了什么!
String s = "Hello world!";
許多人都做過這樣的事情,但是,我們到底聲明了什么?回答通常是:一個String,內(nèi)容是“Hello world!”。這樣模糊的回答通常是概念不清的根源。如果要準(zhǔn)確的回答,一半的人大概會回答錯誤。
這個語句聲明的是一個指向?qū)ο蟮囊茫麨?#8220;s”,可以指向類型為String的任何對象,目前指向"Hello world!"這個String類型的對象。這就是真正發(fā)生的事情。我們并沒有聲明一個String對象,我們只是聲明了一個只能指向String對象的引用變量。所以,如果在剛才那句語句后面,如果再運行一句:
String string = s;
我們是聲明了另外一個只能指向String對象的引用,名為string,并沒有第二個對象產(chǎn)生,string還是指向原來那個對象,也就是,和s指向同一個對象。
問題二:"=="和equals方法究竟有什么區(qū)別?
==操作符專門用來比較變量的值是否相等。比較好理解的一點是:
int a=10;
int b=10;
則a==b將是true。
但不好理解的地方是:
String a=new String("foo");
String b=new String("foo");
則a==b將返回false。
根據(jù)前一帖說過,對象變量其實是一個引用,它們的值是指向?qū)ο笏诘膬?nèi)存地址,而不是對象本身。a和b都使用了new操作符,意味著將在內(nèi)存中產(chǎn)生兩個內(nèi)容為"foo"的字符串,既然是“兩個”,它們自然位于不同的內(nèi)存地址。a和b的值其實是兩個不同的內(nèi)存地址的值,所以使用"=="操作符,結(jié)果會是false。誠然,a和b所指的對象,它們的內(nèi)容都是"foo",應(yīng)該是“相等”,但是==操作符并不涉及到對象內(nèi)容的比較。
對象內(nèi)容的比較,正是equals方法做的事。
看一下Object對象的equals方法是如何實現(xiàn)的:
boolean equals(Object o){
return this==o;
}
Object對象默認(rèn)使用了==操作符。所以如果你自創(chuàng)的類沒有覆蓋equals方法,那你的類使用equals和使用==會得到同樣的結(jié)果。同樣也可以看出,Object的equals方法沒有達(dá)到equals方法應(yīng)該達(dá)到的目標(biāo):比較兩個對象內(nèi)容是否相等。因為答案應(yīng)該由類的創(chuàng)建者決定,所以O(shè)bject把這個任務(wù)留給了類的創(chuàng)建者。
看一下一個極端的類:
Class Monster{
private String content;
...
boolean equals(Object another){ return true;}
}
我覆蓋了equals方法。這個實現(xiàn)會導(dǎo)致無論Monster實例內(nèi)容如何,它們之間的比較永遠(yuǎn)返回true。
所以當(dāng)你是用equals方法判斷對象的內(nèi)容是否相等,請不要想當(dāng)然。因為可能你認(rèn)為相等,而這個類的作者不這樣認(rèn)為,而類的equals方法的實現(xiàn)是由他掌握的。如果你需要使用equals方法,或者使用任何基于散列碼的集合(HashSet,HashMap,HashTable),請察看一下java doc以確認(rèn)這個類的equals邏輯是如何實現(xiàn)的。
繼續(xù)深入討論一下“==”和equals,看如下例子
public class TestStringIntern {
public static void main(String[] args) {
testIt();
}
private static void testIt() {
String s1 = "sean_gao";
String s2 = "sean"+"_"+"gao";
String s3 = new String("sean_gao");
String s4 = new String("sean_gao").intern();
System.out.println("s1==s2? "+(s1==s2));
System.out.println("s1==s3? "+(s1==s3));
System.out.println("s1==s4? "+(s1==s4));
System.out.println("s1.equals(s2)? "+(s1.equals(s2)));
System.out.println("s1.equals(s3)? "+(s1.equals(s3)));
System.out.println("s1.equals(s4)? "+(s1.equals(s4)));
}
}
以下是結(jié)果:
s1==s2? true // 引用的是同一個對象,因為內(nèi)容一致
s1==s3? false // 引用的是不同的對象,因為用了new關(guān)鍵字
s1==s4? true // 引用的是同一個對象,因為用了intern方法
s1.equals(s2)? true // 內(nèi)容一致
s1.equals(s3)? true // 內(nèi)容一致
s1.equals(s4)? true // 內(nèi)容一致
再次解釋:對于String對象,如果是按照String s = "some string";這樣的形式聲明的,如果同一個JVM中恰好有相同內(nèi)容的String對象,那么這個s指向的就是那個已有的對象。但如果使用String s = new String("some string");這樣的語法,那么不管JVM中有沒有可以重用的String對象,都將新建一個對象。
==操作符判斷的是對象引用是否指向同一個對象,而equals方法在String類中的實現(xiàn)是判斷String對象的內(nèi)容是否一致。
問題三:String到底變了沒有?
沒有。因為String被設(shè)計成不可變(immutable)類,所以它的所有對象都是不可變對象。請看下列代碼:
String s = "Hello";
s = s + " world!";
s所指向的對象是否改變了呢?從本系列第一篇的結(jié)論很容易導(dǎo)出這個結(jié)論。我們來看看發(fā)生了什么事情。在這段代碼中,s原先指向一個String對象,內(nèi)容是"Hello",然后我們對s進(jìn)行了+操作,那么s所指向的那個對象是否發(fā)生了改變呢?答案是沒有。這時,s不指向原來那個對象了,而指向了另一個String對象,內(nèi)容為"Hello world!",原來那個對象還存在于內(nèi)存之中,只是s這個引用變量不再指向它了。
通過上面的說明,我們很容易導(dǎo)出另一個結(jié)論,如果經(jīng)常對字符串進(jìn)行各種各樣的修改,或者說,不可預(yù)見的修改,那么使用String來代表字符串的話會引起很大的內(nèi)存開銷。因為String對象建立之后不能再改變,所以對于每一個不同的字符串,都需要一個String對象來表示。這時,應(yīng)該考慮使用StringBuffer類,它允許修改,而不是每個不同的字符串都要生成一個新的對象。并且,這兩種類的對象轉(zhuǎn)換十分容易。
同時,我們還可以知道,如果要使用內(nèi)容相同的字符串,不必每次都new一個String。例如我們要在構(gòu)造器中對一個名叫s的String引用變量進(jìn)行初始化,把它設(shè)置為初始值,應(yīng)當(dāng)這樣做:
public class Demo {
private String s;
...
public Demo {
s = "Initial Value";
}
...
}
而非
s = new String("Initial Value");
后者每次都會調(diào)用構(gòu)造器,生成新對象,性能低下且內(nèi)存開銷大,并且沒有意義,因為String對象不可改變,所以對于內(nèi)容相同的字符串,只要一個String對象來表示就可以了。也就說,多次調(diào)用上面的構(gòu)造器創(chuàng)建多個對象,他們的String類型屬性s都指向同一個對象。
上面的結(jié)論還基于這樣一個事實:對于字符串常量,如果內(nèi)容相同,Java認(rèn)為它們代表同一個String對象。而用關(guān)鍵字new調(diào)用構(gòu)造器,總是會創(chuàng)建一個新的對象,無論內(nèi)容是否相同。
至于為什么要把String類設(shè)計成不可變類,是它的用途決定的。其實不只String,很多Java標(biāo)準(zhǔn)類庫中的類都是不可變的。在開發(fā)一個系統(tǒng)的時候,我們有時候也需要設(shè)計不可變類,來傳遞一組相關(guān)的值,這也是面向?qū)ο笏枷氲捏w現(xiàn)。不可變類有一些優(yōu)點,比如因為它的對象是只讀的,所以多線程并發(fā)訪問也不會有任何問題。當(dāng)然也有一些缺點,比如每個不同的狀態(tài)都要一個對象來代表,可能會造成性能上的問題。所以Java標(biāo)準(zhǔn)類庫還提供了一個可變版本,即StringBuffer。
問題四:final關(guān)鍵字到底修飾了什么?
final使得被修飾的變量"不變",但是由于對象型變量的本質(zhì)是“引用”,使得“不變”也有了兩種含義:引用本身的不變,和引用指向的對象不變。
引用本身的不變:
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//編譯期錯誤
引用指向的對象不變:
final StringBuffer a=new StringBuffer("immutable");
a.append(" broken!"); //編譯通過
可見,final只對引用的“值”(也即它所指向的那個對象的內(nèi)存地址)有效,它迫使引用只能指向初始指向的那個對象,改變它的指向會導(dǎo)致編譯期錯誤。至于它所指向的對象的變化,final是不負(fù)責(zé)的。這很類似==操作符:==操作符只負(fù)責(zé)引用的“值”相等,至于這個地址所指向的對象內(nèi)容是否相等,==操作符是不管的。
理解final問題有很重要的含義。許多程序漏洞都基于此----final只能保證引用永遠(yuǎn)指向固定對象,不能保證那個對象的狀態(tài)不變。在多線程的操作中,一個對象會被多個線程共享或修改,一個線程對對象無意識的修改可能會導(dǎo)致另一個使用此對象的線程崩潰。一個錯誤的解決方法就是在此對象新建的時候把它聲明為final,意圖使得它“永遠(yuǎn)不變”。其實那是徒勞的。
問題五:到底要怎么樣初始化!
本問題討論變量的初始化,所以先來看一下Java中有哪些種類的變量。
1. 類的屬性,或者叫值域
2. 方法里的局部變量
3. 方法的參數(shù)
對于第一種變量,Java虛擬機會自動進(jìn)行初始化。如果給出了初始值,則初始化為該初始值。如果沒有給出,則把它初始化為該類型變量的默認(rèn)初始值。
int類型變量默認(rèn)初始值為0
float類型變量默認(rèn)初始值為0.0f
double類型變量默認(rèn)初始值為0.0
boolean類型變量默認(rèn)初始值為false
char類型變量默認(rèn)初始值為0(ASCII碼)
long類型變量默認(rèn)初始值為0
所有對象引用類型變量默認(rèn)初始值為null,即不指向任何對象。注意數(shù)組本身也是對象,所以沒有初始化的數(shù)組引用在自動初始化后其值也是null。
對于兩種不同的類屬性,static屬性與instance屬性,初始化的時機是不同的。instance屬性在創(chuàng)建實例的時候初始化,static屬性在類加載,也就是第一次用到這個類的時候初始化,對于后來的實例的創(chuàng)建,不再次進(jìn)行初始化。這個問題會在以后的系列中進(jìn)行詳細(xì)討論。
對于第二種變量,必須明確地進(jìn)行初始化。如果再沒有初始化之前就試圖使用它,編譯器會抗議。如果初始化的語句在try塊中或if塊中,也必須要讓它在第一次使用前一定能夠得到賦值。也就是說,把初始化語句放在只有if塊的條件判斷語句中編譯器也會抗議,因為執(zhí)行的時候可能不符合if后面的判斷條件,如此一來初始化語句就不會被執(zhí)行了,這就違反了局部變量使用前必須初始化的規(guī)定。但如果在else塊中也有初始化語句,就可以通過編譯,因為無論如何,總有至少一條初始化語句會被執(zhí)行,不會發(fā)生使用前未被初始化的事情。對于try-catch也是一樣,如果只有在try塊里才有初始化語句,編譯部通過。如果在catch或finally里也有,則可以通過編譯。總之,要保證局部變量在使用之前一定被初始化了。所以,一個好的做法是在聲明他們的時候就初始化他們,如果不知道要出事化成什么值好,就用上面的默認(rèn)值吧!
其實第三種變量和第二種本質(zhì)上是一樣的,都是方法中的局部變量。只不過作為參數(shù),肯定是被初始化過的,傳入的值就是初始值,所以不需要初始化。
問題六:instanceof是什么東東?
instanceof是Java的一個二元操作符,和==,>,<是同一類東東。由于它是由字母組成的,所以也是Java的保留關(guān)鍵字。它的作用是測試它左邊的對象是否是它右邊的類的實例,返回boolean類型的數(shù)據(jù)。舉個例子:
String s = "I AM an Object!";
boolean isObject = s instanceof Object;
我們聲明了一個String對象引用,指向一個String對象,然后用instancof來測試它所指向的對象是否是Object類的一個實例,顯然,這是真的,所以返回true,也就是isObject的值為True。
instanceof有一些用處。比如我們寫了一個處理賬單的系統(tǒng),其中有這樣三個類:
public class Bill {//省略細(xì)節(jié)}
public class PhoneBill extends Bill {//省略細(xì)節(jié)}
public class GasBill extends Bill {//省略細(xì)節(jié)}
在處理程序里有一個方法,接受一個Bill類型的對象,計算金額。假設(shè)兩種賬單計算方法不同,而傳入的Bill對象可能是兩種中的任何一種,所以要用instanceof來判斷:
public double calculate(Bill bill) {
if (bill instanceof PhoneBill) {
//計算電話賬單
}
if (bill instanceof GasBill) {
//計算燃?xì)赓~單
}
...
}
這樣就可以用一個方法處理兩種子類。
然而,這種做法通常被認(rèn)為是沒有好好利用面向?qū)ο笾械亩鄳B(tài)性。其實上面的功能要求用方法重載完全可以實現(xiàn),這是面向?qū)ο笞兂蓱?yīng)有的做法,避免回到結(jié)構(gòu)化編程模式。只要提供兩個名字和返回值都相同,接受參數(shù)類型不同的方法就可以了:
public double calculate(PhoneBill bill) {
//計算電話賬單
}
public double calculate(GasBill bill) {
//計算燃?xì)赓~單
}
所以,使用instanceof在絕大多數(shù)情況下并不是推薦的做法,應(yīng)當(dāng)好好利用多態(tài)。
主要就我所了解的J2EE開發(fā)的框架或開源項目做個介紹,可以根據(jù)需求選用適當(dāng)?shù)拈_源組件進(jìn)行開發(fā).主要還是以Spring為核心,也總結(jié)了一些以前web開發(fā)常用的開源工具和開源類庫
1持久層:
1)Hibernate
這個不用介紹了,用的很頻繁,用的比較多的是映射,包括繼承映射和父子表映射
對于DAO在這里介紹個在它基礎(chǔ)上開發(fā)的包bba96,目前最新版本是bba96 2.0它對Hibernate進(jìn)行了封裝, 查詢功能包括執(zhí)行hsql或者sql查詢/更新的方法,如果你要多層次邏輯的條件查詢可以自己組裝QueryObject.可以參考它做HibernateDAO.也可以直接利用它
2) iBATIS
另一個ORM工具,Apache的,沒有Hibernate那么集成,自由度比較大
2:SpringMVC
原理說明和快速入門:
配置文件為:
Spring的配置文件默認(rèn)為WEB-INF/xxxx-servelet.xm其中xxx為web.xml中org.springframework.web.servlet.DispatcherServlet的servlet-name。
Action分發(fā):
Spring將按照配置文件定義的URL,Mapping到具體Controller類,再根據(jù)URL里的action= xxx或其他參數(shù),利用反射調(diào)用Controller里對應(yīng)的Action方法。
輸入數(shù)據(jù)綁定:
Spring提供Binder 通過名字的一一對應(yīng)反射綁定Pojo,也可以直接從request.getParameter()取數(shù)據(jù)。
輸入數(shù)據(jù)驗證
Sping 提供了Validator接口當(dāng)然還可以使用開源的Commons-Validaor支持最好
Interceptor(攔截器)
Spring的攔截器提供接口需要自己編寫,在這點不如WebWork做的好.全面
(這里提一下WebWork和Struts的區(qū)別最主要的區(qū)別在于WebWork在建立一個Action時是新New一個對象而Struts是SingleMoule所有的都繼承它的一個Action,所以根據(jù)項目需要合適的選擇.)
3:View層
1) 標(biāo)簽庫:JSP2.0/JSTL
由于Webwork或Spring的標(biāo)簽確實很有限,一般view層用JSTL標(biāo)簽,而且據(jù)說JSTL設(shè)計很好速度是所有標(biāo)簽中最快的使用起來也很簡單
2) 富客戶端:DOJO Widgets, YUI(YahooUI),FCKEditor, Coolest日歷控件
Dojo主要提供Tree, Tab等富客戶端控件,可以用其進(jìn)行輔助客戶端開發(fā)
YahooUI和DOJO一樣它有自己的一套javascript調(diào)試控制臺,主要支持ajax開發(fā)也有很多Tree,Table,Menu等富客戶端控件
FCKEditor 最流行的文本編輯器
Coolest日歷控件 目前很多日歷控件可用,集成在項目中也比較簡單,這個只是其中的一個,界面不錯的說..
3) JavaScript:Prototype.js
Prototype.js作為javascript的成功的開源框架,封裝了很多好用的功能,通過它很容易編寫AJAX應(yīng)用,現(xiàn)在AJAX技術(shù)逐漸成熟,框架資源比較豐富,比如YUI,DWR等等,也是因為JavaScript沒有合適的調(diào)試工具,所以沒有必要從零開始編寫AJAX應(yīng)用,個人認(rèn)為多用一些成熟的Ajax框架實現(xiàn)無刷新更新頁面是不錯的選擇.
4)表格控件:Display Tag ,Extreme Table
這兩個的功能差不多,都是View層表格的生成,界面也比較相向,可以導(dǎo)出Excel,Pdf,對Spring支持很容易.
相比較而言比較推薦ExtremeTable,它的設(shè)計很好功能上比DisplayTag多一些,支持Ajax,封裝了一些攔截器,而且最方面的是在主頁wiki中有詳細(xì)的中文使用文檔.
5):OSCache
OSCache是OpenSymphony組織提供的一個J2EE架構(gòu)中Web應(yīng)用層的緩存技術(shù)實現(xiàn)組件,Cache是一種用于提高系統(tǒng)響應(yīng)速度、改善系統(tǒng)運行性能的技術(shù)。尤其是在Web應(yīng)用中,通過緩存頁面的輸出結(jié)果,可以很顯著的改善系統(tǒng)的穩(wěn)定性和運行性能。
它主要用在處理短時間或一定時間內(nèi)一些數(shù)據(jù)或頁面不會發(fā)生變化,或?qū)⒁恍┎蛔兊慕y(tǒng)計報表,緩沖在內(nèi)存,可以充分的減輕服務(wù)器的壓力,防治負(fù)載平衡,快速重啟服務(wù)器(通過硬盤緩存).
6)SiteMesh
sitemesh應(yīng)用Decorator模式主要用于提高頁面的可維護(hù)性和復(fù)用性,其原理是用Filter截取request和response,把頁面組件head,content,banner結(jié)合為一個完整的視圖。通常我們都是用include標(biāo)簽在每個jsp頁面中來不斷的包含各種header, stylesheet, scripts and footer,現(xiàn)在,在sitemesh的幫助下,我們刪掉他們輕松達(dá)到復(fù)合視圖模式.
Sitemesh也是 OpenSymphony的一個項目現(xiàn)在最近的版本是2.2,目前OpenSymphony自從04年就沒有更新的版本了..感覺它還是比較有創(chuàng)新的一種頁面組裝方式, OpenSymphony開源組織的代碼一般寫的比較漂亮,可以改其源代碼對自己的項目進(jìn)行適配.
測試發(fā)現(xiàn)Sitemesh還存在一些問題,比如中文問題,它的默認(rèn)編碼是iso-8859-1在使用時候需要做一些改動.
7)CSS,XHTML
這個不用說了,遵循W3C標(biāo)準(zhǔn)的web頁面開發(fā).
8)分頁標(biāo)簽: pager-taglib組件
Pager-taglib 是一套分頁標(biāo)簽庫,可以靈活地實現(xiàn)多種不同風(fēng)格的分頁導(dǎo)航頁面,并且可以很好的與服務(wù)器分頁邏輯分離.使用起來也比較簡單.
9)Form: Jodd Form taglib
Jodd Form taglib使用比較簡單,只要把<form>的頭尾以<jodd:form bean= "mybean">包住
就會自動綁定mybean, 自動綁定mybean的所有同名屬性到普通html標(biāo)記input, selectbox, checkbox,radiobox.....在這些input框里不用再寫任何代碼…
10)Ajax:DWR
J2EE應(yīng)用最常用的ajax框架
11)報表 圖表
Eclipse BIRT功能比較強大,也很龐大..好幾十M,一般沒有特別需求或別的圖表設(shè)計軟件可以解決的不用它
JasperReports+ iReport是一個基于Java的開源報表工具,它可以在Java環(huán)境下像其它IDE報表工具一樣來制作報表。JasperReports支持PDF、HTML、XLS、CSV和XML文件輸出格式。JasperReports是當(dāng)前Java開發(fā)者最常用的報表工具。
JFreeChart主要是用來制作各種各樣的圖表,這些圖表包括:餅圖、柱狀圖(普通柱狀圖以及堆棧柱狀圖)、線圖、區(qū)域圖、分布圖、混合圖、甘特圖以及一些儀表盤等等。
琴棋報表,國產(chǎn)的..重點推薦,適合中國的情況,開放源代碼,使用完全免費。純JAVA開發(fā),適用多種系統(tǒng)平臺。特別適合B/S結(jié)構(gòu)的系統(tǒng)。官方網(wǎng)站有其優(yōu)點介紹,看來用它還是不錯的選擇,最重要的是支持國產(chǎn)呵呵
4:權(quán)限控制: Acegi
Acegi是Spring Framework 下最成熟的安全系統(tǒng),它提供了強大靈活的企業(yè)級安全服務(wù),如完善的認(rèn)證和授權(quán)機制,Http資源訪問控制,Method 調(diào)用訪問控制等等,支持CAS
(耶魯大學(xué)的單點登陸技術(shù),這個單點登陸方案比較出名.我也進(jìn)行過配置使用,可以根據(jù)項目需要,如果用戶分布在不同的地方不同的系統(tǒng)通用一套登陸口令可以用它進(jìn)行解決,一般注冊機登陸機就是這樣解決的)
Acegi只是于Spring結(jié)合最好的安全框架,功能比較強大,當(dāng)然還有一些其他的安全框架,這里列舉一些比較流行的是我從網(wǎng)上找到的,使用方法看其官方文檔把…
JAAS, Seraph, jSai - Servlet Security, Gabriel, JOSSO, Kasai, jPAM, OpenSAML都是些安全控制的框架..真夠多的呵呵
5:全文檢索
1) Lucene
Lucene是一套全文索引接口,可以通過它將數(shù)據(jù)進(jìn)行倒排文件處理加入索引文件,它的索引速度和查詢速度是相當(dāng)快的,查詢百萬級數(shù)據(jù)毫秒級出結(jié)果,現(xiàn)在最火的Apache開源項目,版本更新速度很快現(xiàn)在已經(jīng)到了2.0,每個版本更新的都比較大,目前用的最多的版本應(yīng)該是1.4.3,但它有個不太方面的地方單個索引文件有2G文件限制,現(xiàn)在2.0版本沒有這個限制,我研究的比較多,它的擴(kuò)展性比較好,可以很方面的擴(kuò)充其分詞接口和查詢接口.
基于它的開發(fā)的系統(tǒng)很多,比如最常用的Eclipse的搜索功能,還有一些開源的軟件比如Compass,Nutch,Lius,還有我最近做的InSearch(企業(yè)級FTP文件網(wǎng)頁搜索)
6:公共Util類
主要是Jakarta-Commons類庫,其中最常用得是以下幾個類庫
1) Jakarta-Commons-Language
最常用得類是StringUtils類,提供了使用的字符串處理的常用方法效率比較高
2) Jakarta-Commons-Beantuils
主要用Beantuils能夠獲得反射函數(shù)封裝及對嵌套屬性,map,array型屬性的讀取。
3) Jakarta-Commons-Collections
里面有很多Utils方法
7 日志管理
Log4J
任務(wù)是日志記錄,分為Info,Warn,error幾個層次可以更好的調(diào)試程序
8 開源的J2EE框架
1) Appfuse
Appfuse是Matt Raible 開發(fā)的一個指導(dǎo)性的入門級J2EE框架, 它對如何集成流行的Spring、Hibernate、iBatis、Struts、Xdcolet、JUnit等基礎(chǔ)框架給出了示范. 在持久層,AppFuse采用了Hibernate O/R映射工具;在容器方面,它采用了Spring,用戶可以自由選擇Struts、Spring/MVC,Webwork,JSF這幾個Web框架。
2) SpringSide
.SpringSide較完整的演示了企業(yè)應(yīng)用的各個方面,是一個電子商務(wù)網(wǎng)站的應(yīng)用 SpringSide也大量參考了Appfuse中的優(yōu)秀經(jīng)驗。最重要的是它是國內(nèi)的一個開源項目,可以了解到國內(nèi)現(xiàn)在的一些實際技術(shù)動態(tài)和方向很有指導(dǎo)意義…
9:模版 Template
主要有Veloctiy和Freemarker
模板用Servlet提供的數(shù)據(jù)動態(tài)地生成 HTML。編譯器速度快,輸出接近靜態(tài)HTML 頁面的速度。
10:工作流
我所知道比較出名的主要有JBpm Shark Osworkflow,由于對它沒有過多的研究所以還不是很清楚之間有什么區(qū)別.
項目管理軟件
dotProject:是一個基于LAMP的開源項目管理軟件。最出名的項目管理軟件
JIRA: 項目計劃,任務(wù)安排,錯誤管理
Bugzilla:提交和管理bug,和eclipse集成,可以通過安裝MyEclipse配置一下即可使用
BugFree借鑒微軟公司軟件研發(fā)理念、免費開放源代碼、基于Web的精簡版Bug管理
CVS:這個就不介紹了都在用.
SVN: SubVersion已逐漸超越CVS,更適應(yīng)于JavaEE的項目。Apache用了它很久后,Sourceforge剛剛推出SVN的支持。
測試用例:主要JUnit單元測試,編寫TestCase,Spring也對Junit做了很好的支持
后記:
以Spring為主的應(yīng)用開發(fā)可選用的組件中間件真是眼花繚亂,所以針對不同的項目需求可以利用不同的開源產(chǎn)品解決,比如用Spring+Hibernate/ iBATIS或Spring+WebWork+Hibernate/ iBATIS或Spring+Struts+Hibernate/ iBATIS,合理的框架設(shè)計和代碼復(fù)用設(shè)計對項目開發(fā)效率和程序性能有很大的提高,也有利于后期的維護(hù).
現(xiàn)在主流的Java集成開發(fā)工具(IDE)Eclipse越來越流行,自從3.0版本以后Eclipse也逐漸穩(wěn)定,現(xiàn)在Eclipse開發(fā)社區(qū)的人員越來越多版本更新速度也越來越快,目前最近的版本是3.2,Eclipse相比較一些其他的IDE如NetBeans/SunOne Studio,Jbuilder,IntelliJ IDEA主要的優(yōu)點在于它是免費的、開放源代碼的、質(zhì)量很好,而且非常容易定制。Eclipse的最大的優(yōu)勢在于它的插件機制,除了很小的運行時內(nèi)核外,Eclipse的所有的東西都是插件.現(xiàn)在插件超多眼花繚亂…正確有效的使用一些插件對開發(fā)速度很有提高.Eclipse的插件開發(fā)機制也比較簡單運用Eclipse的基礎(chǔ)庫SWT,JFace,和插件開發(fā)環(huán)境PDE可以定制開發(fā)一些符合自己框架標(biāo)準(zhǔn)的代碼生成框架,避免重復(fù)性代碼的編寫,把更多的精力放在如何構(gòu)造合理的架構(gòu)提高項目開發(fā)的速度方面.
首先介紹插件的配置方法一般來講對于非安裝文件,將插件的文件夾features, plugins放在Eclipse目錄下的features,plugins下即可.如果不能使用可能是因為插件版本和當(dāng)前Eclipse版本不匹配所致,下載合適的插件版本即可.目前3.1版本比較穩(wěn)定在此平臺應(yīng)用的插件也比較多
下面主要介紹開發(fā)J2EE常用的插件的功能和簡單快速的使用方法:
1:MyEclipse
很強大的Eclipse增強插件集合,集成了很多的J2EE的功能,比如Spring,Hibernate,Struts,EJB,JSTL等插件,也支持Tomcat,Jboss,Weblogic,WebSphere等主流容器的配置,還有DatabaseExplorer數(shù)據(jù)庫管理插件,也集成了主流的bug管理系統(tǒng)bugzilla的錯誤提交,也提供了UML圖的繪制插件,是一個比較全面的插件集合,官方更新速度很快,現(xiàn)在最新的版本是MyEclipse 5.0 GA支持Eclipse3.2不過是收費的,可以在網(wǎng)上找到破解碼:-)
對于Tomcat開發(fā)為主流的常用的功能它的Reload機制,在window->Preferences->MyEclipse下Application Servers里Tomcat設(shè)置TomcatHome,然后通過快捷工具欄中的Run/Stop/Restart MyEclipse Application Servers啟動服務(wù),這樣你的項目修改Java類不需要在重啟Tomcat就可以實現(xiàn)改過的功能.
如果初時工程設(shè)為Web Projects可以通過Myeclipse為其添加Spring,Struts,Jsf,Jstl,Hibernate的庫,設(shè)置方法為右鍵你的工程然后在菜單中選擇Myeclipse在彈出菜單中Add相應(yīng)的Capabilities.也可以選擇為工程添加J2EE容器,如果上一步配置了Myeclipse Application Servers可以Add Tomcat Server,然后它會自動部署到Tomcat的webapps下.
DatabaseExplorer配置比較簡單也比較好用,配置方法為:New一個Driver選擇相應(yīng)SqlServer2000,或Oracle的驅(qū)動jar,按照提示配置好數(shù)據(jù)庫連接字符串就可以操作數(shù)據(jù)庫了,也可以執(zhí)行sql查詢.
Myeclipse為開發(fā)便利為Eclipse開發(fā)了它的幾個視圖,可以通過菜單window->Open Perspective選擇適當(dāng)?shù)囊晥D,如MyEclipse Database Explorer,MyEclipse J2EE Development,MyEclipseHibernate和MyEclipse Web2.0
MyEclipse的缺點就在于對系統(tǒng)要求太高,開文件過多會死掉有時,所以一般1G內(nèi)存跑起來比較爽,可以通過-Xmx屬性對Eclipse使用的內(nèi)存進(jìn)行擴(kuò)充.
對于UML方面說一下一般MyEclipse這個功能是個花瓶中看不中用,小的功能比較簡單的UML圖還可以夠用,對于UML的正向或者逆向工程不支持,所以一般不用它.建議使用”Eclipse UML”插件
2. Lomboz
Lomboz也是一個功能強大的基于J2EE的集成插件其功能主要是JSP高亮顯示,語法檢查和編碼助手,支持部署J2EE Web應(yīng)用程序,利用Wizard創(chuàng)建Web應(yīng)用和常用的代碼生成,支持JSP的調(diào)試.
Lomboz的配置很簡單將其放在Eclipse相應(yīng)的文件夾即可.
Lomboz的優(yōu)勢在于可以調(diào)試Jsp, Lomboz的調(diào)試原理在于對要調(diào)試的jsp頁面所產(chǎn)生的java代碼進(jìn)行調(diào)試,和java工程的調(diào)試過程一樣,調(diào)試方法是打開Lomboz J2EE View 選擇服務(wù)器,單擊右鍵debug server,打開jsp所生成的java文件設(shè)置斷點,在IE打開jsp就可以激活調(diào)試進(jìn)行jsp調(diào)試,其實我感覺最好的調(diào)試方法是System.out.println,比較快捷.
3.SWT-Designer
看名字就知道是開發(fā)Java圖形用戶界面的插件,可以用于開發(fā)PDE插件或基于SWT的應(yīng)用程序,非常強大的開發(fā)工具收費的不過比VE穩(wěn)定很多,可以畫界面,使用方法比較簡單
在官方下載找個注冊碼激活就可以.
4.JSEclipse
這個對于WEB開發(fā)很有用可以對javascript進(jìn)行高亮顯示,語法檢查和編碼助手,特別是在Myeclipse或Lomboz下js開發(fā)時有時候沒有編碼助手,錯誤也沒有提示,很不方面,JSEclipse可以進(jìn)行編碼提示和錯誤提示很實用!對于以后的ajax編碼和富客戶端開發(fā)調(diào)試效率會有很大的提高!
5.Properties Editor
編輯java的屬性文件,并可以自動存盤為Unicode格式
6.XMLBuddy
XMLBuddy 主要用于編輯XML文件
7.Log4E
Log4E Log4j插件,提供各種和Log4j相關(guān)的任務(wù),如為方法、類添加一個logger等,主要優(yōu)點在于代碼生成免去了每個類都要logger一下的麻煩
.使用方法比較簡單..選中某個.java文件右鍵選擇Log4J.
8.FreeMarker Eclipse Plugin / FreeMarker IDE
FreeMarker沒有語法高亮看起來確實很不爽…調(diào)試起來比較痛苦這個插件用來在Eclipse中支持FreeMarker模板語言.它包括語法高亮顯示,語法錯誤提示、視圖等.
9.Veloedit
Velocity模版開發(fā)插件與FreeMarker類似
以上幾個都是最常用的J2EE的插件,我都測試過很方便,在網(wǎng)上都有新版本下載,如果你的內(nèi)存比較大可以用MyEclipse作為主要開發(fā)工具,輔助其他幾個實用的插件,如果你機子配置不是很高.采用Lomboz加上其他幾個插件也可.當(dāng)然還有很多實用的插件這里沒有介紹比如Profiler(性能跟蹤、測量工具,能跟蹤、測量BS程序) VSS Plugin for Eclipse (Microsoft Visual SourceSafe (VSS)),大家可以發(fā)掘介紹…
常用Eclipse快捷鍵介紹
主要總結(jié)了最最常用的Eclipse快捷鍵的功能
F3: 打開申明(Open declaration)。
Control-Shift-G: 在workspace中搜索引用(reference)。這個熱鍵的作用和F3恰好相反.
Control-Shift-F: 根據(jù)代碼風(fēng)格設(shè)定重新格式化代碼.
Control-Shift-O: 快速引入要import的類說明.
Control-O: 快速概要。通過這個快捷鍵,你可以迅速的跳到一個方法或者屬性.
Control-/: 對一行注釋或取消注釋。對于多行也同樣適用。
Spirng的InitializingBean為bean提供了定義初始化方法的方式。InitializingBean是一個接口,它僅僅包含一個方法:afterPropertiesSet()。

Bean實現(xiàn)這個接口,在afterPropertiesSet()中編寫初始化代碼:
package research.spring.beanfactory.ch4;
import org.springframework.beans.factory.InitializingBean;
public class LifeCycleBean implements InitializingBean{
public void afterPropertiesSet() throws Exception {
System.out.println("LifeCycleBean initializing...");
}
}
在xml配置文件中并不需要對bean進(jìn)行特殊的配置:
xml version="1.0" encoding="UTF-8"?>
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="lifeBean" class="research.spring.beanfactory.ch4.LifeCycleBean">
bean>
beans>
編寫測試程序進(jìn)行測試:
package research.spring.beanfactory.ch4;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class LifeCycleTest {
public static void main(String[] args) {
XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch4/context.xml"));
factory.getBean("lifeBean");
}
}
運行上面的程序我們會看到:“LifeCycleBean initializing...”,這說明bean的afterPropertiesSet已經(jīng)被Spring調(diào)用了。
Spring在設(shè)置完一個bean所有的合作者后,會檢查bean是否實現(xiàn)了InitializingBean接口,如果實現(xiàn)就調(diào)用bean的afterPropertiesSet方法。
SHAPE \* MERGEFORMAT
查看bean是否實現(xiàn)InitializingBean接口
|
調(diào)用afterPropertiesSet方法
|
Spring雖然可以通過InitializingBean完成一個bean初始化后對這個bean的回調(diào),但是這種方式要求bean實現(xiàn) InitializingBean接口。一但bean實現(xiàn)了InitializingBean接口,那么這個bean的代碼就和Spring耦合到一起了。通常情況下我不鼓勵bean直接實現(xiàn)InitializingBean,可以使用Spring提供的init-method的功能來執(zhí)行一個bean 子定義的初始化方法。
寫一個java class,這個類不實現(xiàn)任何Spring的接口。定義一個沒有參數(shù)的方法init()。
package research.spring.beanfactory.ch4;
public class LifeCycleBean{
public void init(){
System.out.println("LifeCycleBean.init...");
}
}
在Spring中配置這個bean:
xml version="1.0" encoding="UTF-8"?>
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="lifeBean" class="research.spring.beanfactory.ch4.LifeCycleBean"
init-method="init">
bean>
beans>
當(dāng)Spring實例化lifeBean時,你會在控制臺上看到” LifeCycleBean.init...”。
Spring要求init-method是一個無參數(shù)的方法,如果init-method指定的方法中有參數(shù),那么Spring將會拋出java.lang.NoSuchMethodException
init-method指定的方法可以是public、protected以及private的,并且方法也可以是final的。
init-method指定的方法可以是聲明為拋出異常的,就像這樣:
final protected void init() throws Exception{
System.out.println("init method...");
if(true) throw new Exception("init exception");
}
如果在init-method方法中拋出了異常,那么Spring將中止這個Bean的后續(xù)處理,并且拋出一個org.springframework.beans.factory.BeanCreationException異常。
InitializingBean和init-method可以一起使用,Spring會先處理InitializingBean再處理init-method。
org.springframework.beans.factory.support. AbstractAutowireCapableBeanFactory完成一個Bean初始化方法的調(diào)用工作。 AbstractAutowireCapableBeanFactory是XmlBeanFactory的超類,再 AbstractAutowireCapableBeanFactory的invokeInitMethods方法中實現(xiàn)調(diào)用一個Bean初始化方法:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.java:
//……
//在一個bean的合作者設(shè)備完成后,執(zhí)行一個bean的初始化方法。
protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mergedBeanDefinition)
throws Throwable {
//判斷bean是否實現(xiàn)了InitializingBean接口
if (bean instanceof InitializingBean) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
//調(diào)用afterPropertiesSet方法
((InitializingBean) bean).afterPropertiesSet();
}
//判斷bean是否定義了init-method
if(mergedBeanDefinition!=null&&mergedBeanDefinition.getInitMethodName() != null) {
//調(diào)用invokeCustomInitMethod方法來執(zhí)行init-method定義的方法
invokeCustomInitMethod(beanName, bean, mergedBeanDefinition.getInitMethodName());
}
}
//執(zhí)行一個bean定義的init-method方法
protected void invokeCustomInitMethod(String beanName, Object bean, String initMethodName)
throws Throwable {
if (logger.isDebugEnabled()) {
logger.debug("Invoking custom init method '" + initMethodName +
"' on bean with name '" + beanName + "'");
}
//使用方法名,反射Method對象
Method initMethod = BeanUtils.findMethod(bean.getClass(), initMethodName, null);
if (initMethod == null) {
throw new NoSuchMethodException(
"Couldn't find an init method named '" + initMethodName + "' on bean with name '" + beanName + "'");
}
//判斷方法是否是public
if (!Modifier.isPublic(initMethod.getModifiers())) {
//設(shè)置accessible為true,可以訪問private方法。
initMethod.setAccessible(true);
}
try {
//反射執(zhí)行這個方法
initMethod.invoke(bean, (Object[]) null);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
//………..
通過分析上面的源代碼我們可以看到,init-method是通過反射執(zhí)行的,而afterPropertiesSet是直接執(zhí)行的。所以 afterPropertiesSet的執(zhí)行效率比init-method要高,不過init-method消除了bean對Spring依賴。在實際使用時我推薦使用init-method。
需要注意的是Spring總是先處理bean定義的InitializingBean,然后才處理init-method。如果在Spirng處理InitializingBean時出錯,那么Spring將直接拋出異常,不會再繼續(xù)處理init-method。
如果一個bean被定義為非單例的,那么afterPropertiesSet和init-method在bean的每一個實例被創(chuàng)建時都會執(zhí)行。單例 bean的afterPropertiesSet和init-method只在bean第一次被實例時調(diào)用一次。大多數(shù)情況下 afterPropertiesSet和init-method都應(yīng)用在單例的bean上。
Spring BeanFactory提供了類似pico container中自動裝配組件依賴的對象的功能。自動裝配能應(yīng)用在每個組件上,可以為一些組件定義自動裝配,而另一些組件則不使用。
使用”autowire”屬性可以設(shè)置自動裝配,autowire有五種模式:
默認(rèn)屬性,不進(jìn)行自動裝配。
通過bean的屬性名稱自動裝配合作者。
SHAPE \* MERGEFORMAT
Spring用bean 中set方法名和BeanFactory中定義的合作者的名稱做匹配,一但2者匹配,Sping就會把合作者進(jìn)行注入。
可以使用id屬性也可以使用name屬性定義合作者的名稱,這2個屬性在Spring進(jìn)行自動裝配時沒有區(qū)別。
當(dāng)有多個名稱相同的合作者在Spring中定義時,Srping在自動裝配時選擇最后一個定義的合作者注入。
SHAPE \* MERGEFORMAT
在多個合作者名稱相同進(jìn)行自動裝配時,合作者的id屬性并不會比name屬性優(yōu)先處理。無論怎樣定義Spring總會把最后一個定義的合作者注入。
通過bean set方法中參數(shù)的類型和BeanFactory中定義合作者的類型做匹配,Spring會找到匹配的合作者進(jìn)行注入。
SHAPE \* MERGEFORMAT
在byType自動裝配模式中,Spring不關(guān)心合作者的名稱,只關(guān)心合作者的類型是否滿足條件。
類似上面介紹的byName的方式,在byType方式中,當(dāng)具有相同名稱并且有相同類型的多個合作者被找到時,Spring會注入最后一個定義的合作者。
SHAPE \* MERGEFORMAT
在byType裝配時,如果有2個不同名稱但是類型相同的合作者被找到,那么Spring會拋出一個依賴異常。
SHAPE \* MERGEFORMAT
拋出依賴異常,通知用戶在byType方式中同樣類型的Bean只能定義一個。
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dao' defined in class path resource [research/spring/beanfactory/ch3/context.xml]: Unsatisfied dependency expressed through bean property 'database': There are 2 beans of type [class research.spring.beanfactory.ch3.Database] for autowire by type. There should have been 1 to be able to autowire property 'database' of bean 'dao'...
constructor
constructor其實時按byType的方式進(jìn)行構(gòu)造函數(shù)的注入。
SHAPE \* MERGEFORMAT
constructor裝配方式不關(guān)心構(gòu)造參數(shù)的順序,無論構(gòu)造函數(shù)參數(shù)的順序如何Spring都會按類型匹配到正確的合作者進(jìn)行注入。
在byType方式中,當(dāng)沒有找到類型相同的合作者時Spring什么都不會去做。但是在constructor方式中,當(dāng)沒有找到和Bean構(gòu)造函數(shù)中參數(shù)類型相匹配的合作者時,Spring會拋出異常。
Spring在進(jìn)行constructor方式的自動裝配時,強制要求所有的構(gòu)造函數(shù)中所有的合作者都必須存在。
在autodetect的方式中,Spring檢查一個Bean內(nèi)部是否有默認(rèn)的構(gòu)造函數(shù)。如果有默認(rèn)的參數(shù)Spring就使用byType的方式進(jìn)行自動裝配。如果沒有默認(rèn)的構(gòu)造函數(shù)Spring則使用constructor的方式進(jìn)行自動裝配。
如果一個Bean同時定義了默認(rèn)構(gòu)造函數(shù)和帶參數(shù)的構(gòu)造函數(shù),Spring仍會使用byType的方式進(jìn)行裝配。
不管使用上述哪種裝配方式,都可以在Bean中顯示的定義合作者。顯示定義的依賴關(guān)系優(yōu)先級比自動裝配高。
自動裝配的功能可以和自動依賴檢查一起使用。Spring會首先進(jìn)行自動裝配,然后在進(jìn)行依賴檢查。
自動裝配提供了簡化配置的可能性,但是我并不建議在項目中大量的使用自動裝配,特別時byType方式。因為自動裝配,尤其時byType方式,破壞了Bean和合作者之間顯示的依賴關(guān)系,所有的依賴關(guān)系都時不明顯的。在使用自動裝配后我們的依賴關(guān)系需要到源代碼中才能看到,這使得維護(hù)或文檔化Bean的依賴關(guān)系變得很困難。
適當(dāng)?shù)氖褂米詣友b配比如byName方式的裝配,是有一些好處的。比如我們在一些特定的范圍里可以借助byName自動裝配的功能來實現(xiàn)“
以慣例來代替配置”的框架。
自動依賴檢查可以保證所有java bean中的屬性(set方法)都在Spring中正確的配置。如果在一個java bean中定義了一個name屬性,并且也setName方法。那么在開啟自動依賴檢查功能后,就必須在Spring中定義這個屬性,否則Spring將拋出異常。
請看下面的例子:
Dao.java
包含一個setName方法。
package research.spring.beanfactory.ch3;
public class Dao {
private String name;
public void setName(String name) {
this.name = name;
}
}
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="dao" class="research.spring.beanfactory.ch3.Dao"> </bean>
<bean id="database" class="research.spring.beanfactory.ch3.Database">
</bean>
</beans>
我們在context.xml沒有定義Dao的name屬性。上面的配置,Spring可以正常的實例化Dao對象。
下面我們修改context.xml:
我們通過dependency-check=all,在Dao上增加了自動依賴檢查的功能。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="dao" class="research.spring.beanfactory.ch3.Dao" dependency-check="all" > </bean>
<bean id="database" class="research.spring.beanfactory.ch3.Database">
</bean>
</beans>
當(dāng)配置依賴檢查時,Spring實例化Dao時會拋出一個異常:

Spring定義了4種依賴檢查的策略:
不進(jìn)行依賴檢查。
只對簡單屬性和集合中的簡單屬性進(jìn)行檢查。不對依賴的對象檢查。
只對為對象類型的屬性進(jìn)行檢查。
對所有類型進(jìn)行檢查。
如果把上面例子里的context.xml改成這樣:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="dao" class="research.spring.beanfactory.ch3.Dao" dependency-check="objects" > </bean>
<bean id="database" class="research.spring.beanfactory.ch3.Database">
</bean>
</beans>
Spring將不會拋出異常,因為objects只對依賴的對象進(jìn)行檢查。
dependency-check在Spring中又以下的限制:
- 不能對構(gòu)造函數(shù)中的參數(shù)進(jìn)行檢查。
- 即使屬性中有默認(rèn)值,只要包含了set方法,那么dependency-check仍然需要檢查Spring中是否配置了這個屬性。
package research.spring.beanfactory.ch3;
public class Dao {
private Database database;
private String name="chenjie";
//dependency-check仍然會檢查這個屬性是否配置注入
public void setName(String name) {
this.name = name;
}
public void setDatabase(Database database) {
this.database = database;
}
}
即使Dao設(shè)置里name得默認(rèn)值,但是只要有setName方法,dependency-check仍然會判斷是否在配置文件中設(shè)置了setName對應(yīng)的注入。
depend-on用來表示一個Bean的實例化依靠另一個Bean先實例化。如果在一個bean A上定義了depend-on B那么就表示:A 實例化前先實例化 B。
這種情況下,A可能根本不需要持有一個B對象。
比如說,你的DAO Bean實例化之前你必須要先實例化Database Bean,DAO Bean并不需要持有一個Database Bean的實例。因為DAO的使用是依賴Database啟動的,如果Database Bean不啟動,那么DAO即使實例化也是不可用的。這種情況DAO對Database的依賴是不直接的。
除了在DAO上使用構(gòu)造函數(shù)注入Database Bean以外,Spring沒有任何依賴注入的關(guān)系能夠滿足上面的情況。但是DAO也許根本不需要Database的實例被注入,因為DAO是通過JDBC訪問數(shù)據(jù)庫的,它不需要調(diào)用Database 上的任何方法和屬性。
在這種情況下你可以使用depends-on來定義在DAO被實例化之前先去實例化Database。你可這樣定義:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="dao" class="research.spring.beanfactory.ch3.Dao" depends-on="database">
</bean>
<bean id="database" class="research.spring.beanfactory.ch3.Database">
</bean>
</beans>
通過定義depends-on=”database”可以控制Sping實例化dao的順序。在任何時候Spring總會保證實例化DAO之前先實例Database。
通常depends-on常常應(yīng)用在上面的場景中。如果DAO depend-on Database的同時需要得到Database的實例,那么使用構(gòu)造函數(shù)注入是一個比較好的解決辦法。因為構(gòu)造函數(shù)注入的方式是要先實例化目標(biāo)對象依賴的對象然后在實例化目標(biāo)對象。關(guān)于構(gòu)造函數(shù)的輸入請參考另一篇文章
《Spring內(nèi)核研究-set方法注入和構(gòu)造函數(shù)注入》
DAO depend-on Database時,也可以在DAO上定義setDatabase方法來接收一個Database的實例。這樣Sping會保證DAO創(chuàng)建前先創(chuàng)建Database實例,然后在把實例化DAO后調(diào)用DAO的setDatabase方法把剛才創(chuàng)建的Database的實例注入給DAO。前提條件時Database必須定義成單例的。否則Spring在DAO depend-on Database時會創(chuàng)建一個Database的實例,在DAO.setDatabase時又會創(chuàng)建Database另外的一個實例。這種情況可能不是你想要的,而且很可能會造成比較隱蔽的錯誤。
使用set方法注入depend-on的對象:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="dao" class="research.spring.beanfactory.ch3.Dao" depends-on="database ">
<property name="database">
<ref bean="database"></ref>
</property>
</bean>
<bean id="database" class="research.spring.beanfactory.ch3.Database">
</bean>
</beans>
一般在depends-on一個對象并且又需要這個對象實例的情況下,我都建議你使用構(gòu)造函數(shù)的注入方式替換depend-on。只有不能構(gòu)造函數(shù)中添加依賴對象參數(shù)的情況下才使用上面例子里的方式。
可以同時使用depends-on和構(gòu)造函數(shù)注入,如A depends-on B 并且 new A(B b)。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="dao" class="research.spring.beanfactory.ch3.Dao" depends-on="database">
<constructor-arg>
<ref bean="database"></ref>
</constructor-arg>
</bean>
<bean id="database" class="research.spring.beanfactory.ch3.Database">
</bean>
</beans>
然而這種做法是不合適的,因為在構(gòu)在函數(shù)中注入依賴對象的方式可以包含depends-on的情況。也就時說new A(B b)包含了A depends-on B的所有情況。既然已經(jīng)定義了new A(B b)就沒有必要在定義A depends-on B。所以,new A(B b)可以替代A depends-on B。在A創(chuàng)建前必須創(chuàng)建B,而且A不需要使用B實例的情況下只能使用A depends-on B。
Spring允許Bean和Bean依賴的Bean(合作者)上同時定義depends-on。比如A depends-on B && B depends-on C && C depends-on D。下面這樣定義是合法的。Sping實例化他們的順序是D->C->B->A。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="a" class="research.spring.beanfactory.ch3.A" depends-on="b" />
<bean name="b" class="research.spring.beanfactory.ch3.B" depends-on="c" />
<bean name="c" class="research.spring.beanfactory.ch3.C" depends-on="D" />
<bean name="d" class="research.spring.beanfactory.ch3.D" />
</beans>
但是Spring不允許A depends-on B && B depends-on A的情況。看下面的例子,由于D又依賴回A,這種在依賴關(guān)系中形成了一個閉環(huán),Spring將無法處理這種依賴關(guān)系。所以下面的這種定義是不合法的。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="a" class="research.spring.beanfactory.ch3.A" depends-on="b" />
<bean name="b" class="research.spring.beanfactory.ch3.B" depends-on="c" />
<bean name="c" class="research.spring.beanfactory.ch3.C" depends-on="D" />
<bean name="d" class="research.spring.beanfactory.ch3.D" depends-on="A"/>
</beans>
一個Bean可以同時depends-on多個對象如,A depends-on D,C,B。可以使用“,”或“;”定義多個depends-on的對象。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="a" class="research.spring.beanfactory.ch3.A" depends-on="d,c,b" />
<bean name="b" class="research.spring.beanfactory.ch3.B" />
<bean name="c" class="research.spring.beanfactory.ch3.C" />
<bean name="d" class="research.spring.beanfactory.ch3.D" />
</beans>
上面的例子中A的實例化需要先實例化D,C,B。Spring會按照depend-on中定義的順序來處理Bean。在這個例子里Spring實例化對象的順利是D->C->B->A。雖然實例化對象的順序和前面“A depends-on B && B depends-on C && C depends-on D”的情況一下,但是這里的意義是完全不同的。不能用“A depends-on D,C,B”代替“A depends-on B && B depends-on C && C depends-on D”。
depends-on是一個非常又用的功能,借助depends-on我們可以管理那些依賴關(guān)系不明顯或者沒有直接依賴關(guān)系的對象。
Spring專門設(shè)計了對工廠模式支持,你可以使用靜態(tài)工廠方法來創(chuàng)建一個Bean,也可以使用實例工廠的方法來創(chuàng)建Bean。下面分別介紹這2種方法。
定義一個Bean使用自己類上的靜態(tài)工廠方法來創(chuàng)建自己。
context.xml
factory-menthod定義了userDao Bean使用UserDao類的getInstance方法來創(chuàng)建自己的實例。userManager仍然通過lookup方法獲得userDao。Lookup方法并不關(guān)心一個Bean的實例時怎樣創(chuàng)建的,所以可以混合使用lookup方法和factory-menthod方法。
xml version="1.0" encoding="UTF-8"?>
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="userManager"
class="research.spring.beanfactory.ch2.UserManager">
<lookup-method name="getUserDao" bean="userDao" />
bean>
<bean name="userDao" class="research.spring.beanfactory.ch2.UserDao"
factory-method="getInstance" / >
beans>
UserDao.java
增加一個getInstance方法來創(chuàng)建自己的實例。
package research.spring.beanfactory.ch2;
public class UserDao {
public static UserDao getInstance() {
return new UserDao("static factory method");
}
private String name = "";
public UserDao(String name) {
this.name = name;
}
public void create() {
System.out.println("create user from - " + name);
}
}
Test.java
package research.spring.beanfactory.ch2;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class Test {
public static void main(String[] args) {
XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch2/context.xml"));
UserManager manager=(UserManager) factory.getBean("userManager");
manager.createUser();
}
}
運行Test.java,你會看到:
create user from - static factory method
這說明userDao使用它自己得靜態(tài)工廠創(chuàng)建得。
靜態(tài)工廠方法存在一些限制:
- 靜態(tài)工廠方法上不能有參數(shù),也不能在Spring種定義靜態(tài)工廠方法的參數(shù)。
- 靜態(tài)工廠方法只能是public的,不能是private或protected的。
- 靜態(tài)工廠方法不能和構(gòu)造函數(shù)注入一起使用。下面的定義時不能正常工作的:
package research.spring.beanfactory.ch2;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class Test {
public static void main(String[] args) {
XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch2/context.xml"));
UserManager manager=(UserManager) factory.getBean("userManager");
manager.createUser();
}
}
定義一個Bean使用這個Bean的工廠對象上的工廠方法來創(chuàng)建自己。
我們定義一個UserDao的Factory來創(chuàng)建UserDao。
UserDaoFactory.java
package research.spring.beanfactory.ch2;
public class UserDaoFactory{
public UserDao getUserDao(){
return new UserDao("UserDaoFactory");
}
}
修改context.xml:
xml version="1.0" encoding="UTF-8"?>
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="userManager"
class="research.spring.beanfactory.ch2.UserManager">
<lookup-method name="getUserDao" bean="userDao" />
bean>
<bean name="userDao" class="research.spring.beanfactory.ch2.UserDao"
factory-bean="userDaoFactory" factory-method="getUserDao" >
bean>
<bean name="userDaoFactory" class="research.spring.beanfactory.ch2.UserDaoFactory">
bean>
beans>
再次運行Test.java你會看到:
create user from – UserDaoFactory
通過上面的配置Spring已經(jīng)使用userDaoFactory實例的工廠方法來創(chuàng)建userDao了。
- factory-bean定義了工廠Bean
- factory-method定義了工廠方法
實例工廠和靜態(tài)工廠一樣都存在相同的限制:
- 靜態(tài)工廠方法上不能有參數(shù),也不能在Spring種定義靜態(tài)工廠方法的參數(shù)。
- 靜態(tài)工廠方法只能是public的,不能是private或protected的。
- 靜態(tài)工廠方法不能和構(gòu)造函數(shù)注入一起使用。
和靜態(tài)工廠不同的是:
- 實例工廠方法不能是靜態(tài)的,而靜態(tài)工廠方法必須是靜態(tài)的。
通過上面的例子我們看到Spring對工廠模式對了完整的支持。但是這里還是需要說明,如果使用IoC模式設(shè)計的系統(tǒng)一般情況下不需要為任何Bean做工廠類。在我的觀點里,工廠模式僅僅是遺留系統(tǒng),使用依賴注入模式可以取代工廠模式。Spring對工廠的支持僅僅是為了可以很好的集成遺留系統(tǒng)。
“Lookup方法”可以使Spring替換一個bean原有的,獲取其它對象具體的方法,并自動返回在容器中的查找結(jié)果。
我們來看這個例子:
UserDao.java
在UserDao的構(gòu)造函數(shù)中接受一個name參數(shù),創(chuàng)建UserDao的對象會把自己的名字傳遞給userDao,這樣userDao的create方法中就會把userDao的創(chuàng)建者打印出來。
package research.spring.beanfactory.ch2;
public class UserDao {
private String name="";
public UserDao(String name){
this.name=name;
}
public void create(){
System.out.println("create user from - "+name);
}
}
UserManager.java
在這段代碼中UserManager依靠getUserDao方法來獲取UserDao對象。由于在getUserDao方法里顯示的聲明了如何去實例一個UserDao,所以上面的代碼不符合IoC模式的風(fēng)格。雖然使用GetUserDao封裝了UserDao的創(chuàng)建過程,但是UserManager和UserDao的關(guān)系仍然非常緊密。
package research.spring.beanfactory.ch2;
public class UserManager {
public UserDao getUserDao() {
return new UserDao("UserManager.getUserDao()");
}
public void createUser() {
UserDao dao = getUserDao(); //通過getUserDao獲得userDao
dao.create();
}
}
LookupMethodTest.java
通過BeanFactory獲得UserManager,并調(diào)用createUser方法。
package research.spring.beanfactory.ch2;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class LookupMethodTest {
public static void main(String[] args) {
XmlBeanFactory factory=new
XmlBeanFactory(new ClassPathResource("research/spring/beanfactory/ch2/context.xml"));
UserManager manager=(UserManager) factory.getBean("userManager");
manager.createUser(); //create a User
}
}
context.xml
xml version="1.0" encoding="UTF-8"?>
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="userManager"
class="research.spring.beanfactory.ch2.UserManager">
bean>
<bean name="userDao class="research.spring.beanfactory.ch2.UserDao" >
bean>
beans>
運行LookupMethodTest你會看到屏幕輸入” create user from - UserManager.getUserDao()”。
由于是遺留系統(tǒng),所以我們不能修改UserManager。現(xiàn)在我希望讓這個UserManager依賴的Dao對象由spring管理,而不修改原有的代碼。
在這個場景中我們就可以利用Spring提供的“Lookup方法”來替換原有的getUserDao方法,實現(xiàn)自動獲取userDao的功能。修改context.xml:
xml version="1.0" encoding="UTF-8"?>
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="userManager"
class="research.spring.beanfactory.ch2.UserManager">
<lookup-method name="getUserDao" bean="userDao" />
bean>
<bean name="userDao" class="research.spring.beanfactory.ch2.UserDao" >
<constructor-arg>
<value>lookup methodvalue>
constructor-arg>
bean>
<bean name="userDaoFactory" class="research.spring.beanfactory.ch2.UserDaoFactory">
bean>
beans>
再次運行LookupMethodTest你會看到不同的輸出結(jié)果“create user from - lookup method”。字符串“lookup method”是通過構(gòu)造函數(shù)注入給userDao的。原來的userManager.java并沒有作任何修改,仍然是通過UserDao dao = getUserDao();來獲得userDao的。這說明Spring已經(jīng)替換了原有的getUserDao方法的實現(xiàn),當(dāng)執(zhí)行g(shù)etUserDao時Spring會在容器中尋找指定的Bean,并返回這個Bean。
通過這種機制我們可以在不修改原系統(tǒng)代碼的情況下,可以輕易的把UserDao換成別的類型相容的對象而不會影響原系統(tǒng)。Spring是使用CGLIB在字節(jié)碼級別動態(tài)實現(xiàn)出userManager的子類,并重寫getUserDao方法的方式來實現(xiàn)這個神奇的功能的。
修改LookupMethodTest.java:
package research.spring.beanfactory.ch2;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class LookupMethodTest {
public static void main(String[] args) {
XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch2/context.xml"));
UserManager manager=(UserManager) factory.getBean("userManager");
System.out.println(manager.toString()); //打印userManager的信息
manager.createUser(); //create a User
}
}
我們在獲取UserManager的實例后打印出這個實例的信息,再次運行LookupMethodTest你會看到:

注意manager.toString()打印出了:
這個是CGLIG動態(tài)生成的類,而不是原來的UserManager的實例。所以請記住在任何時候只要定義了一個Bean的Lookup方法,那么這個Bean的實例將是一個CGLIB動態(tài)生成的實例而不是原來類的實例。
Spring允許在一個Bean中定義多個Lookup方法。
<bean name="userManager"
class="research.spring.beanfactory.ch2.UserManager">
<lookup-method name="getUserDao" bean="userDao" />
<lookup-method name="getOtherDao" bean="otherDao" />
bean>
上面的做法是合法的,并且可以正常工作。
雖然Spring會用CGLIB動態(tài)生成一個帶有Lookup方法bean的子類,但是這并不影響Spring完成其它的功能。Sping還是允許Lookup方法和setXXX、構(gòu)造函數(shù)注入以及后面我們將介紹的自動依賴檢查和自動裝配的功能同時使用。
Spring還允許Lookup方法中定義的方法帶有參數(shù),但是Sping不會處理這些參數(shù)。
修改UserManager:
package research.spring.beanfactory.ch2;
public class UserManager {
private UserDao dao;
public void setDao(UserDao dao) {
this.dao = dao;
}
public UserDao getUserDao(String daoName) {
return new UserDao("UserManager.getUserDao()");
}
public void createUser() {
UserDao dao = getUserDao(“userDao”); //通過getUserDao獲得userDao
dao.create();
}
}
雖然方法上由參數(shù),但是上面的代碼可以正常工作。Spring不會處理這些參數(shù)。
Spring對Lookup方法也存在一些限制:
- 方法不能是private的,但可以是protected的。
- 方法不能是靜態(tài)的。
有一個比較有趣的用法,就是在抽象類上定義Lookup方法。你一定記得經(jīng)典的工廠模式吧。定義一個抽象工廠,然后為每一類具體產(chǎn)品實現(xiàn)一個具體產(chǎn)品的工廠。
一個抽象工廠:
package research.spring.beanfactory.ch2;
public abstract class Factory {
public abstract UserDao getProduct();
}
具體一類產(chǎn)品的工廠:
package research.spring.beanfactory.ch2;
public class UserDaoFactory extends Factory{
public UserDao getProduct(){
return new UserDao("UserDaoFactory");
}
}
用戶可以通過:
new UserDaoFactory().getProduce();
來獲取具體的UserDao產(chǎn)品。
但是如果有很多產(chǎn)品就需要做出實現(xiàn)出很多工廠如,DocumentDaoFactory、GroupDaoFactory等等,這樣系統(tǒng)中會出現(xiàn)大量的工廠。工廠的泛濫并不能說明系統(tǒng)的設(shè)計是合理的。
既然Spring可以在抽象類上使用Lookup方法,那么我們就可以不同實現(xiàn)真的去實現(xiàn)那么多的子類了。我們可以在抽象類上直接定義Lookup方法和目標(biāo)對象。用戶直接通過抽象類來獲得需要的產(chǎn)品對象。看下面這個例子:
Factory.ava
package research.spring.beanfactory.ch2;
public abstract class Factory {
public abstract Object getProduct();
}
context.xml
如果指定userDaoFactory的類為一個抽象類,并且再這個bean里定義了Lookup方法,那么Spring會自動生成這個抽象類的子類實現(xiàn)。
xml version="1.0" encoding="UTF-8"?>
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean name="userManager"
class="research.spring.beanfactory.ch2.UserManager">
<lookup-method name="getUserDao" bean="userDao" />
bean>
<bean name="userDao" class="research.spring.beanfactory.ch2.UserDao" >
<constructor-arg>
<value>lookup methodvalue>
constructor-arg>
bean>
<bean name="userDaoFactory" class="research.spring.beanfactory.ch2.Factory" singleton="false">
<lookup-method name="getProduct" bean="userDao" />
bean>
beans>
Test.java
package research.spring.beanfactory.ch2;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class LookupMethodTest {
public static void main(String[] args) {
XmlBeanFactory factory=new XmlBeanFactory(new ClassPathResource(
"research/spring/beanfactory/ch2/context.xml"));
//獲得抽象工廠
Factory abstractFactory=(Factory) factory.getBean("userDaoFactory");
UserDao userDao=(UserDao) abstractFactory.getProduct();
System.out.println(userDao.toString());
userDao.create();
}
}
運行Test你會看到:
research.spring.beanfactory.ch2.UserManager$$EnhancerByCGLIB$$cc2f8f1c@12c7568
create user from - lookup method
對,這個結(jié)果和上面的例子是完全一樣的。UserDao并沒有改變,我們通過抽象的Factory獲得了具體的UserDao的實例。這樣即使系統(tǒng)中很多的具體產(chǎn)品我們也不需要實現(xiàn)每類產(chǎn)品的工廠類了。只需要在系統(tǒng)中配置多個抽象工廠,并且配置每個工廠的singlton為false,在用戶使用時使用不同抽象工廠的實例就可以了。
<bean name="userDaoFactory" class="research.spring.beanfactory.ch2.Factory" singleton="false">
<lookup-method name="getProduct" bean="userDao" />
bean>
<bean name="documentDaoFactory" class="research.spring.beanfactory.ch2.Factory" singleton="false">
<lookup-method name="getProduct" bean="documentDao" />
bean>
Spring不關(guān)心抽象類中的定義的lookup方法是否時抽象的,Spring都會重寫這個方法。
既然Sping可以動態(tài)實現(xiàn)抽象類的子類那么,它能不能動態(tài)創(chuàng)建出實現(xiàn)一個接口的類呢。答案時肯定的。上面的例子可以直接把Factory變成一個接口,仍然可以正常工作。
這里需要注意的是,只要在一個Bean上明確的定義了Lookup方法,Spring才會使用CGLIB來做原對象的字節(jié)碼代理。如果一個沒有定義Lookup方法的抽象類或接口是不能直接被Spring實例的。
本文介紹了Lookup方法的使用和工作原理,希望讀者能夠?qū)ookup方法有了比較深入的了解。雖然我的例子可以簡化工廠模式,但是我并不鼓勵大家在實際系統(tǒng)中這樣做。因為我始終認(rèn)為“工廠模式”只要在遺留系統(tǒng)中才會碰到。使用IoC模式基本上可以替代所有的對象創(chuàng)建模式。本章的例子只是為了說明Lookup方法如何使用,和Lookup方法的一些特殊情況。Lookup方法一般只在處理遺留代碼時使用。