|
利用Thread-Specific Storage撰寫一個HibernateUtil
import java.io.Serializable;
import net.sf.hibernate.HibernateException; import net.sf.hibernate.Session; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.Transaction;
public class HibernateSessionUtil implements Serializable { publicstaticfinal ThreadLocal tLocalsess = new ThreadLocal();
publicstaticfinal ThreadLocal tLocaltx = new ThreadLocal();
/* * getting the thread-safe session for using */ publicstatic Session currentSession(){ Session session = (Session) tLocalsess.get();
try{ if (session == null){ session = openSession(); tLocalsess.set(session); } }catch (HibernateException e){ thrownew InfrastructureException(e); } return session; }
/* * closing the thread-safe session */ publicstatic void closeSession(){
Session session = (Session) tLocalsess.get(); tLocalsess.set(null); try{ if (session != null && session.isOpen()){ session.close(); }
}catch (HibernateException e){ thrownew InfrastructureException(e); } }
/* * begin the transaction */ publicstatic void beginTransaction(){ Transaction tx = (Transaction) tLocaltx.get(); try{ if (tx == null){ tx = currentSession().beginTransaction(); tLocaltx.set(tx); } }catch (HibernateException e){ thrownew InfrastructureException(e); } }
/* * close the transaction */ publicstatic void commitTransaction(){ Transaction tx = (Transaction) tLocaltx.get(); try{ if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()) tx.commit(); tLocaltx.set(null); }catch (HibernateException e){ thrownew InfrastructureException(e); } }
/* * for rollbacking */ publicstatic void rollbackTransaction(){ Transaction tx = (Transaction) tLocaltx.get(); try{ tLocaltx.set(null); if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()){ tx.rollback(); } }catch (HibernateException e){ thrownew InfrastructureException(e); } }
privatestatic Session openSession() throws HibernateException{ return getSessionFactory().openSession(); }
privatestatic SessionFactory getSessionFactory() throws HibernateException{ return SingletonSessionFactory.getInstance(); } }
filter中的程式碼如下
public class HibernateSessionCloser implements Filter{
protected FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig)throws ServletException{ this.filterConfig = filterConfig; }
public void destroy(){ this.filterConfig = null; }
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try{ chain.doFilter(request, response); } finally{ try{ HibernateSessionUtil.commitTransaction(); }catch (InfrastructureException e){ HibernateSessionUtil.rollbackTransaction(); }finally{ HibernateSessionUtil.closeSession(); } }
} }
然後在操作資料庫之前加上
HibernateSessionUtil.beginTransaction(); HibernateSessionUtil.currentSession();
|
Java 5的泛型語法已經有太多書講了,這里不再打字貼書。GP一定有用,不然Java和C#不會約好了似的同時開始支持GP。但大家也清楚,GP和Ruby式的動態OO語言屬于不同的意識形態,如果是一人一票,我想大部分的平民程序員更熱衷動態OO語言的平白自然。但如果不準備跳槽到支持JSR223的動態語言,那還是看看GP吧。 ?? 胡亂總結泛型的四點作用:
?? 第一是泛化,可以拿個T代表任意類型。 但GP是被C++嚴苛的靜態性逼出來的,落到Java、C#這樣的花語平原里----所有對象除幾個原始類型外都派生于Object,再加上Java的反射功能,Java的Collection庫沒有范型一樣過得好好的。 ???第二是泛型 +?反射,原本因為Java的泛型拿不到T.class而覺得泛型沒用,最近才剛剛學到通過反射的API來獲取T的Class,后述。 ?? 第三是收斂,就是增加了類型安全,減少了強制類型轉換的代碼。這點倒是Java Collection歷來的弱項。 ?? 第四是可以在編譯期搞很多東西,比如MetaProgramming。但除非能完全封閉于框架內部,框架的使用者和擴展者都不用學習這些東西的用法,否則那就是自絕于人民的票房毒藥。C++的MetaProgramming好厲害吧,但對比一下Python拿Meta Programming生造一個Class出來的簡便語法,就明白什么才是真正的叫好又叫座。 ???所以,作為一個架構設計師,應該使用上述的第2,3項用法,在框架類里配合使用反射和泛型,使得框架的能力更強; 同時采用收斂特性,本著對人民負責的精神,用泛型使框架更加類型安全,更少強制類型轉換。 ??? ?? 擦拭法避免了Java的流血分裂?:??? 大家經常罵Java GP的擦拭法實現,但我覺得多虧于它的中庸特性---如果你用就是范型,不用就是普通Object,避免了Java陣營又要經歷一場to be or not to be的分裂。? ?? ?最大的例子莫過Java 5的Collection 框架,?比如有些同學堅持認為自己不會白癡到類型出錯,而且難以忍受每個定義的地方都要帶一個泛型定義List〈Book〉 ,不用強制類型轉換所省下的代碼還不夠N處定義花的(對了,java里面還沒有tyepdef.....),因此對范型十分不感冒,這時就要齊齊感謝這個搽拭法讓你依然可以對一個泛型框架保持非泛型的用法了...
?? 通過反射獲得 T.class: ??? ??? 不知為何書上不怎么講這個,是差沙告訴我才知道的,最經典的應用見Hibernate wiki的Generic Data Access Objects, 代碼如下:?
abstract?public?class?BaseHibernateEntityDao<T>?extends?HibernateDaoSupport?{ ?private?Class<T>?entityClass; ?public?BaseHibernateEntityDao()?{ ????????entityClass?=(Class<T>) ((ParameterizedType) getClass() ??????????????????????????????? .getGenericSuperclass()).getActualTypeArguments()[0]; ????} ?public?T?get(Serializable?id)?{ ????????T?o?=?(T)?getHibernateTemplate().get(entityClass,?id); } } ? 精華就是這句了:
Class<T>?entityClass?=?(Class<T>)?((ParameterizedType)?getClass().getGenericSuperclass()).getActualTypeArguments()[0];? ? 泛型之后,所有BaseHibernateEntityDao的子類只要定義了泛型,就無需再重載getEnttityClass(),get()函數和find()函數,銷益挺明顯的,所以SpringSide的Dao基類毫不猶豫就泛型了。
? 不過擦拭法的大棒仍在,所以子類的泛型語法可不能亂寫,最正確的用法只有:
??
public?class?BookDao?extends?BaseHibernateEntityDao<Book> ?
1:排序類
package com.tixa.bad.customer.util;
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator;
import com.tixa.bad.customer.data.AdstatGraphArea;
/** ?* 對地區數據進行排序 ?* ?* @StatAreaSort ?* ?* @author ?* ?* TODO ?*/ public class StatAreaSort { ?/** ? * 點擊排序規則,倒序 ? */ ?private static Comparator orderClick = new Comparator() ?{ ??public int compare(Object o1, Object o2) ??{ ???AdstatGraphArea b1 = (AdstatGraphArea) o1; ???AdstatGraphArea b2 = (AdstatGraphArea) o2; ???return (b2.getClickcount() - b1.getClickcount()); ??} ?};
?/** ? * ip排序規則,倒序 ? */ ?private static Comparator orderIP = new Comparator() ?{ ??public int compare(Object o1, Object o2) ??{ ???AdstatGraphArea b1 = (AdstatGraphArea) o1; ???AdstatGraphArea b2 = (AdstatGraphArea) o2; ???return (b2.getIpcount() - b1.getIpcount()); ??} ?};
?/** ? * 根據點擊量進行排序 ? * ? * @param list ? */ ?public static void getSortClick(ArrayList list) ?{ ??Collections.sort(list, orderClick); ??// Collections.reverse(list); ?}
?/** ? * 根據ip量進行排序 ? * ? * @param list ? */ ?public static void getSortIp(ArrayList list) ?{ ??Collections.sort(list, orderIP); ??// Collections.reverse(list); ?}
} 2:對排序進行測試 package com.tixa.bad.customer.util;
import java.util.ArrayList;
import junit.framework.TestCase;
import com.tixa.bad.customer.data.AdstatGraphArea;
public class StatAreaSortTest extends TestCase {
?protected void setUp() throws Exception ?{ ??super.setUp(); ?}
?protected void tearDown() throws Exception ?{ ??super.tearDown(); ?}
?/* ? * Test method for 'com.tixa.bad.customer.util.StatAreaSort.getSortClick(ArrayList)' ? */ ?public void testGetSortClick() ?{
???ArrayList list = new ArrayList(); ??? ???AdstatGraphArea graAre = new AdstatGraphArea(); ???graAre.setAdid(1); ???graAre.setClickcount(786); ???graAre.setIpcount(43453); ???list.add(graAre); ??? ???AdstatGraphArea graAre1 = new AdstatGraphArea(); ???graAre1.setAdid(2); ???graAre1.setClickcount(987876); ???graAre1.setIpcount(545); ???list.add(graAre1); ??? ???AdstatGraphArea graAre2 = new AdstatGraphArea(); ???graAre2.setAdid(3); ???graAre2.setClickcount(877887); ???graAre2.setIpcount(4534534); ???list.add(graAre2); ??? ???AdstatGraphArea graAre3 = new AdstatGraphArea(); ???graAre3.setAdid(4); ???graAre3.setClickcount(97998); ???graAre3.setIpcount(34534); ???list.add(graAre3); ??? ???AdstatGraphArea graAre4 = new AdstatGraphArea(); ???graAre4.setAdid(5); ???graAre4.setClickcount(500); ???graAre4.setIpcount(2000); ???list.add(graAre4); ??? ???System.out.print("sore before "); ???for(int i = 0;i<list.size();i++) ???{ ????AdstatGraphArea a = (AdstatGraphArea)list.get(i); ????System.out.println(a.getAdid()); ???} ??? ???StatAreaSort.getSortClick(list); ??? ???System.out.print("sore after"); ???for(int i = 0;i<list.size();i++) ???{ ????AdstatGraphArea a = (AdstatGraphArea)list.get(i); ????System.out.println(a.getAdid()); ???} ???System.out.println("----------------------"); ??? ???StatAreaSort.getSortIp(list); ???for(int i = 0;i<list.size();i++) ???{ ????AdstatGraphArea a = (AdstatGraphArea)list.get(i); ????System.out.println(a.getAdid()); ???} ?}
?/* ? * Test method for 'com.tixa.bad.customer.util.StatAreaSort.getSortIp(ArrayList)' ? */ ?public void testGetSortIp() ?{
?} }
一、避免在循環條件中使用復雜表達式
在不做編譯優化的情況下,在循環中,循環條件會被反復計算,如果不使用復雜表達式,而使循環條件值不變的話,程序將會運行的更快。
例子: import java.util.Vector; class CEL { ????void method (Vector vector) { ????????for (int i = 0; i < vector.size (); i++)??// Violation ????????????; // ... ????} }
更正: class CEL_fixed { ????void method (Vector vector) { ????????int size = vector.size () ????????for (int i = 0; i < size; i++) ????????????; // ... ????} }
二、為'Vectors' 和 'Hashtables'定義初始大小 JVM為Vector擴充大小的時候需要重新創建一個更大的數組,將原原先數組中的內容復制過來,最后,原先的數組再被回收。可見Vector容量的擴大是一個頗費時間的事。 通常,默認的10個元素大小是不夠的。你最好能準確的估計你所需要的最佳大小。
例子: import java.util.Vector; public class DIC { ????public void addObjects (Object[] o) { ????????// if length > 10, Vector needs to expand ????????for (int i = 0; i< o.length;i++) {???? ????????????v.add(o);???// capacity before it can add more elements. ????????} ????} ????public Vector v = new Vector();??// no initialCapacity. }
更正: 自己設定初始大小。 ????public Vector v = new Vector(20);?? ????public Hashtable hash = new Hashtable(10);
參考資料: Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming Techniques" Addison Wesley, ISBN: 0-201-70429-3 pp.55 – 57
三、在finally塊中關閉Stream 程序中使用到的資源應當被釋放,以避免資源泄漏。這最好在finally塊中去做。不管程序執行的結果如何,finally塊總是會執行的,以確保資源的正確關閉。 ????????? 例子: import java.io.*; public class CS { ????public static void main (String args[]) { ????????CS cs = new CS (); ????????cs.method (); ????} ????public void method () { ????????try { ????????????FileInputStream fis = new FileInputStream ("CS.java"); ????????????int count = 0; ????????????while (fis.read () != -1) ????????????????count++; ????????????System.out.println (count); ????????????fis.close (); ????????} catch (FileNotFoundException e1) { ????????} catch (IOException e2) { ????????} ????} } ????????? 更正: 在最后一個catch后添加一個finally塊
參考資料: Peter Haggar: "Practical Java - Programming Language Guide". Addison Wesley, 2000, pp.77-79
四、使用'System.arraycopy ()'代替通過來循環復制數組 'System.arraycopy ()' 要比通過循環來復制數組快的多。 ????????? 例子: public class IRB { ????void method () { ????????int[] array1 = new int [100]; ????????for (int i = 0; i < array1.length; i++) { ????????????array1 [i] = i; ????????} ????????int[] array2 = new int [100]; ????????for (int i = 0; i < array2.length; i++) { ????????????array2 [i] = array1 [i];?????????????????// Violation ????????} ????} } ????????? 更正: public class IRB { ????void method () { ????????int[] array1 = new int [100]; ????????for (int i = 0; i < array1.length; i++) { ????????????array1 [i] = i; ????????} ????????int[] array2 = new int [100]; ????????System.arraycopy(array1, 0, array2, 0, 100); ????} } ????????? 參考資料: http://www.cs.cmu.edu/~jch/java/speed.html
五、讓訪問實例內變量的getter/setter方法變成”final” 簡單的getter/setter方法應該被置成final,這會告訴編譯器,這個方法不會被重載,所以,可以變成”inlined”
例子: class MAF { ????public void setSize (int size) { ?????????_size = size; ????} ????private int _size; }
更正: class DAF_fixed { ????final public void setSize (int size) { ?????????_size = size; ????} ????private int _size; }
參考資料: Warren N. and Bishop P. (1999), "Java in Practice", p. 4-5 Addison-Wesley, ISBN 0-201-36065-9
六、避免不需要的instanceof操作 如果左邊的對象的靜態類型等于右邊的,instanceof表達式返回永遠為true。 ????????? 例子:????????? public class UISO { ????public UISO () {} } class Dog extends UISO { ????void method (Dog dog, UISO u) { ????????Dog d = dog; ????????if (d instanceof UISO) // always true. ????????????System.out.println("Dog is a UISO"); ????????UISO uiso = u; ????????if (uiso instanceof Object) // always true. ????????????System.out.println("uiso is an Object"); ????} } ????????? 更正:????????? 刪掉不需要的instanceof操作。 ????????? class Dog extends UISO { ????void method () { ????????Dog d; ????????System.out.println ("Dog is an UISO"); ????????System.out.println ("UISO is an UISO"); ????} }
七、避免不需要的造型操作 所有的類都是直接或者間接繼承自Object。同樣,所有的子類也都隱含的“等于”其父類。那么,由子類造型至父類的操作就是不必要的了。 例子: class UNC { ????String _id = "UNC"; } class Dog extends UNC { ????void method () { ????????Dog dog = new Dog (); ????????UNC animal = (UNC)dog;??// not necessary. ????????Object o = (Object)dog;?????????// not necessary. ????} } ????????? 更正:????????? class Dog extends UNC { ????void method () { ????????Dog dog = new Dog(); ????????UNC animal = dog; ????????Object o = dog; ????} } ????????? 參考資料: Nigel Warren, Philip Bishop: "Java in Practice - Design Styles and Idioms for Effective Java".??Addison-Wesley, 1999. pp.22-23
八、如果只是查找單個字符的話,用charAt()代替startsWith() 用一個字符作為參數調用startsWith()也會工作的很好,但從性能角度上來看,調用用String API無疑是錯誤的! ????????? 例子: public class PCTS { ????private void method(String s) { ????????if (s.startsWith("a")) { // violation ????????????// ... ????????} ????} } ????????? 更正????????? 將'startsWith()' 替換成'charAt()'. public class PCTS { ????private void method(String s) { ????????if ('a' == s.charAt(0)) { ????????????// ... ????????} ????} } ????????? 參考資料: Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming Techniques"??Addison Wesley, ISBN: 0-201-70429-3
九、使用移位操作來代替'a / b'操作 "/"是一個很“昂貴”的操作,使用移位操作將會更快更有效。
例子: public class SDIV { ????public static final int NUM = 16; ????public void calculate(int a) { ????????int div = a / 4;????????????// should be replaced with "a >> 2". ????????int div2 = a / 8;?????????// should be replaced with "a >> 3". ????????int temp = a / 3; ????} }
更正: public class SDIV { ????public static final int NUM = 16; ????public void calculate(int a) { ????????int div = a >> 2;?? ????????int div2 = a >> 3; ????????int temp = a / 3;???????// 不能轉換成位移操作 ????} }
十、使用移位操作代替'a * b' 同上。 [i]但我個人認為,除非是在一個非常大的循環內,性能非常重要,而且你很清楚你自己在做什么,方可使用這種方法。否則提高性能所帶來的程序晚讀性的降低將是不合算的。
例子: public class SMUL { ????public void calculate(int a) { ????????int mul = a * 4;????????????// should be replaced with "a << 2". ????????int mul2 = 8 * a;?????????// should be replaced with "a << 3". ????????int temp = a * 3; ????} }
更正: package OPT; public class SMUL { ????public void calculate(int a) { ????????int mul = a << 2;?? ????????int mul2 = a << 3; ????????int temp = a * 3;???????// 不能轉換 ????} }
十一、在字符串相加的時候,使用 ' ' 代替 " ",如果該字符串只有一個字符的話
例子: public class STR { ????public void method(String s) { ????????String string = s + "d"??// violation. ????????string = "abc" + "d"??????// violation. ????} }
更正: 將一個字符的字符串替換成' ' public class STR { ????public void method(String s) { ????????String string = s + 'd' ????????string = "abc" + 'd'??? ????} }
十二、不要在循環中調用synchronized(同步)方法 方法的同步需要消耗相當大的資料,在一個循環中調用它絕對不是一個好主意。
例子: import java.util.Vector; public class SYN { ????public synchronized void method (Object o) { ????} ????private void test () { ????????for (int i = 0; i < vector.size(); i++) { ????????????method (vector.elementAt(i));????// violation ????????} ????} ????private Vector vector = new Vector (5, 5); }
更正: 不要在循環體中調用同步方法,如果必須同步的話,推薦以下方式: import java.util.Vector; public class SYN { ????public void method (Object o) { ????} private void test () { ????synchronized{//在一個同步塊中執行非同步方法 ????????????for (int i = 0; i < vector.size(); i++) { ????????????????method (vector.elementAt(i));??? ????????????} ????????} ????} ????private Vector vector = new Vector (5, 5); }
十三、將try/catch塊移出循環 把try/catch塊放入循環體內,會極大的影響性能,如果編譯JIT被關閉或者你所使用的是一個不帶JIT的JVM,性能會將下降21%之多! ????????? 例子:????????? import java.io.FileInputStream; public class TRY { ????void method (FileInputStream fis) { ????????for (int i = 0; i < size; i++) { ????????????try {??????????????????????????????????????// violation ????????????????_sum += fis.read(); ????????????} catch (Exception e) {} ????????} ????} ????private int _sum; } ????????? 更正:????????? 將try/catch塊移出循環????????? ????void method (FileInputStream fis) { ????????try { ????????????for (int i = 0; i < size; i++) { ????????????????_sum += fis.read(); ????????????} ????????} catch (Exception e) {} ????} ????????? 參考資料: Peter Haggar: "Practical Java - Programming Language Guide". Addison Wesley, 2000, pp.81 – 83
十四、對于boolean值,避免不必要的等式判斷 將一個boolean值與一個true比較是一個恒等操作(直接返回該boolean變量的值). 移走對于boolean的不必要操作至少會帶來2個好處: 1)代碼執行的更快 (生成的字節碼少了5個字節); 2)代碼也會更加干凈 。
例子: public class UEQ { ????boolean method (String string) { ????????return string.endsWith ("a") == true;???// Violation ????} }
更正: class UEQ_fixed { ????boolean method (String string) { ????????return string.endsWith ("a"); ????} }
十五、對于常量字符串,用'String' 代替 'StringBuffer' 常量字符串并不需要動態改變長度。 例子: public class USC { ????String method () { ????????StringBuffer s = new StringBuffer ("Hello"); ????????String t = s + "World!"; ????????return t; ????} }
更正: 把StringBuffer換成String,如果確定這個String不會再變的話,這將會減少運行開銷提高性能。
十六、用'StringTokenizer' 代替 'indexOf()' 和'substring()' 字符串的分析在很多應用中都是常見的。使用indexOf()和substring()來分析字符串容易導致StringIndexOutOfBoundsException。而使用StringTokenizer類來分析字符串則會容易一些,效率也會高一些。
例子: public class UST { ????void parseString(String string) { ????????int index = 0; ????????while ((index = string.indexOf(".", index)) != -1) { ????????????System.out.println (string.substring(index, string.length())); ????????} ????} }
參考資料: Graig Larman, Rhett Guthrie: "Java 2 Performance and Idiom Guide" Prentice Hall PTR, ISBN: 0-13-014260-3 pp. 282 – 283
十七、使用條件操作符替代"if (cond) return; else return;" 結構 條件操作符更加的簡捷 例子: public class IF { ????public int method(boolean isDone) { ????????if (isDone) { ????????????return 0; ????????} else { ????????????return 10; ????????} ????} }
更正: public class IF { ????public int method(boolean isDone) { ????????return (isDone ? 0 : 10); ????} }
十八、使用條件操作符代替"if (cond) a = b; else a = c;" 結構 例子: public class IFAS { ????void method(boolean isTrue) { ????????if (isTrue) { ????????????_value = 0; ????????} else { ????????????_value = 1; ????????} ????} ????private int _value = 0; }
更正: public class IFAS { ????void method(boolean isTrue) { ????????_value = (isTrue ? 0 : 1);???????// compact expression. ????} ????private int _value = 0; }
十九、不要在循環體中實例化變量 在循環體中實例化臨時變量將會增加內存消耗
例子:????????? import java.util.Vector; public class LOOP { ????void method (Vector v) { ????????for (int i=0;i < v.size();i++) { ????????????Object o = new Object(); ????????????o = v.elementAt(i); ????????} ????} } ????????? 更正:????????? 在循環體外定義變量,并反復使用????????? import java.util.Vector; public class LOOP { ????void method (Vector v) { ????????Object o; ????????for (int i=0;i<v.size();i++) { ????????????o = v.elementAt(i); ????????} ????} }
二十、確定 StringBuffer的容量 StringBuffer的構造器會創建一個默認大小(通常是16)的字符數組。在使用中,如果超出這個大小,就會重新分配內存,創建一個更大的數組,并將原先的數組復制過來,再丟棄舊的數組。在大多數情況下,你可以在創建StringBuffer的時候指定大小,這樣就避免了在容量不夠的時候自動增長,以提高性能。
例子:????????? public class RSBC { ????void method () { ????????StringBuffer buffer = new StringBuffer(); // violation ????????buffer.append ("hello"); ????} } ????????? 更正:????????? 為StringBuffer提供寢大小。????????? public class RSBC { ????void method () { ????????StringBuffer buffer = new StringBuffer(MAX); ????????buffer.append ("hello"); ????} ????private final int MAX = 100; } ????????? 參考資料: Dov Bulka, "Java Performance and Scalability Volume 1: Server-Side Programming Techniques" Addison Wesley, ISBN: 0-201-70429-3 p.30 – 31
二十一、盡可能的使用棧變量 如果一個變量需要經常訪問,那么你就需要考慮這個變量的作用域了。static? local?還是實例變量?訪問靜態變量和實例變量將會比訪問局部變量多耗費2-3個時鐘周期。 ????????? 例子: public class USV { ????void getSum (int[] values) { ????????for (int i=0; i < value.length; i++) { ????????????_sum += value[i];???????????// violation. ????????} ????} ????void getSum2 (int[] values) { ????????for (int i=0; i < value.length; i++) { ????????????_staticSum += value[i]; ????????} ????} ????private int _sum; ????private static int _staticSum; }????? ????????? 更正:????????? 如果可能,請使用局部變量作為你經常訪問的變量。 你可以按下面的方法來修改getSum()方法:????????? void getSum (int[] values) { ????int sum = _sum;??// temporary local variable. ????for (int i=0; i < value.length; i++) { ????????sum += value[i]; ????} ????_sum = sum; } ????????? 參考資料:????????? Peter Haggar: "Practical Java - Programming Language Guide". Addison Wesley, 2000, pp.122 – 125
二十二、不要總是使用取反操作符(!) 取反操作符(!)降低程序的可讀性,所以不要總是使用。
例子: public class DUN { ????boolean method (boolean a, boolean b) { ????????if (!a) ????????????return !a; ????????else ????????????return !b; ????} }
更正: 如果可能不要使用取反操作符(!)
二十三、與一個接口 進行instanceof操作 基于接口的設計通常是件好事,因為它允許有不同的實現,而又保持靈活。只要可能,對一個對象進行instanceof操作,以判斷它是否某一接口要比是否某一個類要快。
例子: public class INSOF { ????private void method (Object o) { ????????if (o instanceof InterfaceBase) { }??// better ????????if (o instanceof ClassBase) { }???// worse. ????} }
class ClassBase {} interface InterfaceBase {}
?UUID(Universally Unique Identifier)全局唯一標識符,是指在一臺機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的。按照開放軟件基金會(OSF)制定的標準計算,用到了以太網卡地址、納秒級時間、芯片ID碼和許多可能的數字。由以下幾部分的組合:當前日期和時間(UUID的第一個部分與時間有關,如果你在生成一個UUID之后,過幾秒又生成一個UUID,則第一個部分不同,其余相同),時鐘序列,全局唯一的IEEE機器識別號(如果有網卡,從網卡獲得,沒有網卡以其他方式獲得),UUID的唯一缺陷在于生成的結果串會比較長。
在Java中生成UUID主要有以下幾種方式:
1. JDK1.5 如果使用的JDK1.5的話,那么生成UUID變成了一件簡單的事,以為JDK實現了UUID: java.util.UUID,直接調用即可. UUID uuid? =? UUID.randomUUID();
2. 第三方開源類庫(推薦使用): 最著名的是 JUG .特點上是: 純Java實現,開源,LGPL協議。采用了Native的方式產生真正的Uuid.而且提供了不同平臺的實現,包括:
??? * Linux / x86 ??? * Windows (98, ME, NT, 2K, XP?) / x86 ??? * Solaris / Sparc ??? * Mac OS X ??? * FreeBSD / x86
import? org.doomdark.uuid.UUID; import? org.doomdark.uuid.UUIDGenerator;
UUIDGenerator generator? =? UUIDGenerator.getInstance(); UUID uuid? =? generator.generateRandomBasedUUID();
3. Java代碼實現: 如果你使用JDK1.4或以前版本,又不想加入第三方的類庫的話,下面提供了一個純Java的UUID實現. 不過需要注意的是:這里產生的可能不是真正的UUID,只不過重復的機會少一些而已。 import? java.net.InetAddress; import? java.net.UnknownHostException;
/** */ /** ?* UUID(Univeral Unique Identity)類 ?*
?* Title: 生成唯一編碼 ?*
?*
?* Description: 源自于w3c.org <</font> http://源自于w3c.org > ?* ?*
?* Copyright: Copyright (c) 2001-2004 ?*
?* ?*? @version? 1.0 ? */ public?? final?? class? UUID? {
???? /** */ /** ???? *? @serial? Integer that helps create a unique UID. ????? */ ???? private?? int? unique;
???? /** */ /** ???? *? @serial? Long used to record the time. The time will be ???? *???????? used to create a unique UID. ????? */ ???? private?? long? time;
???? /** */ /** ???? * InetAddress to make the UID globally unique ????? */ ???? private?? static? String address;
???? /** */ /** ???? * a random number ????? */ ???? private?? static?? int? hostUnique;
???? /** */ /** ???? * Used for synchronization ????? */ ???? private?? static? Object mutex;
???? private?? static?? long? lastTime;
???? private?? static?? long? DELAY;
???? private?? static? String generateNoNetworkID()? { ??????? Thread current? =? Thread.currentThread(); ??????? String nid? =? current.activeCount()? +? System.getProperty( " os.version " ) ???????????????? +? System.getProperty( " user.name? " ) ???????????????? +? System.getProperty( " java.version " ); ??????? System.out.println( " netWorkId = "?? +? nid); ??????? MD5 md5? =?? new? MD5(nid); ??????? md5.processString(); ???????? return? md5.getStringDigest(); ??? }
???? static?? { ??????? hostUnique? =? ( new? Object()).hashCode(); ??????? mutex? =?? new? Object(); ??????? lastTime? =? System.currentTimeMillis(); ??????? DELAY? =?? 10 ;? //? in milliseconds ???????? try?? { ??????????? String s? =? InetAddress.getLocalHost().getHostAddress(); ??????????? MD5 md5? =?? new? MD5(s); ??????????? md5.processString(); ??????????? address? =? md5.getStringDigest(); ??????? }?? catch? (UnknownHostException ex)? { ??????????? address? =? generateNoNetworkID(); ??????? } ??? }
???? public? UUID()? { ???????? synchronized? (mutex)? { ???????????? boolean? done? =?? false ; ???????????? while? ( ! done)? { ??????????????? time? =? System.currentTimeMillis(); ???????????????? if? (time?? lastTime? +? DELAY)? { ???????????????????? //? pause for a second to wait for time to change ???????????????????? try?? { ??????????????????????? Thread.currentThread().sleep(DELAY); ??????????????????? }?? catch? (java.lang.InterruptedException e)? { ??????????????????? }?? //? ignore exception ???????????????????? continue ; ??????????????? }?? else?? { ??????????????????? lastTime? =? time; ??????????????????? done? =?? true ; ??????????????? } ??????????? } ??????????? unique? =? hostUnique; ??????? } ??? }
???? public? String toString()? { ???????? return? Integer.toString(unique,? 16 )? +? Long.toString(time,? 16 )? +? address; ??? }
???? public?? boolean? equals(Object obj)? { ???????? if? ((obj? !=?? null )? &&? (obj? instanceof? UUID))? { ??????????? UUID uuid? =? (UUID) obj; ???????????? return? (unique? ==? uuid.unique? &&? time? ==? uuid.time? &&? address ??????????????????? .equals(uuid.address)); ??????? }?? else?? { ???????????? return?? false ; ??????? } ??? }
???? public?? static?? void? main(String args[])? { ??????? System.out.println( new? UUID()); ??????? System.out.println( new? UUID()); ??????? System.out.println( new? UUID()); ???????? long? start? =? System.currentTimeMillis(); ??????? System.out.println( new? UUID()); ???????? long? end? =? System.currentTimeMillis(); ??????? System.out.println((end? -? start)); ??????? System.out.println( new? UUID().toString().length()); ??? }
???? /** */ /** ???? * 返回最新的UUID號碼 ???? * ???? *? @return? String UUID,長50位 ???? * ????? */ ???? public?? final?? static? String getUUID()? { ??????? UUID uid? =?? new? UUID(); ???????? return? uid.toString(); ??? } }
其中使用到MD5加密算法,實現代碼如下:
import? java.io.ByteArrayInputStream; import? java.io.File; import? java.io.FileInputStream; import? java.io.IOException; import? java.io.InputStream; import? java.io.UnsupportedEncodingException;
/** */ /** ?* MD5 加密算法類 ?* ?*
?* Description: 源自于w3c.org <</font> http://源自于w3c.org > ?* ?*
?* Copyright: Copyright (c) 2001-2004 ?*
?* ?*? @version? 1.0 ? */ public?? class? MD5? { ???? private?? static?? final?? int? BUFFER_SIZE? =?? 1024 ;
???? private?? static?? final?? int? S11? =?? 7 ;
???? private?? static?? final?? int? S12? =?? 12 ;
???? private?? static?? final?? int? S13? =?? 17 ;
???? private?? static?? final?? int? S14? =?? 22 ;
???? private?? static?? final?? int? S21? =?? 5 ;
???? private?? static?? final?? int? S22? =?? 9 ;
???? private?? static?? final?? int? S23? =?? 14 ;
???? private?? static?? final?? int? S24? =?? 20 ;
???? private?? static?? final?? int? S31? =?? 4 ;
???? private?? static?? final?? int? S32? =?? 11 ;
???? private?? static?? final?? int? S33? =?? 16 ;
???? private?? static?? final?? int? S34? =?? 23 ;
???? private?? static?? final?? int? S41? =?? 6 ;
???? private?? static?? final?? int? S42? =?? 10 ;
???? private?? static?? final?? int? S43? =?? 15 ;
???? private?? static?? final?? int? S44? =?? 21 ;
???? private?? static?? byte? padding[]? =?? { ( byte )? 0x80 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ( byte )? 0 , ??????????? ( byte )? 0? } ;
???? private? InputStream in? =?? null ;
???? private?? boolean? stringp? =?? false ;
???? private?? int? state[]? =?? null ;
???? private?? long? count? =?? 0 ;
???? private?? byte? buffer[]? =?? null ;
???? private?? byte? digest[]? =?? null ;
???? private?? static? String stringify( byte? buf[])? { ??????? StringBuffer sb? =?? new? StringBuffer( 2?? *? buf.length); ???????? for? ( int? i? =?? 0 ; i?? buf.length; i ++ )? { ???????????? int? h? =? (buf[i]? &?? 0xf0 )? >>?? 4 ; ???????????? int? l? =? (buf[i]? &?? 0x0f ); ??????????? sb.append( new? Character(( char ) ((h? >?? 9 )? ??? ' a '?? +? h? -?? 10? :? ' 0 '?? +? h))); ??????????? sb.append( new? Character(( char ) ((l? >?? 9 )? ??? ' a '?? +? l? -?? 10? :? ' 0 '?? +? l))); ??????? } ???????? return? sb.toString(); ??? }
???? private?? final?? int? F( int? x,? int? y,? int? z)? { ???????? return? ((x? &? y)? |? (( ~ x)? &? z)); ??? }
???? private?? final?? int? G( int? x,? int? y,? int? z)? { ???????? return? ((x? &? z)? |? (y? &? ( ~ z))); ??? }
???? private?? final?? int? H( int? x,? int? y,? int? z)? { ???????? return? (x? ^? y? ^? z); ??? }
???? private?? final?? int? I( int? x,? int? y,? int? z)? { ???????? return? (y? ^? (x? |? ( ~ z))); ??? }
???? private?? final?? int? rotate_left( int? x,? int? n)? { ???????? return? ((x? <<</span>? n) | (x >>> (32 -? n))); ??? }
???? private?? final?? int? FF( int? a,? int? b,? int? c,? int? d,? int? x,? int? s,? int? ac)? { ??????? a? +=? (F(b, c, d)? +? x? +? ac); ??????? a? =? rotate_left(a, s); ??????? a? +=? b; ???????? return? a; ??? }
???? private?? final?? int? GG( int? a,? int? b,? int? c,? int? d,? int? x,? int? s,? int? ac)? { ??????? a? +=? (G(b, c, d)? +? x? +? ac); ??????? a? =? rotate_left(a, s); ??????? a? +=? b; ???????? return? a; ??? }
???? private?? final?? int? HH( int? a,? int? b,? int? c,? int? d,? int? x,? int? s,? int? ac)? { ??????? a? +=? (H(b, c, d)? +? x? +? ac); ??????? a? =? rotate_left(a, s); ??????? a? +=? b; ???????? return? a; ??? }
???? private?? final?? int? II( int? a,? int? b,? int? c,? int? d,? int? x,? int? s,? int? ac)? { ??????? a? +=? (I(b, c, d)? +? x? +? ac); ??????? a? =? rotate_left(a, s); ??????? a? +=? b; ???????? return? a; ??? }
???? private?? final?? void? decode( int? output[],? byte? input[],? int? off,? int? len)? { ???????? int? i? =?? 0 ; ???????? int? j? =?? 0 ; ???????? for? (; j?? len; i ++ , j? +=?? 4 )? { ??????????? output[i]? =? ((( int ) (input[off? +? j]? &?? 0xff )) ???????????????????? |? ((( int ) (input[off? +? j? +?? 1 ]? &?? 0xff ))? <<</span>? 8 ) ???????????????????? |? ((( int ) (input[off? +? j? +?? 2 ]? &?? 0xff ))? <<</span>? 16) | (((int ) (input[off ???????????????????? +? j? +?? 3 ]? &?? 0xff ))? <<</span>? 24 )); ??????? } ??? }
???? private?? final?? void? transform( byte? block[],? int? offset)? { ???????? int? a? =? state[ 0 ]; ???????? int? b? =? state[ 1 ]; ???????? int? c? =? state[ 2 ]; ???????? int? d? =? state[ 3 ]; ???????? int? x[]? =?? new?? int [ 16 ];
??????? decode(x, block, offset,? 64 ); ???????? /**/ /*? Round 1? */ ??????? a? =? FF(a, b, c, d, x[ 0 ], S11,? 0xd76aa478 );? /**/ /*? 1? */ ??????? d? =? FF(d, a, b, c, x[ 1 ], S12,? 0xe8c7b756 );? /**/ /*? 2? */ ??????? c? =? FF(c, d, a, b, x[ 2 ], S13,? 0x242070db );? /**/ /*? 3? */ ??????? b? =? FF(b, c, d, a, x[ 3 ], S14,? 0xc1bdceee );? /**/ /*? 4? */ ??????? a? =? FF(a, b, c, d, x[ 4 ], S11,? 0xf57c0faf );? /**/ /*? 5? */ ??????? d? =? FF(d, a, b, c, x[ 5 ], S12,? 0x4787c62a );? /**/ /*? 6? */ ??????? c? =? FF(c, d, a, b, x[ 6 ], S13,? 0xa8304613 );? /**/ /*? 7? */ ??????? b? =? FF(b, c, d, a, x[ 7 ], S14,? 0xfd469501 );? /**/ /*? 8? */ ??????? a? =? FF(a, b, c, d, x[ 8 ], S11,? 0x698098d8 );? /**/ /*? 9? */ ??????? d? =? FF(d, a, b, c, x[ 9 ], S12,? 0x8b44f7af );? /**/ /*? 10? */ ??????? c? =? FF(c, d, a, b, x[ 10 ], S13,? 0xffff5bb1 );? /**/ /*? 11? */ ??????? b? =? FF(b, c, d, a, x[ 11 ], S14,? 0x895cd7be );? /**/ /*? 12? */ ??????? a? =? FF(a, b, c, d, x[ 12 ], S11,? 0x6b901122 );? /**/ /*? 13? */ ??????? d? =? FF(d, a, b, c, x[ 13 ], S12,? 0xfd987193 );? /**/ /*? 14? */ ??????? c? =? FF(c, d, a, b, x[ 14 ], S13,? 0xa679438e );? /**/ /*? 15? */ ??????? b? =? FF(b, c, d, a, x[ 15 ], S14,? 0x49b40821 );? /**/ /*? 16? */ ???????? /**/ /*? Round 2? */ ??????? a? =? GG(a, b, c, d, x[ 1 ], S21,? 0xf61e2562 );? /**/ /*? 17? */ ??????? d? =? GG(d, a, b, c, x[ 6 ], S22,? 0xc040b340 );? /**/ /*? 18? */ ??????? c? =? GG(c, d, a, b, x[ 11 ], S23,? 0x265e5a51 );? /**/ /*? 19? */ ??????? b? =? GG(b, c, d, a, x[ 0 ], S24,? 0xe9b6c7aa );? /**/ /*? 20? */ ??????? a? =? GG(a, b, c, d, x[ 5 ], S21,? 0xd62f105d );? /**/ /*? 21? */ ??????? d? =? GG(d, a, b, c, x[ 10 ], S22,? 0x2441453 );? /**/ /*? 22? */ ??????? c? =? GG(c, d, a, b, x[ 15 ], S23,? 0xd8a1e681 );? /**/ /*? 23? */ ??????? b? =? GG(b, c, d, a, x[ 4 ], S24,? 0xe7d3fbc8 );? /**/ /*? 24? */ ??????? a? =? GG(a, b, c, d, x[ 9 ], S21,? 0x21e1cde6 );? /**/ /*? 25? */ ??????? d? =? GG(d, a, b, c, x[ 14 ], S22,? 0xc33707d6 );? /**/ /*? 26? */ ??????? c? =? GG(c, d, a, b, x[ 3 ], S23,? 0xf4d50d87 );? /**/ /*? 27? */ ??????? b? =? GG(b, c, d, a, x[ 8 ], S24,? 0x455a14ed );? /**/ /*? 28? */ ??????? a? =? GG(a, b, c, d, x[ 13 ], S21,? 0xa9e3e905 );? /**/ /*? 29? */ ??????? d? =? GG(d, a, b, c, x[ 2 ], S22,? 0xfcefa3f8 );? /**/ /*? 30? */ ??????? c? =? GG(c, d, a, b, x[ 7 ], S23,? 0x676f02d9 );? /**/ /*? 31? */ ??????? b? =? GG(b, c, d, a, x[ 12 ], S24,? 0x8d2a4c8a );? /**/ /*? 32? */
???????? /**/ /*? Round 3? */ ??????? a? =? HH(a, b, c, d, x[ 5 ], S31,? 0xfffa3942 );? /**/ /*? 33? */ ??????? d? =? HH(d, a, b, c, x[ 8 ], S32,? 0x8771f681 );? /**/ /*? 34? */ ??????? c? =? HH(c, d, a, b, x[ 11 ], S33,? 0x6d9d6122 );? /**/ /*? 35? */ ??????? b? =? HH(b, c, d, a, x[ 14 ], S34,? 0xfde5380c );? /**/ /*? 36? */ ??????? a? =? HH(a, b, c, d, x[ 1 ], S31,? 0xa4beea44 );? /**/ /*? 37? */ ??????? d? =? HH(d, a, b, c, x[ 4 ], S32,? 0x4bdecfa9 );? /**/ /*? 38? */ ??????? c? =? HH(c, d, a, b, x[ 7 ], S33,? 0xf6bb4b60 );? /**/ /*? 39? */ ??????? b? =? HH(b, c, d, a, x[ 10 ], S34,? 0xbebfbc70 );? /**/ /*? 40? */ ??????? a? =? HH(a, b, c, d, x[ 13 ], S31,? 0x289b7ec6 );? /**/ /*? 41? */ ??????? d? =? HH(d, a, b, c, x[ 0 ], S32,? 0xeaa127fa );? /**/ /*? 42? */ ??????? c? =? HH(c, d, a, b, x[ 3 ], S33,? 0xd4ef3085 );? /**/ /*? 43? */ ??????? b? =? HH(b, c, d, a, x[ 6 ], S34,? 0x4881d05 );? /**/ /*? 44? */ ??????? a? =? HH(a, b, c, d, x[ 9 ], S31,? 0xd9d4d039 );? /**/ /*? 45? */ ??????? d? =? HH(d, a, b, c, x[ 12 ], S32,? 0xe6db99e5 );? /**/ /*? 46? */ ??????? c? =? HH(c, d, a, b, x[ 15 ], S33,? 0x1fa27cf8 );? /**/ /*? 47? */ ??????? b? =? HH(b, c, d, a, x[ 2 ], S34,? 0xc4ac5665 );? /**/ /*? 48? */
???????? /**/ /*? Round 4? */ ??????? a? =? II(a, b, c, d, x[ 0 ], S41,? 0xf4292244 );? /**/ /*? 49? */ ??????? d? =? II(d, a, b, c, x[ 7 ], S42,? 0x432aff97 );? /**/ /*? 50? */ ??????? c? =? II(c, d, a, b, x[ 14 ], S43,? 0xab9423a7 );? /**/ /*? 51? */ ??????? b? =? II(b, c, d, a, x[ 5 ], S44,? 0xfc93a039 );? /**/ /*? 52? */ ??????? a? =? II(a, b, c, d, x[ 12 ], S41,? 0x655b59c3 );? /**/ /*? 53? */ ??????? d? =? II(d, a, b, c, x[ 3 ], S42,? 0x8f0ccc92 );? /**/ /*? 54? */ ??????? c? =? II(c, d, a, b, x[ 10 ], S43,? 0xffeff47d );? /**/ /*? 55? */ ??????? b? =? II(b, c, d, a, x[ 1 ], S44,? 0x85845dd1 );? /**/ /*? 56? */ ??????? a? =? II(a, b, c, d, x[ 8 ], S41,? 0x6fa87e4f );? /**/ /*? 57? */ ??????? d? =? II(d, a, b, c, x[ 15 ], S42,? 0xfe2ce6e0 );? /**/ /*? 58? */ ??????? c? =? II(c, d, a, b, x[ 6 ], S43,? 0xa3014314 );? /**/ /*? 59? */ ??????? b? =? II(b, c, d, a, x[ 13 ], S44,? 0x4e0811a1 );? /**/ /*? 60? */ ??????? a? =? II(a, b, c, d, x[ 4 ], S41,? 0xf7537e82 );? /**/ /*? 61? */ ??????? d? =? II(d, a, b, c, x[ 11 ], S42,? 0xbd3af235 );? /**/ /*? 62? */ ??????? c? =? II(c, d, a, b, x[ 2 ], S43,? 0x2ad7d2bb );? /**/ /*? 63? */ ??????? b? =? II(b, c, d, a, x[ 9 ], S44,? 0xeb86d391 );? /**/ /*? 64? */
??????? state[ 0 ]? +=? a; ??????? state[ 1 ]? +=? b; ??????? state[ 2 ]? +=? c; ??????? state[ 3 ]? +=? d; ??? }
???? private?? final?? void? update( byte? input[],? int? len)? { ???????? int? index? =? (( int ) (count? >>?? 3 ))? &?? 0x3f ; ??????? count? +=? (len? <<</span>? 3 ); ???????? int? partLen? =?? 64?? -? index; ???????? int? i? =?? 0 ; ???????? if? (len? >=? partLen)? { ??????????? System.arraycopy(input,? 0 , buffer, index, partLen); ??????????? transform(buffer,? 0 ); ???????????? for? (i? =? partLen; i? +?? 63??? len; i? +=?? 64 ) ??????????????? transform(input, i); ??????????? index? =?? 0 ; ??????? }?? else?? { ??????????? i? =?? 0 ; ??????? } ??????? System.arraycopy(input, i, buffer, index, len? -? i); ??? }
???? private?? byte [] end()? { ???????? byte? bits[]? =?? new?? byte [ 8 ]; ???????? for? ( int? i? =?? 0 ; i??? 8 ; i ++ ) ??????????? bits[i]? =? ( byte ) ((count? >>>? (i? *?? 8 ))? &?? 0xff ); ???????? int? index? =? (( int ) (count? >>?? 3 ))? &?? 0x3f ; ???????? int? padlen? =? (index??? 56 )? ?? ( 56?? -? index) : ( 120?? -? index); ??????? update(padding, padlen); ??????? update(bits,? 8 ); ???????? return? encode(state,? 16 ); ??? }
???? //? Encode the content.state array into 16 bytes array ???? private?? byte [] encode( int? input[],? int? len)? { ???????? byte? output[]? =?? new?? byte [len]; ???????? int? i? =?? 0 ; ???????? int? j? =?? 0 ; ???????? for? (; j?? len; i ++ , j? +=?? 4 )? { ??????????? output[j]? =? ( byte ) ((input[i])? &?? 0xff ); ??????????? output[j? +?? 1 ]? =? ( byte ) ((input[i]? >>?? 8 )? &?? 0xff ); ??????????? output[j? +?? 2 ]? =? ( byte ) ((input[i]? >>?? 16 )? &?? 0xff ); ??????????? output[j? +?? 3 ]? =? ( byte ) ((input[i]? >>?? 24 )? &?? 0xff ); ??????? } ???????? return? output; ??? }
???? /** */ /** ???? * Get the digest for our input stream. This method constructs the input ???? * stream digest, and return it, as a a String, following the MD5 (rfc1321) ???? * algorithm, ???? * ???? *? @return? An instance of String, giving the message digest. ???? *? @exception? IOException ???? *??????????????? Thrown if the digestifier was unable to read the input ???? *??????????????? stream. ????? */
???? public?? byte [] getDigest()? throws? IOException? { ???????? byte? buffer[]? =?? new?? byte [BUFFER_SIZE]; ???????? int? got? =?? - 1 ;
???????? if? (digest? !=?? null ) ???????????? return? digest; ???????? while? ((got? =? in.read(buffer))? >?? 0 ) ??????????? update(buffer, got); ???????? this .digest? =? end(); ???????? return? digest; ??? }
???? /** */ /** ???? * Get the digest, for this string digestifier. This method doesn't throw ???? * any IOException, since it knows that the underlying stream ws built from ???? * a String. ????? */
???? public?? byte [] processString()? { ???????? if? ( ! stringp) ???????????? throw?? new? RuntimeException( this .getClass().getName() ???????????????????? +?? " [processString] "?? +?? "? not a string. " ); ???????? try?? { ???????????? return? getDigest(); ??????? }?? catch? (IOException ex)? { ??????? } ???????? throw?? new? RuntimeException( this .getClass().getName() ???????????????? +?? " [processString] "?? +?? " : implementation error. " ); ??? }
???? /** */ /** ???? * Get the digest, as a proper string. ????? */
???? public? String getStringDigest()? { ???????? if? (digest? ==?? null ) ???????????? throw?? new? RuntimeException( this .getClass().getName() ???????????????????? +?? " [getStringDigest] "?? +?? " : called before processing. " ); ???????? return? stringify(digest); ??? }
???? /** */ /** ???? * Construct a digestifier for the given string. ???? * ???? *? @param? input ???? *??????????? The string to be digestified. ???? *? @param? encoding ???? *??????????? the encoding name used (such as UTF8) ????? */
???? public? MD5(String input, String enc)? { ???????? byte? bytes[]? =?? null ; ???????? try?? { ??????????? bytes? =? input.getBytes(enc); ??????? }?? catch? (UnsupportedEncodingException e)? { ???????????? throw?? new? RuntimeException( " no? "?? +? enc? +?? "? encoding!!! " ); ??????? } ???????? this .stringp? =?? true ; ???????? this .in? =?? new? ByteArrayInputStream(bytes); ???????? this .state? =?? new?? int [ 4 ]; ???????? this .buffer? =?? new?? byte [ 64 ]; ???????? this .count? =?? 0 ; ??????? state[ 0 ]? =?? 0x67452301 ; ??????? state[ 1 ]? =?? 0xefcdab89 ; ??????? state[ 2 ]? =?? 0x98badcfe ; ??????? state[ 3 ]? =?? 0x10325476 ; ??? }
???? /** */ /** ???? * Construct a digestifier for the given string. ???? * ???? *? @param? input ???? *??????????? The string to be digestified. ????? */
???? public? MD5(String input)? { ???????? this (input,? " UTF8 " ); ??? }
???? /** */ /** ???? * Construct a digestifier for the given input stream. ???? * ???? *? @param? in ???? *??????????? The input stream to be digestified. ????? */
???? public? MD5(InputStream in)? { ???????? this .stringp? =?? false ; ???????? this .in? =? in; ???????? this .state? =?? new?? int [ 4 ]; ???????? this .buffer? =?? new?? byte [ 64 ]; ???????? this .count? =?? 0 ; ??????? state[ 0 ]? =?? 0x67452301 ; ??????? state[ 1 ]? =?? 0xefcdab89 ; ??????? state[ 2 ]? =?? 0x98badcfe ; ??????? state[ 3 ]? =?? 0x10325476 ; ??? }
???? public?? static?? void? main(String args[])? throws? IOException? { ???????? if? (args.length? !=?? 1 )? { ??????????? System.out.println( " Md5? " ); ??????????? System.exit( 1 ); ??????? } ??????? MD5 md5? =?? new? MD5( new? FileInputStream( new? File(args[ 0 ]))); ???????? byte? b[]? =? md5.getDigest(); ??????? System.out.println(stringify(b)); ??? }
}
這是一個非常好的Socket服務器樣板程序,這個socket服務器可以為你建立指定的監聽端口、客戶端請求響應機制等一些服務器所具備的基本框架
/*
* Copyright (c) 2000 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 2nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book (recommended),
* visit http://www.davidflanagan.com/javaexamples2.
*/
import java.io.*;
import java.net.*;
import java.util.*;
/**
* This class is a generic framework for a flexible, multi-threaded server.
* It listens on any number of specified ports, and, when it receives a
* connection on a port, passes input and output streams to a specified Service
* object which provides the actual service. It can limit the number of
* concurrent connections, and logs activity to a specified stream.
**/
public class Server {
/**
* A main() method for running the server as a standalone program. The
* command-line arguments to the program should be pairs of servicenames
* and port numbers. For each pair, the program will dynamically load the
* named Service class, instantiate it, and tell the server to provide
* that Service on the specified port. The special -control argument
* should be followed by a password and port, and will start special
* server control service running on the specified port, protected by the
* specified password.
**/
public static void main(String[] args) {
try {
if (args.length < 2) // Check number of arguments
throw new IllegalArgumentException("Must specify a service");
// Create a Server object that uses standard out as its log and
// has a limit of ten concurrent connections at once.
Server s = new Server(System.out, 10);
// Parse the argument list
int i = 0;
while(i < args.length) {
if (args[i].equals("-control")) { // Handle the -control arg
i++;
String password = args[i++];
int port = Integer.parseInt(args[i++]);
// add control service
s.addService(new Control(s, password), port);
}
else {
// Otherwise start a named service on the specified port.
// Dynamically load and instantiate a Service class
String serviceName = args[i++];
Class serviceClass = Class.forName(serviceName);
Service service = (Service)serviceClass.newInstance();
int port = Integer.parseInt(args[i++]);
s.addService(service, port);
}
}
}
catch (Exception e) { // Display a message if anything goes wrong
System.err.println("Server: " + e);
System.err.println("Usage: java Server " +
"[-control ] " +
"[ ... ]");
System.exit(1);
}
}
// This is the state for the server
Map services; // Hashtable mapping ports to Listeners
Set connections; // The set of current connections
int maxConnections; // The concurrent connection limit
ThreadGroup threadGroup; // The threadgroup for all our threads
PrintWriter logStream; // Where we send our logging output to
/**
* This is the Server() constructor. It must be passed a stream
* to send log output to (may be null), and the limit on the number of
* concurrent connections.
**/
public Server(OutputStream logStream, int maxConnections) {
setLogStream(logStream);
log("Starting server");
threadGroup = new ThreadGroup(Server.class.getName());
this.maxConnections = maxConnections;
services = new HashMap();
connections = new HashSet(maxConnections);
}
/**
* A public method to set the current logging stream. Pass null
* to turn logging off
**/
public synchronized void setLogStream(OutputStream out) {
if (out != null) logStream = new PrintWriter(out);
else logStream = null;
}
/** Write the specified string to the log */
protected synchronized void log(String s) {
if (logStream != null) {
logStream.println("[" + new Date() + "] " + s);
logStream.flush();
}
}
/** Write the specified object to the log */
protected void log(Object o) { log(o.toString()); }
/**
* This method makes the server start providing a new service.
* It runs the specified Service object on the specified port.
**/
public synchronized void addService(Service service, int port)
throws IOException
{
Integer key = new Integer(port); // the hashtable key
// Check whether a service is already on that port
if (services.get(key) != null)
throw new IllegalArgumentException("Port " + port +
" already in use.");
// Create a Listener object to listen for connections on the port
Listener listener = new Listener(threadGroup, port, service);
// Store it in the hashtable
services.put(key, listener);
// Log it
log("Starting service " + service.getClass().getName() +
" on port " + port);
// Start the listener running.
listener.start();
}
/**
* This method makes the server stop providing a service on a port.
* It does not terminate any pending connections to that service, merely
* causes the server to stop accepting new connections
**/
public synchronized void removeService(int port) {
Integer key = new Integer(port); // hashtable key
// Look up the Listener object for the port in the hashtable
final Listener listener = (Listener) services.get(key);
if (listener == null) return;
// Ask the listener to stop
listener.pleaseStop();
// Remove it from the hashtable
services.remove(key);
// And log it.
log("Stopping service " + listener.service.getClass().getName() +
" on port " + port);
}
/**
* This nested Thread subclass is a "listener". It listens for
* connections on a specified port (using a ServerSocket) and when it gets
* a connection request, it calls the servers addConnection() method to
* accept (or reject) the connection. There is one Listener for each
* Service being provided by the Server.
**/
public class Listener extends Thread {
ServerSocket listen_socket; // The socket to listen for connections
int port; // The port we're listening on
Service service; // The service to provide on that port
volatile boolean stop = false; // Whether we've been asked to stop
/**
* The Listener constructor creates a thread for itself in the
* threadgroup. It creates a ServerSocket to listen for connections
* on the specified port. It arranges for the ServerSocket to be
* interruptible, so that services can be removed from the server.
**/
public Listener(ThreadGroup group, int port, Service service)
throws IOException
{
super(group, "Listener:" + port);
listen_socket = new ServerSocket(port);
// give it a non-zero timeout so accept() can be interrupted
listen_socket.setSoTimeout(600000);
this.port = port;
this.service = service;
}
/**
* This is the polite way to get a Listener to stop accepting
* connections
***/
public void pleaseStop() {
this.stop = true; // Set the stop flag
this.interrupt(); // Stop blocking in accept()
try { listen_socket.close(); } // Stop listening.
catch(IOException e) {}
}
/**
* A Listener is a Thread, and this is its body.
* Wait for connection requests, accept them, and pass the socket on
* to the addConnection method of the server.
**/
public void run() {
while(!stop) { // loop until we're asked to stop.
try {
Socket client = listen_socket.accept();
addConnection(client, service);
}
catch (InterruptedIOException e) {}
catch (IOException e) {log(e);}
}
}
}
/**
* This is the method that Listener objects call when they accept a
* connection from a client. It either creates a Connection object
* for the connection and adds it to the list of current connections,
* or, if the limit on connections has been reached, it closes the
* connection.
**/
protected synchronized void addConnection(Socket s, Service service) {
// If the connection limit has been reached
if (connections.size() >= maxConnections) {
try {
// Then tell the client it is being rejected.
PrintWriter out = new PrintWriter(s.getOutputStream());
out.print("Connection refused; " +
"the server is busy; please try again later.\n");
out.flush();
// And close the connection to the rejected client.
s.close();
// And log it, of course
log("Connection refused to " +
s.getInetAddress().getHostAddress() +
":" + s.getPort() + ": max connections reached.");
} catch (IOException e) {log(e);}
}
else { // Otherwise, if the limit has not been reached
// Create a Connection thread to handle this connection
Connection c = new Connection(s, service);
// Add it to the list of current connections
connections.add(c);
// Log this new connection
log("Connected to " + s.getInetAddress().getHostAddress() +
":" + s.getPort() + " on port " + s.getLocalPort() +
" for service " + service.getClass().getName());
// And start the Connection thread to provide the service
c.start();
}
}
/**
* A Connection thread calls this method just before it exits. It removes
* the specified Connection from the set of connections.
**/
protected synchronized void endConnection(Connection c) {
connections.remove(c);
log("Connection to " + c.client.getInetAddress().getHostAddress() +
":" + c.client.getPort() + " closed.");
}
/** Change the current connection limit */
public synchronized void setMaxConnections(int max) {
maxConnections = max;
}
/**
* This method displays status information about the server on the
* specified stream. It can be used for debugging, and is used by the
* Control service later in this example.
**/
public synchronized void displayStatus(PrintWriter out) {
// Display a list of all Services that are being provided
Iterator keys = services.keySet().iterator();
while(keys.hasNext()) {
Integer port = (Integer) keys.next();
Listener listener = (Listener) services.get(port);
out.print("SERVICE " + listener.service.getClass().getName()
+ " ON PORT " + port + "\n");
}
// Display the current connection limit
out.print("MAX CONNECTIONS: " + maxConnections + "\n");
// Display a list of all current connections
Iterator conns = connections.iterator();
while(conns.hasNext()) {
Connection c = (Connection)conns.next();
out.print("CONNECTED TO " +
c.client.getInetAddress().getHostAddress() +
":" + c.client.getPort() + " ON PORT " +
c.client.getLocalPort() + " FOR SERVICE " +
c.service.getClass().getName() + "\n");
}
}
/**
* This class is a subclass of Thread that handles an individual
* connection between a client and a Service provided by this server.
* Because each such connection has a thread of its own, each Service can
* have multiple connections pending at once. Despite all the other
* threads in use, this is the key feature that makes this a
* multi-threaded server implementation.
**/
public class Connection extends Thread {
Socket client; // The socket to talk to the client through
Service service; // The service being provided to that client
/**
* This constructor just saves some state and calls the superclass
* constructor to create a thread to handle the connection. Connection
* objects are created by Listener threads. These threads are part of
* the server's ThreadGroup, so all Connection threads are part of that
* group, too.
**/
public Connection(Socket client, Service service) {
super("Server.Connection:" +
client.getInetAddress().getHostAddress() +
":" + client.getPort());
this.client = client;
this.service = service;
}
/**
* This is the body of each and every Connection thread.
* All it does is pass the client input and output streams to the
* serve() method of the specified Service object. That method is
* responsible for reading from and writing to those streams to
* provide the actual service. Recall that the Service object has
* been passed from the Server.addService() method to a Listener
* object to the addConnection() method to this Connection object, and
* is now finally being used to provide the service. Note that just
* before this thread exits it always calls the endConnection() method
* to remove itself from the set of connections
**/
public void run() {
try {
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
service.serve(in, out);
}
catch (IOException e) {log(e);}
finally { endConnection(this); }
}
}
/**
* Here is the Service interface that we have seen so much of. It defines
* only a single method which is invoked to provide the service. serve()
* will be passed an input stream and an output stream to the client. It
* should do whatever it wants with them, and should close them before
* returning.
*
* All connections through the same port to this service share a single
* Service object. Thus, any state local to an individual connection must
* be stored in local variables within the serve() method. State that
* should be global to all connections on the same port should be stored
* in instance variables of the Service class. If the same Service is
* running on more than one port, there will typically be different
* Service instances for each port. Data that should be global to all
* connections on any port should be stored in static variables.
*
* Note that implementations of this interface must have a no-argument
* constructor if they are to be dynamically instantiated by the main()
* method of the Server class.
**/
public interface Service {
public void serve(InputStream in, OutputStream out) throws IOException;
}
/**
* A very simple service. It displays the current time on the server
* to the client, and closes the connection.
**/
public static class Time implements Service {
public void serve(InputStream i, OutputStream o) throws IOException {
PrintWriter out = new PrintWriter(o);
out.print(new Date() + "\n");
out.close();
i.close();
}
}
/**
* This is another example service. It reads lines of input from the
* client, and sends them back, reversed. It also displays a welcome
* message and instructions, and closes the connection when the user
* enters a '.' on a line by itself.
**/
public static class Reverse implements Service {
public void serve(InputStream i, OutputStream o) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(i));
PrintWriter out =
new PrintWriter(new BufferedWriter(new OutputStreamWriter(o)));
out.print("Welcome to the line reversal server.\n");
out.print("Enter lines. End with a '.' on a line by itself.\n");
for(;;) {
out.print("> ");
out.flush();
String line = in.readLine();
if ((line == null) || line.equals(".")) break;
for(int j = line.length()-1; j >= 0; j--)
out.print(line.charAt(j));
out.print("\n");
}
out.close();
in.close();
}
}
/**
* This service is an HTTP mirror, just like the HttpMirror class
* implemented earlier in this chapter. It echos back the client's
* HTTP request
**/
public static class HTTPMirror implements Service {
public void serve(InputStream i, OutputStream o) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(i));
PrintWriter out = new PrintWriter(o);
out.print("HTTP/1.0 200 \n");
out.print("Content-Type: text/plain\n\n");
String line;
while((line = in.readLine()) != null) {
if (line.length() == 0) break;
out.print(line + "\n");
}
out.close();
in.close();
}
}
/**
* This service demonstrates how to maintain state across connections by
* saving it in instance variables and using synchronized access to those
* variables. It maintains a count of how many clients have connected and
* tells each client what number it is
**/
public static class UniqueID implements Service {
public int id=0;
public synchronized int nextId() { return id++; }
public void serve(InputStream i, OutputStream o) throws IOException {
PrintWriter out = new PrintWriter(o);
out.print("You are client #: " + nextId() + "\n");
out.close();
i.close();
}
}
/**
* This is a non-trivial service. It implements a command-based protocol
* that gives password-protected runtime control over the operation of the
* server. See the main() method of the Server class to see how this
* service is started.
*
* The recognized commands are:
* password: give password; authorization is required for most commands
* add: dynamically add a named service on a specified port
* remove: dynamically remove the service running on a specified port
* max: change the current maximum connection limit.
* status: display current services, connections, and connection limit
* help: display a help message
* quit: disconnect
*
* This service displays a prompt, and sends all of its output to the user
* in capital letters. Only one client is allowed to connect to this
* service at a time.
**/
public static class Control implements Service {
Server server; // The server we control
String password; // The password we require
boolean connected = false; // Whether a client is already connected
/**
* Create a new Control service. It will control the specified Server
* object, and will require the specified password for authorization
* Note that this Service does not have a no argument constructor,
* which means that it cannot be dynamically instantiated and added as
* the other, generic services above can be.
**/
public Control(Server server, String password) {
this.server = server;
this.password = password;
}
/**
* This is the serve method that provides the service. It reads a
* line the client, and uses java.util.StringTokenizer to parse it
* into commands and arguments. It does various things depending on
* the command.
**/
public void serve(InputStream i, OutputStream o) throws IOException {
// Setup the streams
BufferedReader in = new BufferedReader(new InputStreamReader(i));
PrintWriter out = new PrintWriter(o);
String line; // For reading client input lines
// Has the user has given the password yet?
boolean authorized = false;
// If there is already a client connected to this service, display
// a message to this client and close the connection. We use a
// synchronized block to prevent a race condition.
synchronized(this) {
if (connected) {
out.print("ONLY ONE CONTROL CONNECTION ALLOWED.\n");
out.close();
return;
}
else connected = true;
}
// This is the main loop: read a command, parse it, and handle it
for(;;) { // infinite loop
out.print("> "); // Display a prompt
out.flush(); // Make it appear right away
line = in.readLine(); // Get the user's input
if (line == null) break; // Quit if we get EOF.
try {
// Use a StringTokenizer to parse the user's command
StringTokenizer t = new StringTokenizer(line);
if (!t.hasMoreTokens()) continue; // if input was empty
// Get first word of the input and convert to lower case
String command = t.nextToken().toLowerCase();
// Now compare to each of the possible commands, doing the
// appropriate thing for each command
if (command.equals("password")) { // Password command
String p = t.nextToken(); // Get the next word
if (p.equals(this.password)) { // Is it the password?
out.print("OK\n"); // Say so
authorized = true; // Grant authorization
}
else out.print("INVALID PASSWORD\n"); // Otherwise fail
}
else if (command.equals("add")) { // Add Service command
// Check whether password has been given
if (!authorized) out.print("PASSWORD REQUIRED\n");
else {
// Get the name of the service and try to
// dynamically load and instantiate it.
// Exceptions will be handled below
String serviceName = t.nextToken();
Class serviceClass = Class.forName(serviceName);
Service service;
try {
service = (Service)serviceClass.newInstance();
}
catch (NoSuchMethodError e) {
throw new IllegalArgumentException(
"Service must have a " +
"no-argument constructor");
}
int port = Integer.parseInt(t.nextToken());
// If no exceptions occurred, add the service
server.addService(service, port);
out.print("SERVICE ADDED\n"); // acknowledge
}
}
else if (command.equals("remove")) { // Remove service
if (!authorized) out.print("PASSWORD REQUIRED\n");
else {
int port = Integer.parseInt(t.nextToken());
server.removeService(port); // remove the service
out.print("SERVICE REMOVED\n"); // acknowledge
}
}
else if (command.equals("max")) { // Set connection limit
if (!authorized) out.print("PASSWORD REQUIRED\n");
else {
int max = Integer.parseInt(t.nextToken());
server.setMaxConnections(max);
out.print("MAX CONNECTIONS CHANGED\n");
}
}
else if (command.equals("status")) { // Status Display
if (!authorized) out.print("PASSWORD REQUIRED\n");
else server.displayStatus(out);
}
else if (command.equals("help")) { // Help command
// Display command syntax. Password not required
out.print("COMMANDS:\n" +
"\tpassword \n" +
"\tadd \n" +
"\tremove \n" +
"\tmax \n" +
"\tstatus\n" +
"\thelp\n" +
"\tquit\n");
}
else if (command.equals("quit")) break; // Quit command.
else out.print("UNRECOGNIZED COMMAND\n"); // Error
}
catch (Exception e) {
// If an exception occurred during the command, print an
// error message, then output details of the exception.
out.print("ERROR WHILE PARSING OR EXECUTING COMMAND:\n" +
e + "\n");
}
}
// Finally, when the loop command loop ends, close the streams
// and set our connected flag to false so that other clients can
// now connect.
connected = false;
out.close();
in.close();
}
}
}
|
一、軟件開發技術
1)服務器端
在最近5年內,Java還是主流,不光是因為當前的普及程度和遺留系統問題,而且除Microsoft幾乎所有大公司都投資到Java上面的原因,此外開源也是一股無法忽略的力量:除了Java方面的開源框架在推動Java,也有Linux在帶動java企業應用在普及(別忘記dotnet只能在 Windows Server上面運行)
dotnet有自己的優勢,但是在五年內無法和Java取得均勢,不光是因為Java普及帶來的優勢,也不光因為開源界對java的推動,也不光因為其他大公司在java上面的投資,而是很多公司的行業性質決定了dotnet的出局,例如電信行業,金融行業,電子政務行業等等,是根本沒有可能采用 dotnet的。
Python和Ruby算不上后起,但是很有競爭實力,不過基于上面的原因,仍然不能成為主流。
在Java服務器端技術中,清晰的分為兩條路線:高端的商業路線,這條路線是EJB3,J2EE5.0;低端的開源路線,這條路線是Hibernate, Spring。這兩條路線也有重疊的地方,例如開源的Struts幾乎成為J2EE Web層的標準,開源的Hibernate奠定了EJB3的基礎。但是劃分路線不是基于技術上的區別,而是基于商業運作上的區別。注重技術支持和商業服務的公司會選擇前者,注重成本控制和選擇自由的公司會選擇后者。
商業路線的技術方案是:EJB3+Struts; 開源路線的技術方案是:Spring+Hibernate+Struts/Webwork
Struts是一個很成功的開源框架,它的地位短期內還無法動搖,JavaEye有一項使命,就是動搖Struts在Java Web領域的地位,把它趕下王座,把Webwork扶上位!
商業的Web層技術,JSTL算是一個不錯的東西,但是和靈活的模板語言如FreeMarker相比,卻有很大的差距。JSF基本上是一個沒有前途的東西。商業Web層技術因為一直沒有出現好的應用,這樣也導致了Struts的上位。
服務器端業務層和持久層框架,我非常看好EJB3,原因也不用多談了,從商業上來說,需要這樣一個東西,跨國公司們也需要這樣一個產品來賣,來取代糟糕的 EJB2。開源的方案里面,Spring+Hibenrate是一個很好的商業方案的開源替代,他們不存在很直接的競爭,而是一個互補的關系。這里比較尷尬的反而是JDO:JDO是商業產品(目前沒有好的開源實現),造成開源應用不會對它感興趣,JDO沒有一個像EJB容器那樣的脫管環境,造成商業方案對它不感興趣。不過有了JDO,我覺得是對EJB3,對Hibernate形成一個良好的競爭環境,這一點是非常有利的。
2)客戶端技術
準確的說是RIA應用。雖然我前面對XAML進行了正面的評價,但是我認為我前面有些結論給錯了。經過這段時間,我覺得,XAML即時在多年之后,也未必能夠成為一個非常成功的解決方案。道理很二:
1、XAML會帶來比ActiveX更嚴重的安全性問題。 XAML本質上就是一個本地應用程序,雖然號稱可以在IE瀏覽器里面運行,但IE就是一個皮而已,XAML應用具備對本地資源完全的訪問能力(就算IE限制也沒有用,IE限制就喪失功能,那樣的話,功能并不會比Javascript來得更多;不限制的話,就為所欲為了),因此只要IE具備了運行XAML的能力,黑客將可以非常輕易的通過IE進行入侵,這僅僅需要引導用戶在不知不覺中訪問一個惡意的網頁就搞定了!用戶必須面臨選擇:要么禁止IE對XAML的運行能力,要么接受隨時被攻擊的危險。
2、XAML應用本質上也是RIA應用,因此必須進行大量的RPC調用 當前XAML采用XML Web Services進行通訊,這是一種低效的RPC。當前的XAML案例中并沒有注意到RPC領域,實際上根據我現在做RIA的體驗來說,RPC絕對不是一個簡單的事情,要考慮的問題非常多。
從當前的階段來說,最實際可用的方案有兩個:
1、AJAX 實際上就是基于XMLHTTP的JS異步交互,這個東西已經出現很多年了,最近隨著Google應用和Sun Blueprint的推出開始火熱。我原來對這個東西持否定態度,但是后來轉變了。我原來否定態度的一個前提就是:XMLHTTP缺乏成熟的組件庫!但是沒有想到的是,現在XMLHTTP從去年下半年開始,如雨后春筍般冒出來。AJAX應用最大的好處就是充分利用現有資源,我認為應成為RIA應用的首選。
2、Flash Flash的優勢也很明顯,強大的AS支持,強大的組件可視化設計,強大的交互能力和很炫的用戶體驗,并且Flash Remoting也已經非常成熟了。Flash的缺點就是Flash雖然嵌入網頁,但是和網頁沒有數據交互能力,Flash另一個缺點就是不適合處理大量文本內容(HTML最適合)。現在有些人開始濫用Flash了。
因此比較好的方式可能是兩種混用,一般不過度復雜的交互交給AJAX,非常復雜,甚至需要托拽操作的,交給Flash。
總結一下:
軟件開發領域服務器端技術Java是主流,兩個技術路線,一個是EJB3,一個是Spring+Hibernate,此外iBATIS也有一席之地;客戶端技術就是AJAX和Flash。
二、數據庫技術
基本上格局不會發生多大變化,Oracle還是高高在上,SQL Server進一步蠶食NT平臺其他數據庫的領地。開源方面,MySQL將一枝獨秀,但是開源數據庫在很多方面還是和商業數據庫有無法拉近的巨大差距。這也使得商業數據庫的地位不可替代。我會比較關注Oracle,MySQL這兩個數據庫。面向對象數據庫仍然不會有什么起色。
三、桌面編程技術
我還是相信一點,對于桌面應用來說,本地代碼的位置永遠無法被取代,所以我總覺得XAML那樣的東西效率實在很成問題。Longhorn要像成熟,也不是第一個版本就可以達到的。當前桌面應用開發技術,還是首推Delphi,不過我覺得Python是后起之秀,非常有可能在未來取代Delphi。
初探在下一代 Windows 中編寫和部署應用程序 http://www.microsoft.com/china/MSDN/library/windev/longhorn/DevelopAppLonghorn.mspx
首先,以Microsoft公司的實力和Windows操作系統的占有率來說,Longhorn遲早會被普及,而XAML的開發方式也有可能普及的。記得當初WindowsXP剛出來的時候,因為資源占用率和新的激活制度招致一片罵聲,但是慢慢的,現在也都接受了下來。由此可以推斷,Longhorn以其更加豐富的桌面功能和誘人的外觀,會在將來成為主流。
但是Longhorn什么時候才會全面普及,這是很值得琢磨的問題。WindowsXP是2001年推出的,在隨后的幾年,Microsoft采用了一些商業手段來迫使用戶升級,例如企圖取消Windows98的技術支持,不再提供WindowsNT技術支持,不再銷售 WindowsNT/Windows98,將Windows2000保持在一個比較高的售價的同時,對WindowsXP推出優惠價格,讓 WindowsXP的售價低于Windows2000等等手段。但是直到現在,Windows2000仍然占據了非常高的份額,據我個人的觀察是比 WindowsXP略高。按照這種情況來推斷,Longhorn要普及,恐怕難度更大,非常多的用戶現在仍然是Windows2000的死忠派, WindowsXP推廣了四年還未能超過Windows2000,那么Longhorn究竟要幾年才能超過WindowsXP呢?我估計四年以上是起碼的。
XAML應用程序不同以往,它只能跑在Longhorn上面,甚至比Java和dotnet要求更嚴格,后者僅僅下載安裝一個運行環境就可以了,但是前者要求你必須更新操作系統。XAML在IE瀏覽器中運行雖然肯定是下一代RIA的主流,但是不可忽視的問題是,只要Longhorn沒有徹底淘汰 Windows2000/XP,軟件開發商和網站開發商就不敢大面積采用XAML。而根據我的觀察,現在企業中,Windows98仍有少部分市場份額。因此Longhorn必須要等待到徹底的,毫不殘留的淘汰Windows98,Windows2000,WindowsXP之后,才會全面普及,而在此之前,不得不經歷一個漫長的過渡期。
就好像現在,假設你開發桌面應用程序,你敢只針對WindowsXP開發嗎?而徹底不支持98和2000嗎?我想,沒有哪個軟件開發商敢這樣做。除非 Windows2000幾乎被徹底淘汰了,你才敢這樣做,但是WindowsXP已經推出四年了,還沒有Windows2000占用率高,哪全面淘汰究竟要幾年呢?再看看現在dotnet winforms應用,推出也已經五年時間了,但是到現在仍然沒有普及開來,根本的原因就是Windows2000/WindowsXP沒有預裝 dotnet framework。僅僅是需要打包安裝一個運行環境就使得winforms五年都推廣不了,更何況要求你升級操作系統呢?
我個人的估計是,假設2006年Longhorn如期上市,那么將需要7-9年時間來徹底淘汰Windows2000/WindowsXP。 Longhorm上面XAML應用的初步普及也至少需要4-5年時間以后才會有軟件開發商大量去做(想向dotnet是2000年開始宣傳和推廣的,到 2004年開始普及,今年和明年才會全面普及)。因此,基于XAML應用的普及可能是在2010年以后!上面的估計中還沒有包括MacOS 和Linux在桌面會否有什么表現。
先說說服務器端吧:
從可預見的未來來看,服務器和客戶端TCP通訊的主流方式一定是HTTP協議(即時通訊軟件走UDP端口,不在討論范圍)。在基于HTTP協議之上,又分為兩類:一類是SOAP協議,異構系統支持良好,但是性能很差,目前Microsoft很喜歡用這種方式;一類是輕量級二進制協議,例如Flash的 AMF協議,Resin的Hessian協議。值得一提的是,不管哪種方式,他們都支持異構的系統,所以完全可用在客戶端采用dotnet,在服務器端采用Java或者Python。因此,XAML的流行不會對服務器端技術產生致命的影響(肯定會提高dotnet的服務器的市場份額)。所以我們可用拋開客戶端影響,單獨來看服務器端技術:
1、Java Java是當前服務器端技術當之無愧的王者,在未來五年內,也不會有任何動搖(受到dotnet和python的影響,市場份額會下降一些)。Java特別有利的一點是,現在有太多的現存系統基于Java,這些系統都不會輕易遷移到其他平臺上。另外還有一個決定因素是除了Microsoft之外的幾乎全部 IT大公司都在Java方面的投資巨大,放棄Java對他們來說也意味著沉重的打擊,甚至毀滅性的打擊。這些公司可以列很長很長,IBM,HP, Oracle,SAP,Sun,BEA,Macromedia等等。
2、dotnet 由于Microsoft的影響力,dotnet會成為為僅次于Java的第二大服務器端技術,但是Microsoft有一個隱憂,就是Linux操作系統在服務器端的高速成長。雖然現在Linux在整個服務器端市場的出貨量只有13%左右,但是成長率驚人,根據我看到的資料顯示,到2008年,將占據 25%以上的市場份額。考慮到很多公司是自己安裝Linux,因此不會被硬件服務器廠商統計進來,因此Linux的服務器端的市場份額應該比25%高一些。并且現在主要的服務器廠商都對Linux有非常巨大的投入和支持,這些公司包括IBM,HP,Dell(只有Sun不支持),因此Linux在未來會對Windows在服務器端的市場構成最嚴重的威脅。
不要忘記dotnet只能在Windows平臺上面跑,雖然有mono,但是你不可能移植MTS,COM+,SQL Server etc。所以只要Linux在服務器市場對Windows構成持續的威脅,dotnet就不可能超過Java,Java的地位還是穩穩的老大。從某種程度上來說,Java的命運是和Linux聯系在一起的,只要Linux在服務器端不輸于Windows,Java就穩穩壓制dotnet。
BTW:從未來來看,Linux和Windows會在低端和中端服務器市場成為主要競爭對手,由于各自都有其不可替代性,所以雙方都不可能徹底消滅對方,最大的可能性是Linux和Windows平分市場,或者Windows市場份額略高一點。
3、Python 我個人認為Python會成長為第三大服務器端技術,Python成長于開源,但是又有商業公司來商業運作,并且背后還有大公司的支持,在歐洲普及的非常好。當然最重要的原因是我覺得Python在技術上非常先進,并且技術發展方向上比較統一,不會出現Java那種吵架的事情。
4、PHP PHP這東西是不錯,Yahoo也在用,IBM現在也對他感興趣,但是我還是要說PHP沒有太廣闊的前途,原因很簡單,PHP沒有服務端中間件,例如 Java有App Server,dotnet有IIS/MTS,Python有Zope,但是PHP他就是一個腳本,沒有自己的中間件就是致命問題。Yahoo用PHP有其特定的原因,主要是從原先自己的技術遷移到PHP很方便,而IBM支持PHP,顯然醉翁之意不在酒,IBM意不在推廣PHP,而在于爭取到那些使用 PHP的商業大客戶們,向他們賣服務。
BTW:感覺歐洲用Python/PHP的很多,似乎開源在歐洲非常深入人心。
從服務器端技術來說,Java還是我們最需要下功夫去學習和掌握的,此外,我會比較傾向于鉆研和應用Python,而不是dotnet。原因也很簡單,跟隨Micorsoft的技術會很辛苦,Microsoft產生的新概念多,他總是會猛的推出n多種技術,然后讓他們在市場上自己生存,最后根據市場反饋,無情的拋棄某些東西,大力推進有市場前景的東西,這樣的例子太多了,舉不勝舉了。我的感覺就是這種方式會讓Microsft經過市場嘗試在技術競爭中篩選最優秀的技術,但是對于Microsoft技術的跟隨者來說,未免有點太不公平,整天吭哧吭哧被Microsoft拿來當免費的試驗品來用。我特別不理解的是MSDN宇宙版,Microsoft總是把無窮無盡的文檔灌給你,讓你永遠學不完,但實際上我真的不需要那么多概念,我只需要能夠很好的完成我工作的技術,并且這個技術可以持續的完善就好了。而不是今天給我這樣一個東西,明天灌給我無窮的文檔,后天當我用順手以后,又告訴我這東西作廢了,你給我重新學習新東西,然后又是無窮的文檔,總之很惱火。
所以就是:重點學習Java,有時間去學習Python,保持對dotnet的關注即可。
客戶端:
前面說了那么多XAML的東西,都是和這有關,七年以后肯定是XAML的天下,但是五到七年之內還不是:
1、Java Java在客戶端真的是扶不起的阿斗,這都怪Sun。Sun造就了Java的成功,又一手毀了Java在客戶端的市場。那些個Swing和SWT的死忠團也不要和我爭什么,我也懶得和你們爭,你們覺得好就好吧,道不同不相與謀,你覺得好你就用你的,我覺得不好我就用別的。用不著纏著我非逼我說Java做客戶端好,沒必要,況且就算你逼我承認又怎樣?我就是玉皇大帝金口玉言了?得到我的承認,Java就有前途了?我好像還沒有那么大本領吧?就是IBM, Sun也沒有那么大本領,所以好不好也不是我說了算,用不著逼我。
2、dotnet winforms 由于Windows2000/WindowsXP不帶dotnet CLR,所以winforms一直沒有能夠普及得很好,等Longhorn一出來,又變成了XAML了,winforms又被淘汰了,所以 winforms的地位特別尷尬,但是在這5-7年中,你想開發既能夠在Windows2000/WindowsXP,又能夠在Longhorn上面跑的桌面程序,winforms好像又是Microsoft技術中最好的選擇。所以只好一直尷尬下去。
3、VC,VB dotnet出來以后就開始尷尬了,說用吧,好像很落伍了,都dotnet時代了,說不用吧,又沒有好的替代品,現階段開發桌面程序,還真得不得不用,而且還挺好用的。所以VC6SP5,VB6的死忠團也比較多。
4、Delphi dotnet出來以后Borland就開始跟風了,這一跟風,連老本都跟沒有了。未來的XAML時代,我也不知道Borland怎樣找自己的定位,但不管怎么說,從歷史來看,本地代碼的應用程序永遠有它一席之地!就算XAML又如何如何做得漂亮了,關鍵的地方,和特定資源處理相關的部分,還是本地代碼的程序管用。你看VB出來多少年了,用VB開發的都是一些上層的項目級別的應用軟件,一旦涉及產品領域,還是VC和Delphi管用。所以現在大家還是不得不用Delphi7阿。
BTW:XAML應用致力于快速開發項目級別的應用,特別是可以跑在IE瀏覽器里面的,因此是RIA的首選。但是畢竟也有很多不適合用RIA的場所,特別是例如我要備份某些文件,你用XAML?那性能就不用提了。所以Delphi如果好好發展VCL,封裝Windows32 API,我覺得也是一條路,未必比現在跟隨dotnet差。
5、Flash RIA 其實我覺得Flash不適合做RIA的,但是Flash普及率太高,XAML又離普及太遙遠,而Flash現在就可以用了,所以是當前RIA的首選。不過我對Macromedia公司比較失望,如果Macromedia能夠公布Flash實現細節,作為一個公開的標準向ISO提交,同時免費開源Flex,我敢說,Flash RIA會迅速普及的。等5-7年XAML的時代,由于Flash的市場占有率,XAML就未必能拼得過Flash。可惜的是Macromedia公司目光過于短淺,只知道賺眼前的小錢。
6、Python 這5-7年內,RIA應用和RCP應用不會統一,XAML才具備將RIA和RCP統一的實力。從這5-7年來看,Flash是RIA的首選,而RCP的首選,我要推薦Python。原因前面已經提過,簡單總結一下: 1)wxWidgets是一個比MFC優雅的庫,TortoiseCVS用wxWidges而不用MFC,就是因為wxWidgets好用,而不是為了可以移植。 2)Python的面向對象腳本語言編程適合快速界面開發 3)Python在服務器端和客戶端都非常有前途,可以形成一個統一的解決方案,這一點明顯比Java有優勢 4)Python桌面應用程序可以完全編譯為本地代碼,脫離Python運行環境,這一點比dotnet winforms都有優勢 5)Python可以不受限制的任意調用Windows32 API,所以凡是VC6可以做的事情,Python就可以做
試想一下,現在我們開發桌面應用程序有什么要求? 一、不要附帶一個JRE或者CLR的累贅 二、可以快速開發 三、性能要有保證 四、方便的遠程方法調用支持 此外如果能夠跨平臺就最好了
Java前三點都不符合;dotnet winforms不符合一;VC6不符合二和四,VB6不符合三和四;Delphi7符合前四點;Flash RIA不符合三;Python全部都符合!并且請記住Python是一個完全開源免費的方案!
客戶端技術在這5-7年中,在RIA領域我會學習一下Flash,在RCP領域我會重點學習Python,此外會觀望一下XAML。
人們總是偏愛“大詞”。一個表達方式,如果聽起來足夠響亮,寫在紙上能夠吸引眼球,那就會變成很多人的新寵。但同樣是這些大詞,經過太多人的傳遞、消費之后,原本的含義反而像硬幣上的圖案一樣被磨損殆盡:幾乎沒有人知道這些說法到底是指什么了。在IT業界,“平臺(platform)”、“框架(framework)”、“構架(architecture)”等等就是這種人見人愛的大詞。幾乎每個廠商都愿意請來其中的一位、甚至多位為自己推銷。久而久之,這些說法似乎適用于各個領域、各個層面:所有的軟件系統都是“平臺”,所有的開發者都在自矜于獨有的“框架”。原本有確切意義的“好詞”,經過這一番爭奪和濫用,也只能衰減為所謂的“buzzwords”,供市場營銷人士們玩味了。 我想讓這些詞中的一個——“框架”——蕩污滌垢,重現青春。要完成這樣的任務,必須動用重典才行。軟件業圣經《設計模式》對框架有如下定義:“A framework is a set of cooperating classes that make up a reusable design for a specific class of software(一個框架,就是一組相互協作的類,對于特定的一類軟件,框架構成了一種可重用的設計)”。這個定義雖然主要著眼于面向對象的軟件開發,但已經基本上給出了這個詞的核心含義:框架是軟件系統的設計、開發過程中的一個概念,它強調對以完成的設計、代碼的重復使用,并且,一個框架主要適用于實現某一特定類型的軟件系統。 為了更好地說明框架是什么,也許還應該看看框架不是什么。 框架不是現成可用的應用系統。它仍是一個半成品,等待后來者做“二次開發”,實現為具體的應用系統。 框架不是“平臺”。后者的概念更加浮泛和模糊——人們說的一個平臺,可以是一種操作系統,一種應用服務器,一種數據庫軟件,一種通信中間件等等,因此“平臺”幾乎成了所有系統軟件的統稱。在平臺的大家族中,框架的概念可能與近來人們常說的“應用平臺”最為接近,但平臺主要指提供特定服務的系統軟件,而框架則更側重于設計、開發過程,或者可以說,框架通過調用平臺提供的服務而起作用。 框架不是工具包(toolkit)/類庫(library) /API。目前流行的很多框架中,就包括了大量的類庫和API,但是調用API并不就是在使用框架開發。僅僅使用API時,開發者完成系統的主體部分,并不時地調用類庫實現特定任務。而框架構成了通用的、具有一般性的系統主體部分,“二次開發者”只是像做填空題一樣,根據具體業務,完成特定應用系統中與眾不同特殊的部分。 框架不是構架(architecture)。構架確定了系統整體結構、層次劃分、不同部分之間的協作等設計考慮。框架比構架更具體,更偏重于技術實現。確定框架后,構架也隨之確定,而對于同一種構架(比如web開發中的MVC),可以通過多種框架(比如Apache Struts或Apache Velocity)實現。
2
那么,在企業應用系統開發中,框架具有什么樣的意義?要闡明這一點,大概要看一看在這個領域里軟件開發方式的演變。在計算機應用普及之前,只有少數大企業才負擔得起企業信息系統軟件,這一類的軟件開發也已委托定制(custom-made software)為主。在企業信息化基礎設施逐步完備之后,多數中、小企業也要在預算不高的前提下實施企業應用系統,按照以前的方式逐個定制開發,是這種類型的企業難以承受的。因此,對于一些需求簡明的系統,往往會購買現成軟件(shrink-wrapped software)解決問題。但是各個企業具體業務不同,需求很難統一,現成軟件只能滿足最通用的情況和最一致的操作(比如財會系統、網站內容發布系統等),對于頭緒眾多的業務處理就難以勝任了。 如何最大程度地萃取不同企業應用系統的共性,重復使用已經完成的設計和代碼,對企業應用系統中典型場景給出最佳解決方案——這是一個“一般性”的問題;如何讓一個早先完成的軟件產品貼切地適應極為多變、復雜的企業需求——這是一個“特殊性”的問題。作為對這一組沖突的一種解決方案,不少廠商推出了自己的企業應用框架。這些框架往往是從大量的委托項目開發中精選出的系統“不變項”,因此具有很強的普適性和實用性。 目前,主流企業應用框架中大都包含對以下問題的現成解決方案: * 持久性(persistence):實現數據存儲、處理,數據與對象映射,數據緩存(caching)。 * 事務(transaction):確保一組關聯操作正常、完整的執行。 * 安全性(security):保證系統的通信安全、數據安全。 * 負載均衡(load balance):在大量并發訪問時,保持系統可用。 * 監控(system monitoring/management):監控系統運行狀況,設置系統參數。 * 日志(logging):記錄系統運行情況和異常,記錄特定用戶操作。 * 應用集成 (application integration):與其他系統、應用程序集成。 * 認證/權限/組織角色管理(authentication/authorization):管理系統用戶、組織職權結構,限制特定用戶對特定功能、特定數據的訪問。 * 業務模型(domain model):管理系統中業務對象的屬性、字段。 * 業務邏輯(business logic/rules):實現業務規則和業務邏輯。 * 工作流(work flow):實現多用戶、多環節之間的業務處理流程。 * 文件管理(file management):管理文檔,實現系統內部的文件傳遞。 * 報表/打印 (reporting/printing):實現數據打印,實現報表的定制和輸出。 * 門戶/信息發布 (portal solution):發布企業相關的信息、新聞,提供企業客戶的訪問入口。 * 通信(communication/messaging):系統內部的消息、通知;系統與外部角色(比如企業客戶)之間通過不同通信媒介(電話、網站、郵件等)的互動。 * 特定行業/領域模塊 (business modules):實現特定行業、流域相關的業務模塊。 以上諸方面中,除了前四項目前主要由應用服務器解決之外,其他的部分本身都是專門的軟件開發領域。框架的作用,在于確定上述每種因素的具體技術實現,并規定它們在系統中的組織方式和協作方式,從而給出完整的企業應用解決方案。 企業應用框架的特點首先是,當應用框架確定之后,系統的整個構架,也就是主體結構就已經固定。因此框架的選取往往是方案選型的首要問題。 其次,人們常常聽信“組件式開發”的一面之詞,認為系統搭建的過程類似于搭積木,好像是用膠水代碼(glue code)拼合現成的組件或模塊。其實采用框架開發時,系統的構建過程更類似于填空——系統骨架早已完成,開發者填寫特定的代碼,由系統來調用。《設計模式》中提到的“好萊塢原則(the Hollywood principle——Don't call us, we'll call you)”,非常符合我們談的這種情況。很多框架還允許下游廠商開發系統插件(plug-ins),以滿足特定需要——這是另一種形式的“填空”。 另外,對于實現具體應用系統的二次開發者來說,不少任務都無需通過編程實現。比如要給一個業務模型增添一個新字段,或是要設置一種新的工作流程,這些工作都可以通過簡單的圖形用戶界面(GUI)操作,或是修改部署描述符(DD),或是編寫腳本來完成。也就是說,相當多(而不是全部)的開發任務是通過聲明/配置的(declarative),而不是編程的(programmatic)的方式實現的。系統往往會在啟動或運行時載入相關的配置,據此實現特定的功能。
企業應用框架是菜場里的半成品。當我們面對要么自己下廚、要么去飯館吃飯的選擇時,我們往往會采取這種省時省力的折衷方案。但是選擇之所以為選擇,就因為其中肯定包含對收益和代價的權衡,都隱含著復雜的利弊關系(pros and cons)。下面我們也來檢討一下企業應用框架的情況: Pros: * 縮短開發周期 毫無疑問,采用框架的開發,要比一切從頭做起快速、高效得多。通過一般化(generalization)和重用(reuse)機制,框架能最大限度地加快一個特定應用系統的實現。 * 客戶化 如上所述,基于框架的系統有很多功能通過配置而不是編程實現,這樣也給用戶帶來了一定便利。比如,企業內部的IT人員經過一定培訓,就能夠自己完成一種新的工作流程的設置。這對于不斷變化的業務需求是一個很理想的解決方案。 * 不重新發明輪子 框架對于大量典型場景給出了最優的實踐。在具體開發時,與其無視前人的成果,重新構思答案,不如套用這些成熟、穩定的做法。這不僅能加快開發進度,更能夠提升系統的質量和健壯性。 * 可維護性/知識共享 完全通過委托開發完成的系統很難由其他廠商維護。框架往往是多個企業、大量開發者實踐的成果,因此能在一定程度上打破上述壁壘,增進系統的可維護性。當框架使用者形成社區之后,還能實現更高層次上的知識共享。 Cons: * 太多 半成品總有其代價。超市配好的一包菜里,老是又我們用不到的調料——但是我們卻不得不為之付費。同樣,為了達到一般性和普適性,框架總比緊湊、貼切的特定應用多出不少內容。二次開發完成后,企業獲得的只是一種特定的實現,卻要為所有的客戶化可能性付費,為所有用不上的功能付費。這是一個相當讓人尷尬的事實。 * 太少 框架總是一種限制。就像半成品菜限制了我們的烹調方法,框架也限制了我們實際應用的可能性。當框架本身設計的足夠普適時,我們不太會感到類似的限制。但是,情況往往正好相反——面對一個足夠特殊的需求,二次開發者總有一種沖破框架的渴望。最后的解決辦法,往往是狡計、妥協和框架補丁的結合體。 * 效率 上面說過,基于框架的系統中,具體功能經常是通過配置實現的。與硬編碼(hard-coded)的方式相比較,這雖然能提供很大的靈活性,但也往往犧牲了運行時的效率。 * 鎖定 一個采用特定框架的系統幾乎肯定被鎖定在這個廠商的產品上。換言之,框架意味著all or nothing式的態度,很難調和兩種不同的框架,各取所長,更難把應用系統從一個框架遷移到另一個——這往往要求系統的全部改寫。 * 學習曲線 一種框架也就是一種方言。要精通特定框架的開發,你要熟悉其中的所有的用法、思路和弱點。對于開發者,這也意味著比較陡峭的學習曲線。
3
上面談到的種種弊端,還屬于一般開發框架共有的缺陷。對于市面上流行的很多企業應用框架來說,更大的問題是框架產品自身的價格過高。我在別處也講過,企業應用系統項目往往不能靠運行安裝程序,再作簡單的設置就完成,而是一個復雜、漫長、不斷嘗試/修改的過程,或者說,更近似于一種服務而不是簡單的產品銷售。但是框架廠商的產品(或者說是半成品)價格過高,經常就蠶食了整個系統的大部分開發預算,使方案總價偏重于框架本身而不是后期開發。對于需求不甚符合原有框架,需要大量開發的項目,或是需求本身不夠清晰的項目,這都幾乎肯定會導致整個項目的失敗。
軟件工程宗師F. Brooks曾經表述過這樣一個道理:沒有銀彈(No Silver Bullet/NSB)。那意思是說,沒有一種萬應藥能夠戲劇性地提升軟件開發的效率和質量。最近的很多輿論好像是專門和這個經典論述抬杠,動不動就把一些特殊的解決方案奉為銀彈。對于企業應用開發而言,基于框架的開發模式是多種選擇中的一種。在不少情況下,這種選擇是不錯的,但同時應該留意隨之而來的風險,更不該以為,選定了框架就一定能保證項目成功。 很多企業應用項目的難點,在于客戶自身缺乏規范的企業管理制度和合格的計算機應用水平。客戶不具備成型的業務流程,也無法明晰表達需求,不知道怎樣的應用形式對自身業務更合理:這種需求不清、或是需求劇烈變更的困境是困擾大量企業應用開發項目的癥結。簡言之,企業應用項目的成敗經常是“業務”、“技術”、“管理”三種因素共同作用的結果,而單純引入框架,只能解決部分“技術問題”。如果過于樂觀地估計框架在其中的作用,甚至認為它能解決任何項目的任何問題,這對于本領域的各個環節(廠商、項目開發商、企業客戶)都只能起到消極作用。 我個人的建議是:在搭建企業應用系統時,針對應用情況不同、預算/時限不同、對系統指標要求不同,有多種替代方案可以從中選擇。當需求明確、固定,又有現成產品完全滿足需要時,或者當企業想要以極低預算消除某個業務瓶頸時,應該優先考慮現成產品;在需求明確、固定,但很難被現成產品完全覆蓋時,可以選擇應用框架,并由合格開發商完成實施;在需求不夠明確,或者預感到需求會發生劇烈變更時,采用開發源碼的應用框架,從而避免高昂的初期投資,并“軟化”框架帶來的種種限制,是另一種可供選擇的思路。還是那個比方,一頓飯怎么吃,究竟是下館子、買半成品或者全由自己動手,還要視具體情形而定——我也希望,每個企業都能吃上可口順心的應用大餐。
Extract Method
?????? 如果方法中含有過多特定的操作,方法太長,或者其中的某段代碼被多次使用,這時,可以用提煉方法重構將這部分代碼提取到單獨的方法中。在Eclipse中應用此重構方便快捷。
?????? 選中要提煉的代碼段,從重構菜單中選擇提煉方法項,或者使用快捷鍵Alt + Shift + M。
?????? 在提煉方法對話框中,輸入新方法的名字,選擇修飾詞,選擇是否讓新方法拋出運行時異常。在底部提供了新方法的預覽。
?
?
Extract Local Variable
?????? 使用一個變量來代替一個表達式有很多好處。如果表達式在多處被使用,這樣能夠提高性能,而且也提高了代碼的可讀性。要把一個表達式提煉為局部變量,選擇要提煉的表達式,從重構菜單中選擇提煉局部變量項,或者使用快捷鍵Alt + Shift + L。
?????? 在提煉局部變量對話框中輸入新變量的名字,選擇是否要替換所有的表達式,是否使此變量為final。在對話框的底部提供變量的預覽。
??????
Extract Constant
?????? 提煉常量與提煉局部變量很相似,唯一的區別是提煉常量重構可以選擇提煉出的常量的修飾詞,而且此常量將作為類的成員變量。
?
Introduce Parameter
?????? 介紹參數重構在方法中創建新的參數,然后用此新參數取代局部變量或者成員變量的實例。要是用此重構,選中方法中一個成員變量或局部變量的引用,然后從重構菜單中選擇介紹參數項。
?
?
Introduce Factory
?????? 工廠是用來創建新對象,返回新創建對象的方法。你可以選擇一個類的構造方法,從重構菜單中選擇介紹工廠項,應用此重構,為此類創建工廠方法。
??????
在介紹工廠對話框,輸入工廠方法的名字和需要工廠方法創建的對象的名字。選擇構造方法的修飾詞是否為私有。
?????? 點擊OK按鈕后,在指定的類中會出現此指定工廠方法。此方法創建一個當前類的實例,然后返回此實例。
?
Convert Local Variable to Field
?????? 轉換局部變量為成員變量重構,將方法內的變量聲明移動到方法所在類中,使該變量對整個類可見。選擇一個局部變量,從重構菜單中選擇轉換局部變量為成員變量項,隨后打開配置的對話框。
??????
?????? 在此對話框中,添入成員變量的名字,選擇修飾詞,選擇在哪里實例化此成員變量。隨后的聲明為靜態,聲明為final 選擇項是否可以使用,取決于實例化位置的選擇情況。
?
Encapsulate Field
?????? 要正確的實踐面向對象編程,應該將成員變量的修飾詞置為私有,提供相應的訪問器來訪問這些成員變量。但是這些操作很煩瑣。如果使用了封裝成員變量重構,則十分方便。選擇一個成員變量,從重構菜單中選擇封裝成員變量項。
?
?????? 在封裝局部變量對話框中,添入Getter, Setter方法的名字,選擇新方法在哪個方法后出現。選擇合適的修飾詞。應用了此重構會創建兩個新方法,將此成員變量的修飾詞置為私有,將對此成員變量的引用改變為對新方法的引用。
?
重構項列表:
?????? 下表從Eclipse幫助中提取,列出了各種重構支持的Java資源類型,對應的快捷鍵。
?
名字
|
可應用的Java元素
|
快捷鍵
|
Undo
|
在一次重構后可執行
|
Alt + Shift + Z
|
Redo
|
在一次撤銷重構后可執行
|
Alt + Shift + Y
|
Rename
|
對方法,成員變量,局部變量,方法參數,對象,類,包,源代碼目錄,工程可用。
|
Alt + Shift + R
|
Move
|
對方法,成員變量,局部變量,方法參數,對象,類,包,源代碼目錄,工程可用。
|
Alt + Shift + V
|
Change Method Signature
|
對方法可用。
|
Alt + Shift + C
|
Convert Anonymous Class to Nested
|
對匿名內部類可用。
|
?
|
Move Member Type to New File
|
對嵌套類可用。
|
?
|
Push Down
|
對同一個類中成員變量和方法可用。
|
?
|
Pull Up
|
對同一個類中成員變量和方法,嵌套類可用。
|
?
|
Extract Interface
|
對類可用。
|
?
|
Generalize Type
|
對對象的聲明可用。
|
?
|
Use Supertype Where Possible
|
對類可用。
|
?
|
Inline
|
對方法,靜態final類,局部變量可用。
|
Alt + Shift + I
?
|
Extract Method
|
對方法中的一段代碼可用。
|
Alt + Shift + M
?
|
Extract Local Variable
|
對選中的與局部變量相關的代碼可用。
|
Alt + Shift + L
?
|
Extract Constant
|
對靜態final類變量,選中的與靜態final類變量相關的代碼可用。
|
?
|
Introduce Parameter
|
對方法中對成員變量和局部變量的引用可用。
|
?
|
Introduce Factory
|
對構造方法可用。
|
?
|
Convert Local Variable to Field
|
對局部變量可用。
|
Alt + Shift + F
|
Encapsulate Field
|
對成員變量可用。
|
?
|
?????? 本文介紹了Eclipse提供的各種重構。這些重構易于使用,可以確保代碼重構更加方便安全。而且可以自動生成代碼以提高生產率。
??????
某些重構改變了某些類的結構,但沒有改變項目中其他類的結構,如下推,上移重構。這時,就要確保項目中所有對改變元素的引用都要被更新。這也是為什么要有一個好的測試套。同時,你也要更新測試套中的對改變元素的引用。所以說,重構和單元測試的有機結合對于軟件開發是多么的重要。
重構和單元測試是程序員的兩大法寶,他們的作用就像空氣和水對于人一樣,平凡,不起眼,但是意義深重。預善事,必先利器,本文就介紹怎樣在Eclipse中進行重構。
?
本文介紹了Eclipse支持的重構種類,它們的含義,以及怎樣重構。本文同時也可以作為學習重構知識的快速手冊。
?
什么是重構
重構是指在保持程序的全部功能的基礎上改變程序結構的過程。重構的類型有很多,如更改類名,改變方法名,或者提取代碼到方法中。每一次重構,都要執行一系列的步驟,這些步驟要保證代碼和原代碼相一致。
?
為什么重構很重要???
???????? 手工重構時,很容易在代碼中引入錯誤,例如拼寫錯誤或者漏掉了重構的某一步。為了防止引入錯誤,在每次重構前后,都要執行充分的測試。你可能會好奇重構是否是值得的。
重構的理由很多。你可能想要更新一段代碼很爛的程序。或者最初的設計隊伍都不在了,現在隊伍中每人了解這些程序。為了更新,你必須要重新設計構建程序來滿足你的需求。另一個原因是原來的設計無法使你將新的特性添加進去。為了添加進去,你要重構這些代碼。第三個原因是一個自動重構的工具可以為你自動生成代碼,例如Eclipse中的重構功能。使用重構,你可以在重寫盡量少的代碼和仍保持軟件功能的同時,使代碼的邏輯性更好。
?
?
測試
在重構時,測試是十分重要的。應為重構改變了代碼的結構,你要保證重構后代碼的功能沒有被改變。手工重構時,一個好的測試套是必須的。使用自動重構工具是,測試也是必要的,但不需要很頻繁,應為自動重構工具不會產生手工重構時的那些錯誤,如拼寫錯誤。
在Eclipse中可以使用JUnit方便的為程序創建測試代碼,具體方法不在本文描述。
?
?
Eclipse中的重構
JDT,Eclipse中的Java插件,能夠對Java項目,類,或成員進行多種類型的自動重構。可以采取多種方法快速的為Java項目中的某個元素進行重構。
為某些元素進行重構的前提是你必須選中他們。你可以在多個視圖中選擇這些元素,像大綱視圖或包瀏覽視圖。可以按住Ctrl或Shift鍵,在視圖中選擇多個元素。另外一種選擇的方法是使該元素的編輯區高亮顯示,或者把鼠標定位到源程序文件。在選中希望重構的元素后,可以從重構菜單的下拉項選擇重構,也可以從右鍵單擊后彈出菜單中選擇重構子菜單。同時,Eclipse還提供了重構的快捷鍵操作。
某些重構可以應用在任意元素上,有些則只能用在特定類型的元素上,如類或方法。在本文的最后的表格中,列出了重構能夠應用的元素類型,以及重構的快捷鍵。
在Eclipse中,所有的重構都能夠在正式執行之前預覽一下。在重構對話框中點擊“預覽”按鈕,可以查看所有將要被改變的地方。唯一沒有預覽按鈕的的重構是Pull Up,在它的重構向導中,到最后,預覽面板總會出現。可以將其中的個別變化反選掉,這樣這些改變就不會生效。
?
?
撤銷和重做
?????? 在重構菜單中有撤銷和重做項。他們和編輯菜單中的撤銷重做不同。即使重構改變了很多文件,編輯菜單中的撤銷重做只會更改當前文件。重構菜單中的撤銷和重做則會對一次重構的所有文件進行撤銷和重做操作。但是在使用時,它們有一定的限制。
重構后,無論重構改變了文件與否,如果任一個文件被另外改變而且保存了,你就無法撤銷或重做這個重構。假如一個文件在重構中被修改了,然后又被編輯了,但是還沒有保存,這時就會有錯誤信息提示,如果你想要撤銷或重做該重構,必須撤銷未保存的文件。
只要注意到以上的限制條件,你就可以隨心所欲的對重構進行撤銷或重做。你甚至能夠編譯,運行你的程序測試一下,然后再撤銷該重構,只要你沒有改變并保存任何文件。
?
Eclipse中的重構類型
?????? 如果你看一下Eclipse的重構菜單,可以看到四部分。第一部分是撤銷和重做。其他的三部分包含Eclipse提供的三種類型的重構。
第一種類型的重構改變代碼的物理結構,像Rename和Move。第二種是在類層次上改變代碼結構,例如Pull Up和Push Down。第三種是改變類內部的代碼,像Extract Method和Encapsulate Field。這三部分的重構列表如下。
?
類型1 物理結構
l???????? Rename
l???????? Move
l???????? Change Method signature
l???????? Convert Anonymous Class to Nested
l???????? Convert Member Type to New File
?
類型2 類層次結構
l???????? Push Down
l???????? Push Up
l???????? Extract Interface
l???????? Generalize Type (Eclipse 3)
l???????? User Supertype Where Possible
類型3 類內部結構
l???????? Inline
l???????? Extract Method
l???????? Extract Local Variable
l???????? Extract Constant
l???????? Introduce Parameter
l???????? Introduce Factory
l???????? Encapsulate Field
?
?
?
Rename:
?????? Rename用來改變一個Java元素的名字。雖然你可以手工改變Java文件Java元素的名字,但是這樣不能自動更新所有引用它們的文件或Java元素。你必須在項目中搜索文件然后手工替換這些引用。很可能你就會漏掉一個或者改錯一個。Rename 重構會智能的更新所有有此引用的地方。
?????? 有時候,Java元素的名字不是很明了,或者它的功能已經改變了。為了保持代碼的可讀性,該元素的名字也要更新。使用Rename重構,能夠十分快捷的更新元素的名字和所有引用它的地方。
?????? 要為一個Java元素改名,在包瀏覽視圖或大綱視圖選中該元素,從重構菜單中選擇Rename項,或者使用快捷鍵Alt+Shift+R。Rename對話框會出現。在這里添入新的名字,選擇是否更新該元素的引用。點擊預覽按鈕,會打開預覽窗口,在這里,你可以看到那些內容會被改變。點擊OK按鈕,重構結束。
?
Move
?????? Move和Rename很相似。它用來把元素從一個位置移動到另一個位置。它主要用來將類從一個包移動到另一個包。選中要移動的元素,從重構菜單中選擇Move,或者使用快捷鍵,Alt+Shift+V,在彈出窗口中選擇要移動的目的地。你仍然可以用預覽功能檢查一下有什么改變,也可以按OK按鈕直接讓其生效。
?
?
Change Method Signature
?????? 更改方法簽名能夠改變參數名,參數類型,參數順序,返回類型,以及方法的可見性。也可以添加,刪除參數。
?????? 要執行此重構,選擇要重構的方法,選中重構菜單的更改方法簽名項,會出現更改方法簽名對話框。
?
在此對話框中選擇方法的修飾詞,返回類型,參數。參數的添加,修改,移動,刪除可以通過右邊的按鈕控制。當添加新的參數時,會自動賦予默認值。凡是調用此方法的地方都會用此默認值作為參數輸入。
?????? 改變方法簽名可能在方法中導致問題,如果有問題,當你點擊預覽或OK時,會被標記出來。
??????
?
Move Members Type to New File
?????? 此重構將嵌套類轉為一個單獨類。將會創建一個新的Java文件包含此嵌套類。選中要重構的類,在重構菜單上選擇Move Member Type to New File項,在彈出的對話框中添入要創建的實例的名字。
?
??????
?
Push Down
?????? 此重構將算中的方法和成員從父類中移動到它的直接子類中,所有下推的方法都可選作為一個抽象方法留在父類中。下推重構對于重新構建項目設計十分有用。
????????
選擇若干方法或成員,從重構菜單中選擇下推項,彈出下推對話框。
????????
?????? 在此對話框中,可以分別選擇方法或成員,所有選中元素都會移動到當前類的子類中。當點擊Add Required按鈕時,所有已選擇元素所必需的元素也會自動選上,此行為并不能保證所有必須的元素都能自動選中,還是需要人工確認。當有方法被選中時,編輯按鈕就會可用,點擊編輯按鈕,彈出編輯對話框。在其中可以選擇為選中方法在當前類中遺留抽象方法,還是在當前類中刪除這些方法。雙擊一天選中的方法,也可以打開編輯對話框。在方法的Action列點擊,會出現一個下拉列表,可以在其中選擇遺留抽象方法還是在當前類中刪除方法。按回車鍵確認編輯結果。
?
?
Pull Up
?????? 上移與下推類似,也是在類之間移動方法和成員。上移將方法或成員從一個類移動到它的一個父類中。選中若干個方法或成員,在重構菜單中選擇上移項,上移向導馬上會出現。
?????? 在選擇目標類多選框中,列出了當前類繼承的所有父類。你只能將方法或成員移動到它們其中的一個里面。
?????? 如果在選中方法的Action列,被設置成在目標類中聲明抽象方法,那么在目標類的非抽象子類中創建必須的方法選項變為可選。當它選中時,目標類的所有子類,如果它們中沒有選中的方法,則會為它們創建選中的方法。
?????? 和在下推中一樣,選擇多個方法,點擊編輯按鈕,或者雙擊一個方法,都會打開編輯成員對話框。其中有兩個選項,上移和在目標類中聲明抽象方法。上移只是簡單的復制方法到到父類中,并提供選擇是否在當前類中刪除該方法。在目標類中聲明抽象方法會在父類中創建一個選中方法的抽象方法,如果父類不是抽象類則置為抽象類,最后選中方法留在當前類中。和在下推中一樣,也可以點擊Action列,可以在出現的下拉列表中選擇。
?????? 如果方法的Action列選為上移,在下一步的向導中,將會要求你選擇是否在當前類中刪除這些方法,選中的方法會在當前類中被刪除。
?????? 在向導的任意一步都可以按完成按鈕,結束重構操作,此時按照默認規則進行重構。
?
?
Extract Interface
?????? 提煉接口可以從一個存在的類中創建一個接口。你可以選擇在接口中包含著個類的那些方法。選中一個類,從重構菜單選擇提煉接口項,就可以打開提煉接口對話框。
?????? 這此對話框中添入接口的名字,選擇希望包含的方法,在這個列表里面只列出了公共方法。選中改變對類[當前類名]的應用為對接口的引用選擇框,將把所有對當前類的引用更新為對此接口的引用。
?
?
Generalize Type
?????? 泛化類型重構可以將一個聲明對象的類型改變為它的超類,選擇變量,參數,對象成員,方法返回類型,然后選擇重構菜單的泛化類型項。在打開的泛化類型對話框,選擇希望的新類型,然后點擊完成按鈕,結束重構。
?
?
Use Supertype Where Possible
?????? 使用超類會將對一個特定類型的引用改變為對它的超類的引用。選擇一個類,選中重構菜單的使用超類項,會打開使用超類對話框。選中希望的超類類型,點擊完成按鈕完成重構。重構后,instanceof 表達式也會做相應的替換。
?
?
?
Inline
?????? 內聯是用代碼或值來取代調用方法的地方,靜態final對象成員,或局部變量。比如說,如果你內聯一個方法調用,這個調用的地方就會被替換為該方法體。要內聯一個方法,靜態final對象成員,局部變量,選中這些元素,在重構菜單中選擇內聯項,或者使用快捷鍵Alt + Ctrl + I。在隨后打開的內聯對話框,你可以選擇是否要內聯所有的調用,或者是選擇的調用。如果選擇所有調用,你還可以選擇是否刪除聲明本身。
|