<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Java Votary

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      48 隨筆 :: 1 文章 :: 80 評論 :: 0 Trackbacks

    2005年12月15日 #

    ThreadLocal是什么

    早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優(yōu)美的多線程程序。

    ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地線程”。其實,ThreadLocal并不是一個Thread,而是Thread的局部變量,也許把它命名為ThreadLocalVariable更容易讓人理解一些。

    當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

    從線程的角度看,目標變量就象是線程的本地變量,這也是類名中“Local”所要表達的意思。

    線程局部變量并不是Java的新發(fā)明,很多語言(如IBM IBM XL FORTRAN)在語法層面就提供線程局部變量。在Java中沒有提供在語言級支持,而是變相地通過ThreadLocal的類提供支持。

    所以,在Java中編寫線程局部變量的代碼相對來說要笨拙一些,因此造成線程局部變量沒有在Java開發(fā)者中得到很好的普及。

    ThreadLocal的接口方法

    ThreadLocal類接口很簡單,只有4個方法,我們先來了解一下:

    • void set(Object value)

    設置當前線程的線程局部變量的值。

    • public Object get()

    該方法返回當前線程所對應的線程局部變量。

    • public void remove()

    將當前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。

    • protected Object initialValue()

    返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的缺省實現(xiàn)直接返回一個null。

    值得一提的是,在JDK5.0中,ThreadLocal已經(jīng)支持泛型,該類的類名已經(jīng)變?yōu)門hreadLocal<T>。API方法 也相應進行了調(diào)整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。

    ThreadLocal是如何做到為每一個線程維護變量的副本的呢?其實實現(xiàn)的思路很簡單:在ThreadLocal類中有一個Map,用于存儲每一個線程的變量副本,Map中元素的鍵為線程對象,而值對應線程的變量副本。我們自己就可以提供一個簡單的實現(xiàn)版本:

    代碼清單1 SimpleThreadLocal

    public class SimpleThreadLocal {

    private Map valueMap = Collections.synchronizedMap(new HashMap());

    public void set(Object newValue) {

    valueMap.put(Thread.currentThread(), newValue);①鍵為線程對象,值為本線程的變量副本

    }

    public Object get() {

    Thread currentThread = Thread.currentThread();

    Object o = valueMap.get(currentThread);②返回本線程對應的變量

    if (o == null && !valueMap.containsKey(currentThread)) {③如果在Map中不存在,放到Map

    中保存起來。

    o = initialValue();

    valueMap.put(currentThread, o);

    }

    return o;

    }

    public void remove() {

    valueMap.remove(Thread.currentThread());

    }

    public Object initialValue() {

    return null;

    }

    }

    雖然代碼清單9?3這個ThreadLocal實現(xiàn)版本顯得比較幼稚,但它和JDK所提供的ThreadLocal類在實現(xiàn)思路上是相近的。

    一個TheadLocal實例

    下面,我們通過一個具體的實例了解一下ThreadLocal的具體使用方法。

    代碼清單2 SequenceNumber

    package com.baobaotao.basic;

    public class SequenceNumber {

    通過匿名內(nèi)部類覆蓋ThreadLocalinitialValue()方法,指定初始值

    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){

    public Integer initialValue(){

    return 0;

    }

    };

    獲取下一個序列值

    public int getNextNum(){

    seqNum.set(seqNum.get()+1);

    return seqNum.get();

    }

    public static void main(String[] args)

    {

    SequenceNumber sn = new SequenceNumber();

    3個線程共享sn,各自產(chǎn)生序列號

    TestClient t1 = new TestClient(sn);

    TestClient t2 = new TestClient(sn);

    TestClient t3 = new TestClient(sn);

    t1.start();

    t2.start();

    t3.start();

    }

    private static class TestClient extends Thread

    {

    private SequenceNumber sn;

    public TestClient(SequenceNumber sn) {

    this.sn = sn;

    }

    public void run()

    {

    for (int i = 0; i < 3; i++) {④每個線程打出3個序列值

    System.out.println("thread["+Thread.currentThread().getName()+

    "] sn["+sn.getNextNum()+"]");

    }

    }

    }

    }

     

    通常我們通過匿名內(nèi)部類的方式定義ThreadLocal的子類,提供初始的變量值,如例子中①處所示。TestClient線程產(chǎn)生一組序列號, 在③處,我們生成3個TestClient,它們共享同一個SequenceNumber實例。運行以上代碼,在控制臺上輸出以下的結果:

    thread[Thread-2] sn[1]

    thread[Thread-0] sn[1]

    thread[Thread-1] sn[1]

    thread[Thread-2] sn[2]

    thread[Thread-0] sn[2]

    thread[Thread-1] sn[2]

    thread[Thread-2] sn[3]

    thread[Thread-0] sn[3]

    thread[Thread-1] sn[3]

    考察輸出的結果信息,我們發(fā)現(xiàn)每個線程所產(chǎn)生的序號雖然都共享同一個SequenceNumber實例,但它們并沒有發(fā)生相互干擾的情況,而是各自產(chǎn)生獨立的序列號,這是因為我們通過ThreadLocal為每一個線程提供了單獨的副本。

    Thread同步機制的比較

    ThreadLocal和線程同步機制相比有什么優(yōu)勢呢?ThreadLocal和線程同步機制都是為了解決多線程中相同變量的訪問沖突問題。

    在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候?qū)ψ兞窟M行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。

    而ThreadLocal則從另一個角度來解決多線程的并發(fā)訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線 程對數(shù)據(jù)的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編 寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。

    由于ThreadLocal中可以持有任何類型的對象,低版本JDK所提供的get()返回的是Object對象,需要強制類型轉換。但JDK 5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用,代碼清單 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。

    概括起來說,對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。

    Spring使用ThreadLocal解決線程安全問題

    我們知道在一般情況下,只有無狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用 域。就是因為Spring對一些Bean(如RequestContextHolder、 TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態(tài)采用 ThreadLocal進行處理,讓它們也成為線程安全的狀態(tài),因為有狀態(tài)的Bean就可以在多線程中共享了。

    一般的Web應用劃分為展現(xiàn)層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過接口向上層開放功能調(diào)用。在一般情況下,從接收請求到返回響應所經(jīng)過的所有程序調(diào)用都同屬于一個線程,如圖9?2所示:

    通通透透理解ThreadLocal

    1同一線程貫通三層

    這樣你就可以根據(jù)需要,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應的調(diào)用線程中,所有關聯(lián)的對象引用到的都是同一個變量。

    下面的實例能夠體現(xiàn)Spring對有狀態(tài)Bean的改造思路:

    代碼清單3 TopicDao:非線程安全

    public class TopicDao {

    private Connection conn;一個非線程安全的變量

    public void addTopic(){

    Statement stat = conn.createStatement();引用非線程安全變量

    }

    }

    由于①處的conn是成員變量,因為addTopic()方法是非線程安全的,必須在使用時創(chuàng)建一個新TopicDao實例(非singleton)。下面使用ThreadLocal對conn這個非線程安全的“狀態(tài)”進行改造:

    代碼清單4 TopicDao:線程安全

    import java.sql.Connection;

    import java.sql.Statement;

    public class TopicDao {

    ①使用ThreadLocal保存Connection變量

    private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();

    public static Connection getConnection(){

    ②如果connThreadLocal沒有本線程對應的Connection創(chuàng)建一個新的Connection,

    并將其保存到線程本地變量中。

    if (connThreadLocal.get() == null) {

    Connection conn = ConnectionManager.getConnection();

    connThreadLocal.set(conn);

    return conn;

    }else{

    return connThreadLocal.get();③直接返回線程本地變量

    }

    }

    public void addTopic() {

    ④從ThreadLocal中獲取線程對應的Connection

    Statement stat = getConnection().createStatement();

    }

    }

    不同的線程在使用TopicDao時,先判斷connThreadLocal.get()是否是null,如果是null,則說明當前線程還沒有對 應的Connection對象,這時創(chuàng)建一個Connection對象并添加到本地線程變量中;如果不為null,則說明當前的線程已經(jīng)擁有了 Connection對象,直接使用就可以了。這樣,就保證了不同的線程使用線程相關的Connection,而不會使用其它線程的 Connection。因此,這個TopicDao就可以做到singleton共享了。

    當然,這個例子本身很粗糙,將Connection的ThreadLocal直接放在DAO只能做到本DAO的多個方法共享Connection時 不發(fā)生線程安全問題,但無法和其它DAO共用同一個Connection,要做到同一事務多DAO共享同一Connection,必須在一個共同的外部類 使用ThreadLocal保存Connection。

    小結

    ThreadLocal是解決線程安全問題一個很好的思路,它通過為每個線程提供一個獨立的變量副本解決了變量并發(fā)訪問的沖突問題。在很多情況 下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結果程序擁有更高的并發(fā)性。

    posted @ 2009-07-01 12:15 Dion 閱讀(344) | 評論 (0)編輯 收藏

         摘要: 題記世界是事實的總體,而不是事物的總體。世界為諸事實所規(guī)定,為它們既是全部事實所規(guī)定。——路德維希·維特根斯坦,《邏輯哲學論》The answer, please?"請回答我..."The stern voice startles you. 一個嚴厲的聲音把你嚇個半死.You were dozing in Mrs. Rosencrantz's high school math class agai...  閱讀全文
    posted @ 2006-03-11 10:02 Dion 閱讀(2111) | 評論 (0)編輯 收藏

    前幾天跟著寫了一個簡單的例子.
    覺得Drools的配置也沒有什么.
    今天在運行house的例子的時候, 無論怎么樣, 總是異常: 沒有定義的SMF.
    顯然沒有找到我定義的drools.config文件.
    官方網(wǎng)站上是這樣寫地:
    String droolsConfigProp = System.getProperty( "drools.conf" );

    if ( droolsConfigProp != null )
    {
        loadConfig( droolsConfigProp );
    }

    ClassLoader cl = Thread.currentThread( ).getContextClassLoader( ); if ( cl == null )
    {
        cl = getClass( ).getClassLoader( );
    }

    Enumeration configUrls = cl.getResources( "META-INF/drools.conf" );

    if ( !configUrls.hasMoreElements( ) )
    {
        cl = getClass( ).getClassLoader( );
        configUrls = cl.getResources( "META-INF/drools.conf" );
    }

    if ( !configUrls.hasMoreElements( ) )
    {
        cl = ClassLoader.getSystemClassLoader( );
        configUrls = cl.getResources( "META-INF/drools.conf" );
    }

    this.classLoader = cl;
    while ( configUrls.hasMoreElements( ) )
    {
        URL configUrl = (URL) configUrls.nextElement( );
        loadConfig( configUrl );
    }

    好像每一個旮旯里面都找了, 為什么沒有找到我的呢?
    System.getProperty指向的位置并不一定和loadFromUrl位置一樣.呵呵.
    posted @ 2006-03-11 10:00 Dion 閱讀(1640) | 評論 (0)編輯 收藏

    內(nèi)容提要
           在本文的第一部分,我將討論規(guī)則引擎如何幫助你從軟件的應用邏輯中分離出商業(yè)規(guī)則邏輯,以實現(xiàn)商業(yè)應用的靈活性。另外,我還將介紹JSR-94規(guī)則引擎 API,及其開源實現(xiàn)Drools項目,它是這一新技術的先驅(qū)。在第二部分,我們將介紹一個規(guī)則引擎例子,并深入地研究Drools引擎及其JSR-94 擴展的復雜性。

    為什么使用規(guī)則引擎
           商業(yè)世界充滿了關于變化的陳詞濫調(diào),如任何事物都會改變,唯一不變的是變化等等。而在技術領域里,情況正好相反。我們?nèi)匀辉谠噲D解決30年前軟件業(yè)中同樣 的一堆問題--也許比30年前還要多的問題。在過去的十年,IT從業(yè)人員淹沒在軟件方法學的大量文獻中,如快速軟件開發(fā),極限編程,敏捷軟件開發(fā)等,它們 無一例外地強調(diào)靈活和變化的重要性。
           但商業(yè)通常比開發(fā)團隊所依賴的軟件過程和技術改變得更加迅速。當商業(yè)策劃人員試圖重整IT部門,以支持新的業(yè)務轉型時,仍然覺得很費勁。

    Lost in Translation
           雖然IT團隊反應迅速,但他們通常帶來"電話效應"――IT給商業(yè)計劃的執(zhí)行帶來的阻力和它帶來的利益一樣多。不幸的是,在開發(fā)團隊完全理解商業(yè)決策規(guī)則 并實現(xiàn)之前,規(guī)則已經(jīng)改變了。在軟件進入市場前,它已經(jīng)過時了,需要進行重構以滿足新的業(yè)務需求。如果你是一個開發(fā)人員,你會知道我在說什么。再也沒有比 在需求變動的情況下構造軟件讓開發(fā)人員更沮喪的事情了。作為軟件開發(fā)人員,你必須比業(yè)務人員更了解業(yè)務,有時還要了解更多。
           試想一下你是一位商業(yè)決策者。假如公司的成功依賴于你對于市場趨勢敏銳的洞察力,它常常幫助你領先于競爭者利用變化的市場環(huán)境獲利。每天你都會得到更多更 好的市場信息,但并不要緊。完成新產(chǎn)品開發(fā)可能需要6-9個月,在此期間,對于市場大膽和敏銳的洞察和信息優(yōu)勢可能已經(jīng)浪費了。而且,當產(chǎn)品發(fā)布時,有這 樣幾種可能:產(chǎn)品沒有什么吸引人的特性,預算超支,過了產(chǎn)品的最佳發(fā)布期限,或三者兼而有之。
           情況可能還會更糟,在完成產(chǎn)品開發(fā)時,市場環(huán)境和規(guī)劃產(chǎn)品開發(fā)時相比,已經(jīng)發(fā)生了根本變化。現(xiàn)在你必須要遵守新的規(guī)則,你已經(jīng)喪失了你的邊際優(yōu)勢,而且設 計軟件的五人中的三人已經(jīng)離開了公司。你必須給接手的新人重新講解復雜的業(yè)務。如果事情不順利,你可能發(fā)現(xiàn)自己要對付一個缺少文檔,并且你完全不了解的遺 留應用。
           你的戰(zhàn)略在哪出現(xiàn)了問題?你在哪里應該可以做到更好?最近的輕量級軟件過程,如極限編程,敏捷軟件開發(fā)等都在強調(diào)自動單元測試和軟件功能優(yōu)先級的重要性。 除此之外,還有其他的原則,你的開發(fā)團隊可能也很熟悉,這些原則可以幫助他們對需求的變動作出迅速反應并縮短項目的開發(fā)周期。這些原則的大多數(shù),如系統(tǒng)分 解,多年前就已經(jīng)出現(xiàn),并得到了Java平臺的支持(如JMX等),還有如面向?qū)ο蠛徒巧#呀?jīng)內(nèi)建在Java語言中。
           但Java仍然是一門相當年輕的語言,而且Java平臺遠遠還沒有完備。當前在Java社區(qū),一個引人注目的新技術是,分離商業(yè)決策者的商業(yè)決策邏輯和應 用開發(fā)者的技術決策,并把這些商業(yè)決策放在中心數(shù)據(jù)庫,讓它們能在運行時(即商務時間)可以動態(tài)地管理和修改。這是一個你值得考慮的策略。
           為什么你的開發(fā)團隊不得不象商業(yè)經(jīng)理人一樣,在代碼中包含復雜微妙的商業(yè)決策邏輯呢?你怎樣才能向他們解釋決策推理的微妙之處呢?你這樣做是否謹慎呢?可 能不是。象bottom line一樣,某些東西在解釋的過程中丟失了。為什么要冒這樣的風險,讓應用代碼或測試代碼錯誤地表達你的商業(yè)決策邏輯呢?如果這樣做的話,你怎樣檢查它 們的正確性呢――難道你自己想學習如何編程和編寫測試代碼,或者你的客戶會為你測試軟件?你一方面要應付市場,一方面要應付軟件代碼,這實在太困難了。
           如果能將這些商業(yè)決策規(guī)則集中地放在一個地方,以一種你可以理解的格式定義,讓你可以直接管理,而不是散落在代碼的各個角落,那該有多好。如果你能把商業(yè) 決策規(guī)則獨立于你的軟件代碼,讓開發(fā)團隊作出技術決策,你將會獲得更多好處。你的項目開發(fā)周期會更短,軟件對于變動的需求更靈活。

    規(guī)則引擎標準Java API
           2003年11月,Java社區(qū)通過了Java Rule Engine API規(guī)范(JSR-94)的最后草案。這個新的API讓開發(fā)人員在運行時訪問和執(zhí)行規(guī)則有了統(tǒng)一的標準方式。隨著新規(guī)范產(chǎn)品實現(xiàn)的成熟和推向市場,開發(fā) 團隊將可以從應用代碼中抽取出商業(yè)決策邏輯。
           這就需要新一代的管理工具,幫助商務經(jīng)理人可以定義和細化軟件系統(tǒng)的行為。不必通過開發(fā)過程來修改應用,并假定可以得到正確的結果,經(jīng)理人將可以隨時根據(jù)需要修改決策規(guī)則,并進行測試。
           但這將需要開發(fā)人員在設計系統(tǒng)時作出某些改變,并可以得到合適的開發(fā)工具。

    分離商務和技術的關注點
           這是一個非常簡單的例子,從經(jīng)理人的角度,說明如何分離商務和技術的關注點。
           你管理著一個反向投資基金。你公司計算機系統(tǒng)的一部分用于分析股票價格,收益和每股凈資產(chǎn),并在需要時向你提出預警。這個計算機系統(tǒng)的工作是,識別出PE比率比市場平均值低的股票,并標記出來以便進一步的檢查。
           你的IT部門擁有一大堆數(shù)據(jù),并開發(fā)了一系列你可以在規(guī)則中引用的簡單數(shù)據(jù)對象。現(xiàn)在,為簡單起見,假設你是一名受過良好教育的,了解技術的管理人,你了解XML的基本知識,可以讓你編寫和修改簡單的XML規(guī)則文件。
           你的第一個規(guī)則是,給道瓊斯所有的股票估值,并剔除P/E比率大于10的股票(這有點過分簡化,但這里只作為一個例子)。保留下來的股票用來生產(chǎn)一系列報表。對于這個簡單的例子,你的規(guī)則文件看起來如下(我們將會過頭來討論這個文件的結構):

    <stock:overvalued>
        <stock:index> DJIA </stock:index>
        <stock:pe> over 10.0 </stock:pe>
    </stock:overvalued>

           一個月后,你接到一家巴西分析師公司的電話,雇傭你的公司生成一系列巴西股市的報表,但他們有更嚴格的標準。而目前在巴西,P/E比率市場平均值是個位 數(shù),因此你用來評估被市場低股票的閾值需要改變。除了較低的P/E比率,你的新客戶還要求以Price-to-Book比率作為參考標準。
           你啟動規(guī)則編輯器,并修改規(guī)則以匹配新的評估條件。現(xiàn)在,規(guī)則引擎剔除巴西股市中P/E比率大于6.5,以及Price to Book 比率小于等于1的股票。完成規(guī)則文件修改后,看起來如下:

    <stock:overvalued>
        <stock:index> Brazil </stock:index>
        <stock:pe> over 6.5 </stock:pe>
        <stock:pb> over 1.0 </stock:pb>
    </stock:overvalued>

           你無需為此向開發(fā)團隊作任何解釋。你無需等待他們開發(fā)或測試程序。如果你的規(guī)則引擎的語義足夠強大,讓你描述工作數(shù)據(jù),你可以隨時按需修改商業(yè)規(guī)則。
           如果限制因素是規(guī)則的定義語言和數(shù)據(jù)模型,你可以確信這兩者將會標準化,并出現(xiàn)先進的編輯器和工具,以簡化規(guī)則的定義,保存和維護。
           現(xiàn)在,我希望你已經(jīng)清楚以下的原則:在這個例子中,哪只股票是否被選擇是一個商務決策,而不是技術決策。決定將哪只股票交給你的分析師是經(jīng)理人的邏輯 ――"logic of the bottom line"。經(jīng)理人作出這些決策,并可以按需定制應用。這些規(guī)則因此變成了一種控制界面,一種新的商業(yè)系統(tǒng)用戶界面。

    使用Rule開發(fā)
           如果在這個應用場景中,你是一個開發(fā)人員,你的工作會稍微輕松一些。一旦你擁有了一種用于分析股票的規(guī)則語言,你可以取出數(shù)據(jù)對象并交給規(guī)則引擎執(zhí)行。我們將會到規(guī)則語言的討論,但現(xiàn)在我們繼續(xù)剛才的例子。
           你的系統(tǒng)將一系列的stock bean輸入規(guī)則引擎。當規(guī)則執(zhí)行后,你可以選出符合條件的股票并可以對它們作進一步處理。也許是把它們輸入報表生成系統(tǒng)。分析師使用這些報表幫助他們分 析股市。同時,老板也可能讓你使用新的技術分析工具,并用Dow理論預測股市的底部和頂部。
           規(guī)則引擎可以讓你的系統(tǒng)變得更簡單,因為你無需在代碼中編寫商務邏輯,如怎樣選擇股票,選擇股票過程中奇怪的條件組合等。這些邏輯不再進入你的代碼。你將可以專注于數(shù)據(jù)模型。
           現(xiàn)在可以這么認為,通過從應用代碼中剝離出易變的商業(yè)邏輯,你的效率會更高。但凡是總有例外――簡單應用可能并不能從規(guī)則系統(tǒng)中獲益。但如果你開發(fā)一個大型系統(tǒng),有很多易變的商業(yè)邏輯,你可以考慮在應用中集成規(guī)則引擎。
           除了從應用代碼中剝離出商業(yè)決策邏輯外,規(guī)則引擎還有其他用處。有時候你需要應用成百上千的規(guī)則進行決策,并且有上千個對象和這些規(guī)則一起使用。很難想象 有什么先進的人工智能引擎可以處理這種情況。遇到這種情況,你需要一個極快的決策算法或是大型機。大型機并不便宜,但你可以非常便宜的得到效率和可伸縮性 最好的算法。

    Bob McWhirter的Drools項目
           現(xiàn)在,我要介紹Drools項目,Charles Forgy Rete算法的一個增強的Java語言實現(xiàn)。Drools是一個Bob McWhirter開發(fā)的開源項目,放在The Codehaus上。在我寫這篇文章時,Drools發(fā)表了2.0-beata-14版。在CVS中,已完整地實現(xiàn)了JSR94 Rule Engine API并提供了單元測試代碼。
           Rete算法是Charles Forgy在1979年發(fā)明的,是目前用于生產(chǎn)系統(tǒng)的效率最高的算法(除了私有的Rete II)。Rete是唯一的,效率與執(zhí)行規(guī)則數(shù)目無關的決策支持算法。For the uninitiated, that means it can scale to incorporate and execute hundreds of thousands of rules in a manner which is an order of magnitude more efficient then the next best algorithm。Rete應用于生產(chǎn)系統(tǒng)已經(jīng)有很多年了,但在Java開源軟件中并沒有得到廣泛應用(討論Rete算法的文檔參見http://herzberg.ca.sandia.gov/jess/docs/61/rete.html。)。
           除了應用了Rete核心算法,開源軟件License和100%的Java實現(xiàn)之外,Drools還提供了很多有用的特性。其中包括實現(xiàn)了JSR94 API和創(chuàng)新的規(guī)則語義系統(tǒng),這個語義系統(tǒng)可用來編寫描述規(guī)則的語言。目前,Drools提供了三種語義模塊――Python模塊,Java模塊和 Groovy模塊。本文余下部分集中討論JSR94 API,我將在第二篇文章中討論語義系統(tǒng)。
           作為使用javax.rules API的開發(fā)人員,你的目標是構造一個RuleExecutionSet對象,并在運行時通過它獲得一個RuleSession對象。為了簡化這個過程, 我編寫了一個規(guī)則引擎API的fa?ade,可以用來解釋代表Drools的DRL文件的InputStream,并構造一個 RuleExecutionSet對象。
           在上面提到了Drools的三種語義模塊,我接下來使用它們重新編寫上面的例子XML規(guī)則文件。這個例子中我選擇Java模塊。使用Java模塊重新編寫的規(guī)則文件如下:

    <rule-set name="StockFlagger"
          xmlns="http://drools.org/rules"
          xmlns:java="http://drools.org/semantics/java">
      <rule name="FlagAsUndervalued">
        <parameter identifier="stock">
          <java:class>org.codehaus.drools.example.Stock</java:class>
        </parameter>
        <java:condition>stock.getIndexName().equals("DJIA");</java:condition>
        <java:condition>stock.getPE() > 10 </java:condition>
        <java:consequence>
          removeObject(stock);   ( 譯注:應該是retractObject(stock) )
        </java:consequence>
      </rule>
    </rule-set>

           現(xiàn)在的規(guī)則文件并沒有上面的簡潔明了。別擔心,我們將在下一篇文章討論語義模塊。現(xiàn)在,請注意觀察XML文件的結構。其中一個rule-set元素包含了 一個或多個rule元素,rule元素又包含了parameter,condition和consequence元素。Condition和 consequence元素包含的內(nèi)容和Java很象。注意,在這些元素中,有些事你可以做,有些事你不能做。目前,Drools使用 BeanShell2.0b1作為它的Java解釋器。我在這里并不想詳細的討論DRL文件和Java語義模塊的語法。我們的目標是解釋如何使用 Drools的JSR94 API。
           在Drools項目CVS的drools-jsr94模塊中,單元測試代碼包含了一個ExampleRuleEngineFacade對象,它基于 Brian Topping的Dentaku項目。這個fa?ade對象通過javax.rules API,創(chuàng)建了供RuleExecutionSet和RuleSession使用的一系列對象。它并沒有完全包括了Drools引擎API的所有特性和細 微差別,但可以作為新手使用API的一個簡單例子。
          下面的代碼片斷顯示如何使用規(guī)則引擎的facade構造一個RuleExecutionSet對象,并通過它獲得一個RuleSession對象。
     
    import java.io.InputStream;
    import javax.rules.*;
    import org.drools.jsr94.rules.ExampleRuleEngineFacade;
    public class Example {
        private ExampleRuleEngineFacade engine;
        private StatelessRuleSession statelessSession;
        /* place the rule file in the same package as this class */
        private String bindUri = "myRuleFile.drl"
        public Example() {
            /* get your engine facade */
            engine = new ExampleRuleEngineFacade();
            /* get your input stream */
            InputStream inputStream =
                    Example.class.getResourceAsStream(bindUri);
            /* build a RuleExecutionSet to the engine */
            engine.addRuleExecutionSet(bindUri, inputStream);
            /* don't forget to close your InputStream! */
            inputStream.close();
            /* get your runtime session */
            this.statelessSession = engine.getStatelessRuleSession(bindUri);
        }
        ...
    }

           在以上的例子代碼中,你需要處理InputStream的IOException例外,這里為了簡單起見省略了。你要做的只是構建InputStream 對象,并把它輸入ExampleRuleEngineFacade,用來創(chuàng)建一個RuleExecutionSet對象。然后,你可以得到一個 StatelessRuleSession,并用它來執(zhí)行所有的規(guī)則。使用StatelessRuleSession相對簡單。我們可以給上面的類添加一 個方法,用來對一個對象列表執(zhí)行規(guī)則:

    public List getUndervalued(List stocks) {
        return statelessSession.executeRules(stocks);
    }

           該方法輸入一個stock對象列表給規(guī)則引擎,然后使用規(guī)則評估輸入的股票對象,并剔除那些不符合價值低估標準的股票。它是個簡單的例子,但足以說明問題。
           在ExampleRuleEngineFacade類中,代碼會稍微有些復雜。ExampleRuleEngineFacade類創(chuàng)建了一個 RuleServiceProvider對象,并用它創(chuàng)建RuleAdministrator,RuleExecutionSetProvider和 RuleRuntime對象。RuleExecutionSetProvider負責解釋InputStream,并創(chuàng)建一個 RuleExecutionSet對象。RuleRuntime對象用來得到一個session,RuleAdministrator用來管理所有的對 象。在往下是Drools核心API,它的核心是Rete算法實現(xiàn)。我在這里不打算詳細討論,但你可以看看 ExampleRuleEngineFacade的代碼。
           現(xiàn)在你已經(jīng)看到了在商業(yè)和科研方面使用規(guī)則引擎的一些例子,并對Drools項目有了基本的了解。在下一篇文章里,我將討論DRL文件的結構和Java語 義模塊,讓你可以編寫自己的DRL文件。還將向你解釋如何編寫你自己的語義模塊,討論salience和working memory的概念。

    資源
    · Drools Project
    · JSR-94 Specification
     
    作者
          N. Alex Rupp is a freelance software architect and developer from Minneapolis, and the current JSR94 Lead for the Drools project.
    posted @ 2006-03-11 10:00 Dion 閱讀(1750) | 評論 (0)編輯 收藏

    一般情況下, 只顯式引用:

    • drools-all-2.0.jar
    • antlr-2.7.5.jar
    • xercesImpl-2.6.2.jar

    就可以了.當然ClassPath下也要用一些其他的jar.
    下載位置: http://dist.codehaus.org/drools/distributions/drools-2.0-bin-withdeps.zip

    如果, 在DRL文件中定義了Java Function, 這時候就要顯式的引用:

    • janino-2.3.2.jar

    這時候, 引擎是需要janino把DRL中的java function描述轉換成可執(zhí)行的二進制代碼(?)的.

    posted @ 2006-03-11 09:59 Dion 閱讀(962) | 評論 (0)編輯 收藏

    Drools and Mandarax

    兩個項目做了兩件不同的事情: 一個是Forward Chaining,另一個是 backward chaining. Drools 是forward chaining的,  意味著 它對assert的對象反應, 事件驅(qū)動的. Mandarax 是 backward chaining的, 像 prologue一樣, 你問它問題, 它試圖給你它知道的答案. 舉例來說, 在使用Drools的時候, 你可能會先assert 給它今天的日期, 如果它發(fā)現(xiàn)有匹配的規(guī)則的手,它會用事件的方式通知你"今天是你的生日". 在 backward chaining 的系統(tǒng), 你可能先問 "今天是我的生日嘛?" 系統(tǒng)會搜索它知道的, 然后告訴你答案.
    For an excellent explanation of forward and backward chaining read Charles Forgey's recent articles at http://rulespower.com/ - Forward and Backward Chaining:
    Parts 1, 2 and 3.
    posted @ 2006-03-11 09:58 Dion 閱讀(1173) | 評論 (0)編輯 收藏

    Open Source Rule Engines Written In Java

    • Drools The drools engine uses a modified form of the Rete algorithm called the Rete-OO algorithm. Internally it operates using the same concepts and methods as Forgy's original but adds some node types required for seemless integration with an object-oriented language.
    • OFBiz Rule Engine Backward chaining is supported. Original code base from "Building Parsers in Java" by Steven John Metsker.
    • Mandarax Based on backward reasoning. The easy integration of all kinds of data sources. E.g., database records can be easily integrated as sets of facts and reflection is used in order to integrate functionality available in the object model.
    • Algernon Efficient and concise KB traversal and retrieval. Straightforward access to ontology classes and instances. Supports both forward and backward chaining.
    • TyRuBa TyRuBa supports higher order logic programming: variables and compound terms are allowed everywhere in queries and rules, also in the position of a functor- or predicate-name. TyRuBa speeds up execution by making specialized copies of the rule-base for each query in the program. It does so incrementally while executing a logic program and builds an index for fast access to rules and facts in the rule base, tuned to the program that is running. The indexing techniques works also for higher-order logic. TyRuBa does 'tabling' of query results.
    • JTP Java Theorem Prover is based on a very simple and general reasoning architecture. The modular character of the architecture makes it easy to extend the system by adding new reasoning modules (reasoners), or by customizing or rearranging existing ones.
    • JEOPS JEOPS adds forward chaining, first-order production rules to Java through a set of classes designed to provide this language with some kind of declarative programming.
    • InfoSapient Semantics of business rules expressed using fuzzy logic.
    • JShop Simple Hierarchical Ordered Planner (SHOP) written in Java.
    • RDFExpert RDF-driven expert system shell. The RDFExpert software uses Brian McBride's JENA API and parser. A simple expert system shell that uses RDF for all of its input: knowledge base, inference rules and elements of the resolution strategy employed. It supports forward and backward chaining.
    • Jena 2 - Jena is a Java framework for writing Semantic Web applications. Jena2 has a reasoner subsystem which includes a generic rule based inference engine together with configured rule sets for RDFS and for the OWL/Lite subset of OWL Full. These reasoners can be used to construct inference models which show the RDF statements entailed by the data being reasoned over. The subsystem is designed to be extensible so that it should be possible to plug a range of external reasoners into Jena, though worked examples of doing so are left to a future release.
    • JLisa - JLisa is a powerful framework for building business rules accessible to Java and it is compatible with JSR-94. JLisa is more powerful than Clips because it has the expanded benefit of having all the features from common lisp available. These features are essential for multi-paradigm software development
    • Euler - Euler is a backward-chaining reasoner enhanced with Euler path detection and will tell you whether a given set of facts and rules supports a given conclusion. Things are described in N3.
    • JLog - JLog is an implementation of a Prolog interpreter, written in Java. It includes built-in source editor, query panels, online help, animation primitives, and a GUI debugger.
    • Pellet OWL Reasoner - Pellet is an open-source Java based OWL DL reasoner. It can be used in conjunction with either Jena or OWL API libraries. Pellet API provides functionalities to see the species validation, check consistency of ontologies, classify the taxonomy, check entailments and answer a subset of RDQL queries (known as ABox queries in DL terminology). Pellet is an OWL DL reasoner based on the tableaux algorithms developed for expressive Description Logics.
    • Prova - Prova is derived from Mandarax Java-based inference system developed by Jens Dietrich. Prova extends Mandarax by providing a proper language syntax, native syntax integration with Java, and agent messaging and reaction rules. The development of this language was supported by the grant provided within the EU project GeneStream. In the project, the language is used as a rules-based backbone for distributed web applications in biomedical data integration.
    posted @ 2006-03-11 09:58 Dion 閱讀(1342) | 評論 (0)編輯 收藏

    始終會用上的Common BeanUtils

    Beanutils用了魔術般的反射技術,實現(xiàn)了很多夸張有用的功能,都是C/C++時代不敢想的。無論誰的項目,始終一天都會用得上它。我算是后知后覺了,第一回看到它的時候居然錯過。

    1.屬性的動態(tài)getter、setter

    在這框架滿天飛的年代,不能事事都保證執(zhí)行getter,setter函數(shù)了,有時候?qū)傩允且鶕?jù)名字動態(tài)取得的,就像這樣:  
    BeanUtils.getProperty(myBean,"code");
    而Common BeanUtils的更強功能在于可以直接訪問內(nèi)嵌對象的屬性,只要使用點號分隔。
    BeanUtils.getProperty(orderBean, "address.city");
    相比之下其他類庫的BeanUtils通常都很簡單,不能訪問內(nèi)嵌的對象,所以有時要用Commons BeanUtils來替換它們。

    BeanUtils還支持List和Map類型的屬性,如下面的語法即可取得Order的顧客列表中第一個顧客的名字
    BeanUtils.getProperty(orderBean, "customers[1].name");
    其中BeanUtils會使用ConvertUtils類把字符串轉為Bean屬性的真正類型,方便從HttpServletRequest等對象中提取bean,或者把bean輸出到頁面。
    而PropertyUtils就會原色的保留Bean原來的類型。

    2.BeanCompartor 動態(tài)排序

    還是通過反射,動態(tài)設定Bean按照哪個屬性來排序,而不再需要在實現(xiàn)bean的Compare接口進行復雜的條件判斷。
    List peoples = ...; // Person對象的列表
    Collections.sort(peoples, new BeanComparator("age"));

    如果要支持多個屬性的復合排序,如"Order By lastName,firstName"

    ArrayList sortFields = new ArrayList();
    sortFields.add(new BeanComparator("lastName"));
    sortFields.add(new BeanComparator("firstName"));
    ComparatorChain multiSort = new ComparatorChain(sortFields);
    Collections.sort(rows,multiSort);

    其中ComparatorChain屬于jakata commons-collections包。
    如果age屬性不是普通類型,構造函數(shù)需要再傳入一個comparator對象為age變量排序。
    另外, BeanCompartor本身的ComparebleComparator, 遇到屬性為null就會拋出異常, 也不能設定升序還是降序。這個時候又要借助commons-collections包的ComparatorUtils.

       Comparator mycmp = ComparableComparator.getInstance();
       mycmp = ComparatorUtils.nullLowComparator(mycmp);  //允許null
       mycmp = ComparatorUtils.reversedComparator(mycmp); //逆序
       Comparator cmp = new BeanComparator(sortColumn, mycmp);
    3.Converter 把Request或ResultSet中的字符串綁定到對象的屬性

       經(jīng)常要從request,resultSet等對象取出值來賦入bean中,如果不用MVC框架的綁定功能的話,下面的代碼誰都寫膩了。

       String a = request.getParameter("a");
    bean.setA(a);
    String b = ....
    bean.setB(b);
    ......

    不妨寫一個Binder自動綁定所有屬性:

        MyBean bean = ...;
    HashMap map = new HashMap();
    Enumeration names = request.getParameterNames();
    while (names.hasMoreElements())
    {
    String name = (String) names.nextElement();
    map.put(name, request.getParameterValues(name));
    }
    BeanUtils.populate(bean, map);

        其中BeanUtils的populate方法或者getProperty,setProperty方法其實都會調(diào)用convert進行轉換。
         但Converter只支持一些基本的類型,甚至連java.util.Date類型也不支持。而且它比較笨的一個地方是當遇到不認識的類型時,居然會拋 出異常來。 對于Date類型,我參考它的sqldate類型實現(xiàn)了一個Converter,而且添加了一個設置日期格式的函數(shù)。
    要把這個Converter注冊,需要如下語句:

        ConvertUtilsBean convertUtils = new ConvertUtilsBean();
       DateConverter dateConverter = new DateConverter();
       convertUtils.register(dateConverter,Date.class);



    //因為要注冊converter,所以不能再使用BeanUtils的靜態(tài)方法了,必須創(chuàng)建BeanUtilsBean實例
    BeanUtilsBean beanUtils = new BeanUtilsBean(convertUtils,new PropertyUtilsBean());
    beanUtils.setProperty(bean, name, value);
    4 其他功能
    4.1 ConstructorUtils,動態(tài)創(chuàng)建對象
         public static Object invokeConstructor(Class klass, Object arg)
    4.2 MethodUtils,動態(tài)調(diào)用方法
        MethodUtils.invokeMethod(bean, methodName, parameter);

    4.3 PropertyUtils,當屬性為Collection,Map時的動態(tài)讀取:
    Collection: 提供index
       BeanUtils.getIndexedProperty(orderBean,"items",1);
    或者
      BeanUtils.getIndexedProperty(orderBean,"items[1]");
    Map: 提供Key Value
      BeanUtils.getMappedProperty(orderBean, "items","111");//key-value goods_no=111 
    或者
      BeanUtils.getMappedProperty(orderBean, "items(111)") 

    4.4 PropertyUtils,直接獲取屬性的Class類型
         public static Class getPropertyType(Object bean, String name)
    4.5 動態(tài)Bean 用DynaBean減除不必要的VO和FormBean 
    posted @ 2006-01-15 20:20 Dion 閱讀(5093) | 評論 (2)編輯 收藏

         摘要: Migrate apps from Internet Explorer to MozillaHow to make Internet Explorer-specific Web applications work in Mozilla-based browsersDocument options Print this page'); //--> Print this page E-ma...  閱讀全文
    posted @ 2006-01-04 19:18 Dion 閱讀(1490) | 評論 (1)編輯 收藏

    以下以 IE 代替 Internet Explorer,以 MF 代替 Mozzila Firefox

    1. document.form.item 問題
        (1)現(xiàn)有問題:
            現(xiàn)有代碼中存在許多 document.formName.item("itemName") 這樣的語句,不能在 MF 下運行
        (2)解決方法:
            改用 document.formName.elements["elementName"]
        (3)其它
            參見 2

    2. 集合類對象問題
        (1)現(xiàn)有問題:
            現(xiàn)有代碼中許多集合類對象取用時使用 (),IE 能接受,MF 不能。
        (2)解決方法:
            改用 [] 作為下標運算。如:document.forms("formName") 改為 document.forms["formName"]。
            又如:document.getElementsByName("inputName")(1) 改為 document.getElementsByName("inputName")[1]
        (3)其它

    3. window.event
        (1)現(xiàn)有問題:
            使用 window.event 無法在 MF 上運行
        (2)解決方法:
            MF 的 event 只能在事件發(fā)生的現(xiàn)場使用,此問題暫無法解決。可以這樣變通:
            原代碼(可在IE中運行):
                <input type="button" name="someButton" value="提交" onclick="javascript:gotoSubmit()"/>
                ...
                <script language="javascript">
                    function gotoSubmit() {
                        ...
                        alert(window.event);    // use window.event
                        ...
                    }
                </script>

            新代碼(可在IE和MF中運行):
                <input type="button" name="someButton" value="提交" onclick="javascript:gotoSubmit(event)"/>
                ...
                <script language="javascript">
                    function gotoSubmit(evt) {
                        evt = evt ? evt : (window.event ? window.event : null);
                        ...
                        alert(evt);             // use evt
                        ...
                    }
                </script>
            此外,如果新代碼中第一行不改,與老代碼一樣的話(即 gotoSubmit 調(diào)用沒有給參數(shù)),則仍然只能在IE中運行,但不會出錯。所以,這種方案 tpl 部分仍與老代碼兼容。

    4. HTML 對象的 id 作為對象名的問題
        (1)現(xiàn)有問題
            在 IE 中,HTML 對象的 ID 可以作為 document 的下屬對象變量名直接使用。在 MF 中不能。
        (2)解決方法
            用 getElementById("idName") 代替 idName 作為對象變量使用。

    5. 用idName字符串取得對象的問題
        (1)現(xiàn)有問題
            在IE中,利用 eval(idName) 可以取得 id 為 idName 的 HTML 對象,在MF 中不能。
        (2)解決方法
            用 getElementById(idName) 代替 eval(idName)。

    6. 變量名與某 HTML 對象 id 相同的問題
        (1)現(xiàn)有問題
            在 MF 中,因為對象 id 不作為 HTML 對象的名稱,所以可以使用與 HTML 對象 id 相同的變量名,IE 中不能。
        (2)解決方法
            在聲明變量時,一律加上 var ,以避免歧義,這樣在 IE 中亦可正常運行。
            此外,最好不要取與 HTML 對象 id 相同的變量名,以減少錯誤。
        (3)其它
            參見 問題4

    7. event.x 與 event.y 問題
        (1)現(xiàn)有問題
            在IE 中,event 對象有 x, y 屬性,MF中沒有。
        (2)解決方法
            在MF中,與event.x 等效的是 event.pageX。但event.pageX IE中沒有。
            故采用 event.clientX 代替 event.x。在IE 中也有這個變量。
            event.clientX 與 event.pageX 有微妙的差別(當整個頁面有滾動條的時候),不過大多數(shù)時候是等效的。

            如果要完全一樣,可以稍麻煩些:
            mX = event.x ? event.x : event.pageX;
            然后用 mX 代替 event.x
        (3)其它
            event.layerX 在 IE 與 MF 中都有,具體意義有無差別尚未試驗。


    8. 關于frame
       (1)現(xiàn)有問題
             在 IE中 可以用window.testFrame取得該frame,mf中不行
       (2)解決方法
             在frame的使用方面mf和ie的最主要的區(qū)別是:
    如果在frame標簽中書寫了以下屬性:
    <frame src="xx.htm" id="frameId" name="frameName" />
    那么ie可以通過id或者name訪問這個frame對應的window對象
    而mf只可以通過name來訪問這個frame對應的window對象
    例如如果上述frame標簽寫在最上層的window里面的htm里面,那么可以這樣訪問
    ie: window.top.frameId或者window.top.frameName來訪問這個window對象
    mf: 只能這樣window.top.frameName來訪問這個window對象

    另外,在mf和ie中都可以使用window.top.document.getElementById("frameId")來訪問frame標簽
    并且可以通過window.top.document.getElementById("testFrame").src = 'xx.htm'來切換frame的內(nèi)容
    也都可以通過window.top.frameName.location = 'xx.htm'來切換frame的內(nèi)容
    關于frame和window的描述可以參見bbs的‘window與frame’文章
    以及/test/js/test_frame/目錄下面的測試
    ----adun 2004.12.09修改

    9. 在mf中,自己定義的屬性必須getAttribute()取得
    10.在mf中沒有  parentElement parement.children  而用
                   parentNode parentNode.childNodes
       childNodes的下標的含義在IE和MF中不同,MF使用DOM規(guī)范,childNodes中會插入空白文本節(jié)點。
      一般可以通過node.getElementsByTagName()來回避這個問題。
       當html中節(jié)點缺失時,IE和MF對parentNode的解釋不同,例如
       <form>
       <table>
            <input/>
       </table>
       </form>
       MF中input.parentNode的值為form, 而IE中input.parentNode的值為空節(jié)點

      MF中節(jié)點沒有removeNode方法,必須使用如下方法 node.parentNode.removeChild(node)

    11.const 問題
      (1)現(xiàn)有問題:
         在 IE 中不能使用 const 關鍵字。如 const constVar = 32; 在IE中這是語法錯誤。
      (2)解決方法:
         不使用 const ,以 var 代替。

    12. body 對象
       MF的body在body標簽沒有被瀏覽器完全讀入之前就存在,而IE則必須在body完全被讀入之后才存在

    13. url encoding
    在js中如果書寫url就直接寫&不要寫&amp;例如var url = 'xx.jsp?objectName=xx&amp;objectEvent=xxx';
    frm.action = url那么很有可能url不會被正常顯示以至于參數(shù)沒有正確的傳到服務器
    一般會服務器報錯參數(shù)沒有找到
    當然如果是在tpl中例外,因為tpl中符合xml規(guī)范,要求&書寫為&amp;
    一般MF無法識別js中的&amp;


    14. nodeName 和 tagName 問題
      (1)現(xiàn)有問題:
         在MF中,所有節(jié)點均有 nodeName 值,但 textNode 沒有 tagName 值。在 IE 中,nodeName 的使用好象
         有問題(具體情況沒有測試,但我的IE已經(jīng)死了好幾次)。
      (2)解決方法:
         使用 tagName,但應檢測其是否為空。

    15. 元素屬性
       IE下 input.type屬性為只讀,但是MF下可以修改


    16. document.getElementsByName() 和 document.all[name] 的問題
      (1)現(xiàn)有問題:
         在 IE 中,getElementsByName()、document.all[name] 均不能用來取得 div 元素(是否還有其它不能取的元素還不知道)。
    posted @ 2005-12-31 17:55 Dion 閱讀(934) | 評論 (1)編輯 收藏

    這里說說我的經(jīng)歷吧。大學前以及大學前面三年的經(jīng)歷就不說了,因為大學前的高中就是好好學習,大學前三年就是混過來的。

        我上的學校還算可以,雖然不是北大清華這樣第一流名牌大學,但至少也算中國的第二流名牌大學了。大學中前面三年都陪伴著游戲過去,所學到的只是些計算機基 礎知識。到大四后我突然發(fā)現(xiàn)就業(yè)的問題就在眼前,而自己似乎什么也不會,于是開始看書。最一開始重點看的是C++,可是后來自從看了一本J2ME的書以后 被Java所吸引。當時雖然學校上過Java課程,但是自己也只是學了很少的皮毛,也就只會寫寫Hello World和什么加減法之類很簡單的程序,連API都知道沒有幾個,比如說字符串長度的API我都不知道。所以剛開始自己學J2ME的時候?qū)覍沂艽欤约? 也明白自己的缺點,決定從J2SE開始好好補上。

        剛開始為了熟悉Java開發(fā)環(huán)境,買了本JBuilder開發(fā)的教程,并且在自己的本本上安裝了JBuilder進行演練。當時的我連JavaDoc都不 知道,每次究竟什么API能做什么事情一點頭緒都沒有,還不知道哪里去查,后來同學告訴我有個JavaDoc這個東西,我還興奮不已,覺得自己被從黑暗中 拉回來了。一開始使用JBuilder的時候,馬上為之所吸引,有兩個原因,第一是因為它自動標出語法錯誤,邊寫代碼邊提示你什么地方語法出錯,記得以前 使用VC++的時候,每次程序?qū)懞煤笙染幾g,然后再Build,再運行,這其中每個步驟都會出不少錯誤。特別是在編譯的時候,寫個200多行的程序一次編 譯下來就有100多個錯誤,結果每次花在這上面的工夫都要好長時間。而JBuilder使用了即時語法分析,所以基本上程序?qū)懲辏涂梢允÷哉{(diào)試語法錯誤 的步驟了。第二個原因是可以自動提示代碼,這個功能可以讓你迅速熟悉API,免得每次去查幫助文檔那么麻煩,我就是這么很快掌握了許多API的。

    可能大家會問我為什么一開始不學習《Java編程思想》,的確這本書我們宿舍就有好幾本,不過大家普遍反映效果不好,到最后都不知道說的是什么,所以我也沒敢看。

        經(jīng)過20天左右的學習,對Java有了更進一步的了解,熟悉了不少API函數(shù),由于在那本書上寫開發(fā)SWING占了不少篇幅,所以也對Swing的開發(fā)了 解了不少。看完以后因為同學說Java的靈魂就是多線程編程,所以開始看Oreilly的《Java線程》。記得在大學中操作系統(tǒng)這門課我們就提到過線程 的知識。并且課本上就是用Java實現(xiàn)的,當時有了一點點概念,但這次看這本專門說線程的書后才發(fā)現(xiàn)我原來了解的那些根本是什么都不算(當然,現(xiàn)在回想起 來,我那時看書學到的也只是很簡單的皮毛而已)。看完這本書后我自己學會在我的JBuilder下開發(fā)很簡單的多線程程序,并且模擬線程沖突,等待等情 況。當時看著自己寫的一兩百行程序可以順利執(zhí)行,那種興奮勁就別提了。這本書我看得也很快,大概就花了3個星期看完。

        經(jīng)過上面的學習,自己相比以前來說提升了不少,這時候自己也找到了工作,是做J2EE對日外包的,所以更加堅定了努力學習Java的信心。

        在上面寫的程序中,我自己寫程序沒有規(guī)范性,在代碼編寫的時候自己的盲點特別多,還容易犯低級失誤。同學有一個《Effective Java》中文版,可是我看了幾頁發(fā)現(xiàn)自己根本看不懂,里面什么靜態(tài)工廠啊,什么單例模式什么的根本不知道什么東東。我知道自己目前的水平還不夠,所以決 定放下這本書,去尋找別的適合我的書看。這個時候我看到了候捷先生翻譯的《Practical Java》一書,當時是剛剛上的書架。這本書我在書店翻了下目錄后就感覺如獲至寶,馬上買回家,在回家的公車上就貪婪地讀起來。這本書不算很厚,但是自己 看得卻很認真很仔細,也明白了不少東西,比如Java中等號和equals()方法的區(qū)別,究竟什么時候用什么。還有Exception處理機制,以前不 知道什么叫Exception,只是JBuilder提示我要我拋出Exception我再拋出Exception,自己覺得這東西基本沒什么用呢。但是 看了這本書后我改變了看法,我發(fā)現(xiàn)Exception是個很好的東西,可以迅速把程序從正常狀態(tài)和異常狀態(tài)區(qū)分開來,即使而準確地在指定位置得到處理。那 時自己也有了以后寫程序的時候注意編寫異常處理部分的想法。《Practical Java》這本書雖然不厚,但是我卻非常仔細地去看了,大概花了1個月時間,我把這本書完全消化了下去。

        當時聽說Java在網(wǎng)絡上的應用非常廣,我也不知道究竟是什么應用,我于是買了Oreilly的《Java網(wǎng)絡編程》這本書。這本書雖然很厚,其實前半部 分內(nèi)容不是很復雜,后半部分寫什么RMI的東西我也看不大懂,只能理解個概念。通過這本書,我了解了HTTP協(xié)議究竟是什么一個東西,在它上面利用 Java傳輸數(shù)據(jù)該如何做,知道了什么是Request,什么是Response。這也為以后開始我的J2EE之旅打下了很好的基礎。當時自己依然是邊看 書邊自己寫代碼來驗證,自己寫了個服務器端Socket和客戶端Socket,成功進行了通信,又在上面加上了安全Socket內(nèi)容,實現(xiàn)了SSL通信。 當時我把寫的這個又套上了Swing的外殼,還和同學拿這個傳文件呢。不過當時也沒有考慮過什么校驗碼之類的東西,所以傳傳小文件還是可以的,文件稍微一 大一點,傳過去的文件總是不對頭,和我原來的文件經(jīng)常會出一些差異,導致文件打不開。

        《Java網(wǎng)絡編程》這本書看了不少時間,因為書比較厚,東西也比較多,不過除了后面的一些知識以外,其他的還是容易理解的。大概花了2個月左右的時間看 完。看完后,時間也到了2004年的3月。我也輪到開始我畢業(yè)設計的時候了。我們的畢業(yè)設計導師都還不錯,給你自己選個課題,我選的是一個B/S結構的在 線簡歷處理系統(tǒng),正好和我所學和下面所工作的東西是一條路上的了。這時我覺得我應該往B/S結構上轉了,當時在選擇先看Servlet還是先看JSP上猶 豫不決。最終決定先看Servlet,后來也證明了我的決定是對的,我在熟悉了Servlet后再學JSP是非常容易的,基本上根本沒有遇到什么難點。

    可 能有人會覺得我看了好多Oreilly的書,雖然我不能說Oreilly本本都是好書,不過相對來說,好書的概率總超過許多其他的出版社,而且體系比較齊 全。我看得幾本書我都覺得還不錯。現(xiàn)說說下面這本我學Servlet時候看的《Java Servlet編程》來說吧,很不錯的一本書,讓我迅速知道了什么是Servlet,然后通過最簡單的實例,讓你知道了Servlet如何運行的,跟 HTTP協(xié)議是如何配合的,如何返回HTML形式的文本,XML配置符該如何寫,究竟每個元素是什么意思等等。由于我原來有一定的XML基礎(知道XML 語法各種格式的含義而已),所以掌握起來還算比較快。通過這本書,我知道了如何動態(tài)生成HTML文檔,知道如何把一個Servlet映射到一個虛擬的地 址。在后半部分寫到了數(shù)據(jù)庫操作部分,我對數(shù)據(jù)庫的了解其實也僅限于原來大學課本上的《數(shù)據(jù)庫系統(tǒng)原理》,如何從程序和數(shù)據(jù)庫交互是一竅不通。通過數(shù)據(jù)庫 操作這章,我知道了如何使用JDBC語句如何編寫,大家不要笑,對于當初一個新手來說,這個真是一個全新的領域,做什么事情都需要Sample來對照,跟 著依葫蘆畫瓢吧,其實現(xiàn)在的軟件開發(fā)也是這樣,我想現(xiàn)在大家誰能直接手寫Struts或者Hibernate的配置文件都很難吧。閑話少說,大概這個時 候,我對畢業(yè)設計的雛形有了點思想上的概念。看完了《Java Servlet編程》后緊接著就又看Oreilly的《JSP設計》,由于有了Servlet的基礎,學起JSP特別快。當時沒有著重看Tag的自定義設 計,光看了JSP的其他東西,終于在五一節(jié)后把畢業(yè)設計都寫完了,當時總代碼量是2000多行,第一次寫這么多代碼的程序覺得很有成就感。現(xiàn)在看起來那時 做的是標準垃圾,但是當時覺得還是很不錯。用了Servlet + JSP。其實Servlet也不是用來當控制器的,而是和JSP做的差不多功能,都是作view的功能的。很快,畢業(yè)設計交差過去了,寫寫畢業(yè)論文,準備 答辯。在這個過程中,我又一次考慮自己下面該看什么書。

    這次我又看中了侯捷翻譯的一本巨著,也就是鼎鼎大名的Martin Fowler寫的《重構——改善既有代碼的設計》這本書。剛開始聽見重構這個名字,總覺得非常高深,加上都評論說重構是和設計模式齊名的東東,感覺更加高 深恐怖了。大概在6月初我開始看了重構,剛開始看的時候雖然抱著試試看的心態(tài),不過還是非常認真的。但是,讓我頗感意外的是重構并不是很難,至少這本書中 說的非常通俗易懂,通過大量的實例讓你覺得重構是種很簡單很基本的技術。雖然我看完了重構以后在真實的代碼編寫中很少直接按照上面代碼所說的方法進行重構 代碼,基本上都是通過IDE來重構代碼,但是卻大大提升了自己編程思維,從此以后寫代碼就很少瞻前顧后了,因為我擁有了重構這個工具。這本書有點厚,再加 上中間有答辯,拍畢業(yè)照,以及畢業(yè)手續(xù)等等,這本書我花了一個半月看完。我看書的速度也不算快,不過我看書比較有恒心,不像有部分人看幾天就不想看了,我 能堅持天天看,所以總的來說還是不慢的。我計算過,如果我每天看10頁書,堅持下去,那一年就是3650頁書,平均一本書365頁來算,1年就是10本。 如果這10本書中有8本不屬于垃圾書籍,那么你這年就能有非常大的提高了。

    看重構這本書中間我也抽了一段時間看了兩本其他的書,第一本是 《Java夜未眠》,挺不錯的一本書,雖然是散文,但是還是能讓你明白不少道理,受益匪淺。另外一本就是李維的《Borland傳奇》,由于自己當時最喜 歡用的工具就是JBuilder,所以也對Borland公司非常敬仰,特別對安德森,簡直就頂禮膜拜啊,哈哈。這本書寫得很精彩,寫了Borland公 司二十年來的血淚史,寫了如何跟微軟斗爭,如何在微軟和IBM的夾縫中生存。當然,也有很多的對于技術方面作者李維自己的見解,看了會有不少同感的。就這 樣,磨磨蹭蹭地把重構看完了。

        當看完了《重構》這本書之后,我也開始去公司報到上班了。可以看出來,我當時工作的時候水平也很有限,但總比一年前要好不少,至少很多東西都已經(jīng)知道了。 那時外面極限編程聽的比較多,自己也去書店買了本《Java極限編程》回來看,現(xiàn)在想想算是我看得第一本垃圾書籍了。不過也是有收獲的,這本書極限編程也 就說了點概念,然后就寫了不少工具的使用方法。在看《重構》中對JUnit有了點認識,不過只是皮毛中的皮毛。看了這本《Java極限編程》后對 JUnit的使用又了解了不少皮毛,對于Cactus有了點了解,對Ant了解了不少,至少可以自己寫出自己需要的配置文件了,并且可以結合JUnit生 成測試Report。由于我去的是日企,做對日外包的,所以公司開始培訓日本語,用的是《標準日本語》這套教材。我于是邊學日語邊看技術,大概2個星期左 右我把那本《Java極限編程》初步看完后就扔在了家里。這時的我已經(jīng)開始會用Ant了,覺得是步入J2EE的重要一步。

        很快啃掉那本垃圾書以后又看了本和Java不是非常有關的書:《程序員修煉之道——從小工到專家》,原因其實很簡單,大學同學都說這本書是經(jīng)典書,看書這 東西,別人的評價還是能起不少作用的。這本書字數(shù)不是很多,不過排版的時候比較分散,導致書本有點厚,呵呵,可能也算出版社賺錢的一種方法吧。不過總的來 說,我覺得出版社紙張質(zhì)量最好的是電子工業(yè)出版社,其次是中國電力出版社,最爛的恐怕就是機械工業(yè)出版社了,機械工業(yè)出版社有少量書紙張還能說過去,但有 不少簡直讓人不得不有脾氣啊,紙張薄得感覺和寫毛筆字的宣紙都差不多了。這本電子工業(yè)出版社的書紙張質(zhì)量的確不錯,不過也許是因為我功力尚淺,所以這本書 雖然都看懂了,但是深有感觸并且銘記于心的沒有幾個,現(xiàn)在再回想,也只記得軟件模塊設計時要正交等等少數(shù)幾點了。這本書由于內(nèi)容不是非常多,所以我就看了 半個月不到搞定。這時的我開發(fā)IDE已經(jīng)轉移到了Eclipse上,畢竟商業(yè)開發(fā)用D版有點說不過去,而且公司也怕查,所以不允許用JBuilder,鼓 勵大家用Eclipse。我用了一段時間的Eclipse后,從一開始的不適應到后來覺得Eclipse很方便使用,JBuilder比Eclipse多 的就是一些根據(jù)不同類型開發(fā)的模版而已,而這些可以由Eclipse的插件來彌補。到了這時,我覺得我的Java基礎應該算還可以的了,API也熟悉了非 常多。我覺得看《Effective Java》的時機成熟了。

        由于大學已經(jīng)畢業(yè)了,所以也不會有同學的《Effective Java》放在邊上讓我看這樣的好事出現(xiàn),老老實實地去了書店買了本《Effective Java》中文版回來研讀。呵呵,大家也許會問我為什么不買本E文的看,雖然我大學早早也把英語4級過了,而且大學中不少計算機專業(yè)課程教材也是E文的, 當時為了考試也認真讀了。但是畢竟英語不是我們的母語,看起來速度上明顯比中文版的慢一截。當然,如果是那種垃圾翻譯者用機器翻譯出來的中文版,看那些垃 圾中文版速度肯定比不上直接看英文原版的。這時的我看《Effective Java》已經(jīng)不再是當初的那么感覺很陌生了,覺得上面說的那些要點自己想想還都是可以理解的。我個人覺得提高自身編程習慣以及水平最多的還是看類似于 《Practical Java》和《Effective Java》的這種按照條目來進行說明的書,能把你自己平時容易忽略的地方按照重點一個個揪出來進行修正。比如《Effective Java》中的第一條,使用靜態(tài)工廠來代替構造函數(shù),自己原來在進行開發(fā)的時候,從來不怎么會主動想到建立一個靜態(tài)工廠,總覺得使用構造函數(shù)來新建一個對 象是天經(jīng)地義的事情。但看完這個條目后,我的看法也隨之改變,發(fā)現(xiàn)靜態(tài)工廠還是非常好的,當然,也不是什么地方用靜態(tài)工廠都很好。上面也寫到了靜態(tài)工廠的 優(yōu)缺點,比如在什么地方適合使用,什么場合最好不要使用等等。這本書我覺得翻譯的也不錯,絕對值,強烈向有一定開發(fā)經(jīng)驗的人推薦。我大概看了3周半的樣子 把這本書看完,這時的時間也到了2004年的9月初,新員工入司培訓也不再是第一個月純粹的日語培訓,而是技術培訓和日語培訓一起開展,技術上培訓 Java,Web開發(fā),數(shù)據(jù)庫開發(fā)這三門課程,日語則開始進行日本語國際三級的培訓。公司的日語培訓和技術培訓都還不錯,技術培訓純粹把大家當作什么都不 懂的人,在Java上從最原始的Hello World開始培訓,Web開發(fā)上從HTML頁面開始培訓,數(shù)據(jù)庫開發(fā)則從Oracle的安裝,SQL語句的編寫開始培訓。當然,在培訓的過程中我也不會 閑著,而是又開始尋找自己要啃的書本,這次,我選中了Oreilly新出版不久的《Java與XML》第二版。

        由于XML表達數(shù)據(jù)的自由性以及強大型,所以XML特別適合于做配置文件以及數(shù)據(jù)信息文件,在Java中XML的使用可謂是多如牛毛。在J2EE中,從 Web Application的web.xml開始就是XML文件,到下面的Framework配置等等,沒有一個沒有XML的身影,而且XML都起到了舉足輕 重的作用。雖然我原來也懂一點XML,不過也僅限于XML的語法以及結構等等,那些深入下去的東西基本還是盲點,關于Java中如何處理XML更是一竅不 通。為了更好的學習J2EE,XML是必須征服得一座山峰。這次,我依然又再一次信任了Oreilly出版社,買了本當時出版不久的《Java與XML》 中文第二版。這本書剛開始并沒有過多介紹XML本身過多的東西,只是為了怕某些讀者并不了解XML而對XML語法結構等做了非常簡要的介紹,不過也非常到 位的介紹。介紹完了這些XML基礎知識后就開始了SAX——〉DOM——〉JDOM——〉JAXP——〉Web Service的歷程。不過我現(xiàn)在覺得如果能介紹DOM4J就更好了,因為我現(xiàn)在覺得DOM4J是Java中最好用而且性能也不錯的XML處理工具。剛開 始的我其實什么是SAX,什么是DOM都不知道,對JAXP更是一無所知。這本書英文版據(jù)說很受好評,中文版我只能說一般,因為有些地方估計譯者并不擅長 這一塊,所以翻譯得很生硬,以至于部分段落難于理解。總體來說,書的絕大多數(shù)內(nèi)容還是可以看懂,由于沒有具體實際操作的經(jīng)驗,所以很多也就是把概念理解 了,直到幾個月后做正式項目開始應用這些XML處理工具進行開發(fā)的時候才達到了熟練運用的能力。在這本書中學會了JDOM的使用方法,JDOM也還是比較 好用的,學會了JDOM,以后操縱XML也方便了許多。這本書我的建議就是,可以一口氣讀到第十章JAXP部分,后面的Cocoon以及SOAP等等部分 那本書介紹的并不是很好。Cocoon我是看了官方專門的幫助文檔以后才感覺入了門。而SOAP是經(jīng)過別的書籍加上項目中的實際運用才真正學會的。

    這 時到我剛進公司已經(jīng)兩個月過去了,時間已經(jīng)到了9月中旬的樣子,還有一個月我們公司新員工入司培訓就要結束,也意味著還有一個多月我們就要開始接觸正式項 目。這時的我寫B(tài)/S程序僅僅是JSP + JavaBean的水平,連JSP中的TAG都不會自定義,看見別人網(wǎng)上的程序自己還自己定義Tag很是羨慕,于是決定把那本《JSP設計》認真看完,把 自定義Tag的功能實現(xiàn)。后來看了以后發(fā)現(xiàn)原來那本《JSP設計》的精華都在最后的150頁內(nèi),最后那部分先是介紹了自定義Tag的定義方法以及Tag定 義所帶來的一些好處。自從學會了如何自定義Tag,在后來公司的項目中自己也根據(jù)項目的特點定義了一些共通的Tag,大大方便了不少項目中的開發(fā)人員,提 高了生產(chǎn)力。這本書而且也說了一下B/S開發(fā)的兩種Web Module。在這里,我第一次知道了Web開發(fā)可以用一個Servlet作為控制器,用JSP僅僅作用于表現(xiàn)層,這也為以后掌握MVC打下了很好的基 礎。

    9月中下旬掃完了《JSP設計》的尾巴后,有一次跟公司給我們培訓的老師在閑聊時談到了項目開發(fā),我詢問他項目是不是用JSP和 JavaBean來開發(fā),他笑著和我說不是這樣的,而是基于Framework來進行開發(fā)。比如Struts就是公司的常用Framework。 Struts這東西以前也好像聽說過,不過從來也只是聽說而已,并沒有看過。得到這個信息的我,為了能盡快熟悉實際項目的開發(fā)環(huán)境,便決心盡快學會 Struts。當時的市場上講解Struts的書只有一本,也就是Oreilly的《Jakarta Struts編程》,不像現(xiàn)在連《Struts in Action》的中文版也有了。我去了書店買來開始回家看,剛開始看的時候覺得如同云里霧里一般,因為這本書歸納總結性的東西很多,比較適合當參考手冊, 而真正帶領新手入門這一塊做的并不好。所以當我把這本書都看完了以后,還是不會用Struts編寫一個程序,只是感覺自己朦朦朧朧懂了一些概念,比如 MVC什么的。在公司我們的培訓也結束了,通知在國慶節(jié)過來以后的第一個星期——大概是10月10日左右進行考試,最后根據(jù)培訓考核情況來調(diào)整薪水。當時 跟我一起培訓的新員工基本上沒有人會Struts,其實這個時候連會用JSP + JavaBean寫一個最簡單的登錄畫面的人也沒有多少個,大部分人還是模模糊糊懂一點,但是具體做東西還是做不來的那種水平。國慶節(jié)大概10月5號的我 去了趟書店,突然發(fā)現(xiàn)書架上新上了一本書,就是孫衛(wèi)琴編寫的《精通Struts》這本書。孫衛(wèi)琴的書我倒是聽說過,就是在這之前出的一本關于Tomcat 以及Web App開發(fā)的書,據(jù)說挺容易上手的。我翻看了這本書的目錄結構,覺得可以值得一讀,于是雖然價格不菲,仍然買回家當天就研讀起來。憑我的讀后感覺來說,這 本書也許學術價值并不高,說得深入的東西基本沒有,但是特別適合初學者,通過Hello World這種例子迅速讓你手把手編寫出第一個Struts程序。就這樣,在這本書買回來的第二天,我自己就用Struts寫了一個很簡單的登錄畫面程 序,當時的感覺別提多興奮了,就感覺自己入了門,以后的道路一片光明。在這里,我要由衷地感謝孫衛(wèi)琴女士,寫了這么一本適合初學者的書,同時也建議沒有學 過Struts但又想掌握Struts的Java程序員們買這本書回來看(不知道我是不是有書托之嫌,我只是說我自己的心里想法)。

        國慶的假期放完了,我也回到了公司準備考核,上午是筆試,下午是上機考試。筆試分為了4塊,分別是Java,Web開發(fā),Oracle數(shù)據(jù)庫,以及 CMMI規(guī)約。這四門除了Oracle數(shù)據(jù)庫我一向不是很擅長,只考了個中等分數(shù)以外,其他三門分數(shù)都名列前茅。不過CMMI規(guī)約老實說我也不怎么會,不 過碰巧考的很多都是我知道的東西。下午是上機考試,題目給出來了,我一看題目,原來是一個最簡易的成績查詢系統(tǒng),也就是數(shù)據(jù)庫里面已經(jīng)有一些學生成績,我 們寫一個檢索頁面,可以輸入或者選擇檢索條件,把符合我們檢索條件的數(shù)據(jù)輸出并顯示在畫面中。我于是拿剛學會不久的Struts進行編寫,在3個小時內(nèi)把 整個頁面都寫好了,并且還自定義了一個Tag來顯示數(shù)據(jù)信息。考完以后我才知道總共也就五六個人程序可以運行,而且只有我一個人用的是Struts,其他 人基本都是最簡單的JSP + JavaBean,有的人連JavaBean都沒有,數(shù)據(jù)庫操作全部寫在了JSP頁面中。毫無疑問,這次上機考試我得到了好評,給了最高分。在全部的培訓 成績中我也居前兩名,我們部門新員工我排第一名。帶著這個成績,我們的入司培訓基本結束,開始進入部門做實習項目。

        雖然說我們正式進了部門,不過試用期還沒有結束,我們試用期最后一個月的任務就是做一個實習項目,當然,每天還是要進行日語培訓,因為要參加12月份的國 際日語三級考試。公司也象征性得給大家培訓了三節(jié)課的技術,第一節(jié)是Struts培訓,第二節(jié)是Web App的MVC結構的培訓,第三節(jié)是Log4j培訓,這幾次培訓下來,大部分人感覺好象云里霧里一樣,基本什么都沒聽懂,不過我由于有了點Struts的 基本知識,所以感覺收獲比較大,特別是MVC的培訓中我真正明白了視圖——控制器——模型這三層每層應該怎么處理,知道了一個Web App中如何分Java Package比較好,明白了專門有一個DAO層來處理數(shù)據(jù)庫所帶來的便捷,明白了Log在Web開發(fā)中的重要地位,這為以后的開發(fā)帶來了很大的好處。實 習項目的課題很快就下來了,要我們做一個電子相冊的B/S系統(tǒng),要求有圖片上傳,圖片檢索,圖片顯示以及要用Struts來構建,這些是基本的要求,其他 功能可以自由擴張。我們部門的新員工分為兩個小Group,都是一樣的課題,互相促進和學習,每個Group還配備了一個老員工,作為督促我們的進度,防 止我們有過大的偏差等等,不過具體做東西上原則上要求是不會給我們什么幫助。首先每個小Group要選出一個Leader,結果我被大家一致選為我們 Group的Leader。在小組討論中我們先進行需求分析,大家的討論很是熱烈,主意也很多,不過基本上組員們也都是點子多,具體實現(xiàn)上面還都沒有想 過。對于他們的那些建議,絕大多數(shù)我決定都作為我們要實現(xiàn)的目標,但也有少部分我覺得目前以我們的水平還無法實現(xiàn)的,就先否決了。會議開完后,當天回家以 后我就開始排開發(fā)計劃和個人的進度,第二天寫畫面的基本設計,第三天把組員拉過來開始Review基本設計,我們組的速度還算比較快。從星期二公布課題, 到星期五就和幾個組員一起把DEMO畫面設計出來了。原來的計劃是第二個星期一開始Coding,大概花一個星期完成。不過其余組員似乎還是不怎么會 Struts,于是我回家星期六星期天基本全天都在看書寫代碼學習,花了兩天把項目基本Coding完畢。其中Web頁面部分也不再使用一開始使用 Frame的做法,而是采用了Tiles框架。Tiles的使用過后我感覺是非常好的東西,經(jīng)過簡單的配置可以完成大批網(wǎng)頁中類似部分的構建,而且生成的 屬于一個頁面,這樣就省去了以前寫Frame時提交頁面總是要考慮設置Target以及在引用對象的時候大批Parent或者top對象使用的麻煩事了。 在開發(fā)過程中我使用了Log4j,這為我的調(diào)試程序帶來了極大的方便,呵呵,可以想象,沒有Log來調(diào)試一個Web程序真是不可想象的。

    這 段時間我是邊開發(fā)邊翻查那本《精通Struts》的,這樣,迅速把Struts中提供的許多Tag弄熟練了,為以后具體的項目開發(fā)帶來了便捷。也許是一向 以來公司的實習項目完成效果都不是很理想吧,這次我們的迅速完成比較出乎Leader的意料,綜合三個月的試用培訓,由于我在日語和技術以及實習項目中表 現(xiàn)都還不錯,所以定工資級別時也是同一批進公司的新員工中最高的,隨著轉正會議的結束,我也在10月底成為了公司的正式員工。大概剛剛進入11月份,我們 Group便開動一個項目,項目不是很大。當時老員工們許多都在做項目的詳細設計,我便跟著公司一位技術專家(也是當初給我們?nèi)胨九嘤柕钠渲幸晃焕蠋煟┳? 起項目的Framework構建工作。當時的我才進公司,第一資歷尚淺,第二我的確也并不是很會什么東西,所以給我的任務很多都是一些模塊的 Utility的設計。比如通用的Check方法啊,CSV以及定長文件的讀取解析什么的啊,還有某些在IE中可以實現(xiàn)的效果如何在Netscape中也 能實現(xiàn)同樣的效果等等。雖然這些東西在現(xiàn)在看來并不是很復雜,但是當時自己的確隨著做這些東西而學到了很多很多。比如使用JDOM對XML文件的解析啊, 很多Check方法的小技巧啊,IE和Netscape究竟有什么地方不一致,該如何解決等等,這些都在這幾天內(nèi)了解了很多。在這幾天中,我通過網(wǎng)上查找 資料,臨場迅速學習了Java反射的使用方法,并且自己邊學邊寫實例,實現(xiàn)了各種情況下的反射案例。我個人覺得掌握Java反射技術是非常重要的,這讓你 可以寫一些通用的工具。如果不會反射技術的話,也許你永遠只能寫一些針對特定情況下的解決方法。而會使用反射以后,你可以寫一些代碼,這些代碼可以用在許 多地方,達到自己擴展甚至構建Framework的效果。在這個項目中,我使用了自定義Tag和Java反射技術,定義了些項目中比較需要的通用的 Tag,方便了大家。

        后來聽老員工說新員工進公司就開始做Framework是以前從來都沒有過的事情,因為我們Leader對我希望比較大,所以想盡可能培養(yǎng)我,讓我早點挑 起項目大梁,所以給我的成長提供了一次又一次的機遇。11月中旬以后,項目開始進入編碼階段,我也第一次看到了正式的項目設計書。第一次看到設計書的時候 我都覺得自己腦子有點懵,一大堆日語什么含義自己不是很清楚,而且感覺根本無從下手,不知道從哪里開始看比較好。項目擔當耐心得和我說了設計書的格式以及 究竟什么地方是什么一個含義,以及Coding的時候按照什么個思路來看設計書。再加上項目中有老員工先寫了個Sample,讓大家看了標準的一個流程, 所以我們就依葫蘆畫瓢,慢慢得把一個畫面一個畫面Coding完畢。當然了,后來也有測試員來測試我們的畫面,發(fā)現(xiàn)bug后就發(fā)Bug Report給我,那一個月就是在Coding,修正Bug中渡過的,這個項目是用Struts做的,因為不大。所以也沒有再用其他的 Framework,數(shù)據(jù)庫操作那里只有個非常簡單的單表操作DAO層,其余的DB操作都是自己通過JDBC操作語句來完成的。在這第一個自己接觸的真正 項目中,我自己學到了很多B/S設計的技巧,感覺很充實。不過書本學習方面我也沒有閑著,我為了能夠深入了解Java,大概在11月中旬左右開始看《深入 Java虛擬機》這本書,由于內(nèi)容比較深入,所以看得也有點吃力。書翻譯得和寫得都還不錯,值得一看,我一直看了前八章,看到Java程序運行細節(jié)后就沒 再看了,大概看到了12月底的樣子吧,呵呵,有時間的話決定把后面的部分也看完。這本書看完后收獲就是了解了Class文件的實質(zhì),Java的安全模型, 虛擬機是如何工作的。這些知識對后來調(diào)試程序Bug或者Exception的時候非常有好處,可以把以前自己覺得莫名其妙的錯誤的原因找出來,不像以前遇 到很古怪的Exception的時候怎么死的都不知道,從讀完這本書以后,在以后的調(diào)試異常中很少再有不知所以然的感覺了。

        2004年12月底的時候,我的第一個項目也做完了,由于我空閑著,Leader便在星期三的時候把一個公司內(nèi)部開發(fā)的Source統(tǒng)計的小工具讓我進行 修改,使得添加一個比較有用的功能。東西給我的時候基本沒有任何文檔,在我手上的就是一堆源代碼而已,界面是用Swing制作的,因為沒有專門在UI上進 行精心設計,所以說不上好看,典型的Java編寫的圖形界面的程序的樣子。軟件不是非常大,估計在1萬行源代碼以內(nèi),不過對于只有一個人修改來說,也比較 夠嗆了。還好我在剛學Java的時候用JBuilder寫了一些Swing的程序,現(xiàn)在還是對Swing有概念的,所以拿到手上以后經(jīng)過仔細分析,逐漸理 清了頭緒。經(jīng)過修改和自己測試完畢后,覺得還比較滿意,達到了預期的目標,于是在星期五的時候提交給了Leader。通過這次,對Swing的開發(fā)又加深 了印象,自然,在有的細節(jié)技巧方面受益匪淺。

    元旦很快來臨了,在年底以前,公司覺得有必要學習下Hibernate,雖然我們目前的項目中 還沒有用過Hibernate,而是用另外一個公司內(nèi)部開發(fā)的ORM工具,不過幾名技術專家初步對Hibernate感覺后覺得Hibernate的功能 要強大的多,而且是開源的,不斷有人在推動,升級,所以有必要我們要學Hibernate。這次的學習采用學習小組的形式,也就是公司內(nèi)部先抽幾名員工 (主要是技術部門的,當然,開發(fā)部門如果有興趣的話也可以考慮)來進行深入學習,然后定期開會交流互相學習,達到短時間內(nèi)先行的幾名成員迅速深入掌握 Hibernate的形式。由于我第一處于空閑狀態(tài),第二也比較有興趣,而且跟技術部門的專家們也比較談得來,所以我也加入了其中,成為幾名學習小組中成 員的一部分。我們學習資料主要就是《Hibernate in Action》英文版一書以及官方的幫助手冊。我負責其中對象操作,Transaction和Cache,還有高級Mapping關系的設置幾個部分的學 習。由于資料都是全英文的,所以看書速度并不是很快,不過還是初步得到掌握了。大概學習了半個多月的樣子,我們各自基本學習完畢,互相交流后并且寫下了讀 書筆記,用來后面具體項目開發(fā)時候參考用。通過這大半個月的學習,我個人覺得提高了非常多,在這之前,我只知道有ORM這種東西,但是從來沒有使用過,也 從來沒有看過。自從學過了以后,我不僅對Hibernate在代碼編寫時的使用比較熟悉了,而且對Hibernate的配置以及許多底層的知識有了很清楚 的認識,讓自己在數(shù)據(jù)持久化方面的認識提高了大大的一步。

        元旦過后,雖然一邊在學習Hibernate,不過由于下面項目的需要,Leader跟我說要我學一下Unix下的Shell編程,因為 項目中許多批處理會用Shell來啟動。UNIX命令在學校時候?qū)W過的,不過這個時候已經(jīng)忘記了很多,只是翻閱資料的時候還能回想起來不少命令。 Shell并不難,如果有了編程基礎,學習Shell編程也很快的,總體感覺就是編程語言大同小異,從基本語法來說,不外乎賦值、條件、循環(huán)這幾種類型。 只要迅速掌握這幾種類型在這種編程語言中的編碼格式,那么你就可以迅速掌握這門語言最基本的編程能力。Shell經(jīng)過一周的學習后覺得感覺不錯,不僅可以 順利看懂別人寫的Shell程序,而且自己可以在Linux下順利寫出符合自己需求的Shell程序并能順利執(zhí)行。但是突發(fā)事件總是有的,那個項目突然決 定延后兩個月,所以前一個星期的學得Shell等于暫時派不上用場了。不過嘛,多學一樣技能總是沒有害處的,而且又復習了那么多Unix命令啦,感覺還是 很不錯的。于是我又進入了不在項目中的真空期了。

        但是好景不長啊,好日子還沒有過上兩個星期,公司去年做的一個比較大的項目開始了2期開發(fā),我也被一下拖入了項目中。說起那個項目,公司好多人還心有余 悸,據(jù)說去年那個項目開發(fā)的時候,大概50多號人干了好幾個月,每天都是11點以后才有可能回家,周六是鐵定加班,周日是看情況,晚上就算加班加到凌晨3 點也不是什么奇怪的事情。當時大家都說多來幾個這種項目大家就要死了,這次這個項目的2期過來了,大家精神又一次緊張起來咯。一開始照例是項目會議,聽完 項目經(jīng)理描述以后,大家也放心了不少,這次雖然說是二期,不過規(guī)模不大,只需要15個人左右干一個月就能搞定。主要是把項目一期里面一些地方進行改修,當 然也有需要新的畫面的開發(fā),不過相對于去年的那塊不是很多而已。對我來說這次是個很大的考驗,因為項目是二期,項目組內(nèi)除了我,其他的人都做過1期開發(fā), 所以對項目結構都很清楚。這次項目開始并沒有什么培訓,所以我只能單獨看1期的源代碼來熟悉項目結構什么的。這個時候項目經(jīng)理把我叫去要我辦理護照,準備 這個項目派遣我去東京現(xiàn)場維護。

    這個項目是個比較全面比較大的項目,服務器采取了集群的方式,數(shù)據(jù)量也是千萬乃至上億級別的,所以性能要求 特別高。在技術方面用到了很多,使用EJB來控制Transaction,使用了ORM工具來操縱DB數(shù)據(jù)等等等等。而且由于比較龐大,所以服務器初始化 的那塊為了Load上去大批配置信息,代碼量極其龐大,在權限控制的那塊地方,代碼非常難以讀懂。這也給我一開始的學習代碼帶來了很大的一塊麻煩。不過總 算靜下心來后把整個項目框架以及實現(xiàn)手法基本摸清楚了,這個時候覺得非常開心,而且對Web應用程序的構造心里面也非常充實,覺得自己已經(jīng)具備寫 Framework的初步能力了。

    項目是緊張的,基本上每天晚上都要加班到11點,然后打車回家,哈哈,公司報銷。而且臨近過年,這么加班 也一點都感覺不到過年的氣息。不過我也不能因此放松了自己的學習。我覺得自己的基礎應該算比較好了,便開始啃起另外一本大部頭——《Java與模式》。一 直以來我對設計模式的感覺就是一些已經(jīng)成型的,久經(jīng)考驗的代碼框架,具有非常好的可擴展能力以及非常好的代碼健壯性。不過初學者最好不要看設計模式,因為 你接觸的代碼不多,如果貿(mào)然看設計模式的話,會造成不明白為什么這種設計模式好,究竟好在什么地方的情況下就在代碼中亂套設計模式,對自己的以后編碼發(fā)展 帶來不利的影響。每一種設計模式都有每一種設計模式的特點,自然也有他們自身的適用范圍,比如拿最基本的單例模式(Singleton)來說,適合于做配 置信息讀取器,主鍵產(chǎn)生器等全局唯一的東西。如果初學者不明白這些,拿單例模式亂套,什么都用單例模式,比如把普通傳遞數(shù)據(jù)用的JavaBean也做成了 單例模式,帶來的惡果就嚴重了。這本《Java與模式》我是從頭到尾認認真真看了,每看完一個模式都會仔細回想以前看的代碼哪里用到過這個模式,總會自己 想想這些模式適用于哪些地方。因為這個時候我自己編寫的代碼行數(shù)也已經(jīng)很多了,所以看見這些模式就會特別有感覺。經(jīng)過50多天的認真研讀,所有模式都被我 消化了下去,并且使得我的對程序開發(fā)上面的認識提升了非常大的一步。順路說一句,這本書寫得非常好,例子很多,但是不復雜,有一定代碼編寫經(jīng)驗的人就可以 看懂了。不過看懂并不是最重要的,重要的是消化下去,用來理解以前看過的代碼的精華,這樣自己才能得到提高。

        這個項目雖然很緊張很忙,不過我還是適應了下來,而且對整個項目結構什么的都有了比較好的整體的把握。項目橫跨了整個過年期間,所以在過年的那幾天都必須 開著手機,怕有什么突發(fā)事件要求去加班。簽證在2月4日左右送過去簽,Leader跟我說因為在過年期間,所以簽證可能會比較緩慢,比較難簽,不過一般情 況下1個月應該足夠了。原計劃我是跟另外兩個同事3月6日去東京,這樣算算也差不多。不過中國有句話叫好事多磨,呵呵,用在我身上的確不過分,我的簽證3 月3日日本領事館才簽,三月四日送到南京。3月5日和3月6日是雙休日,所以3月7日簽證才送到我手上。由于計劃是3月6日派人去東京,所以只好派另外一 個身上有簽證還沒有過期的人代替我過去,這次的機會就算泡湯咯。不過我并不是很在意,因為公司這里去東京出差的機會狠多,特別對于開發(fā)人員,據(jù)說工作幾年 后一聽到去日本出差就不樂意,畢竟也背井離鄉(xiāng)么。

        在這個項目的途中,大概在2005年1月底2月初的時候公司也開始進行了制作詳細設計的培訓,我雖然在項目中,不過也成為了其中一員。這次培訓總共大概6 次課,每次2個多小時,雖然時間不長,不過把詳細設計的要點以及思路和容易出錯的地方都說了出來,感覺很是不錯,這幾次課的培訓后,雖然可能要我立即進行 詳細設計編寫還有點困難,不過心里面已經(jīng)有了不少底,我覺得經(jīng)過一段時間后的鍛煉,我應該可以有進行詳細設計的能力了。

        3月初這個大項目結束后,本以為可以休整下,不過很快通知我3月7日加入另外一個項目,其實也不算一個正式的項目,屬于公司知識庫的一個信息查詢模塊。由 公司的一個技術專家負責,那人也就是我進公司時候第一個項目中帶著我的那個技術專家,說起來我和他還真有緣,現(xiàn)在我這個項目還是跟著他,而且公司里面唯一 一個和我同月同日生的人,真是很巧的巧合呢。他人挺好,很熱心,所以我也從他那學到了很多東西。這次由于不是正式項目,所以并沒有什么基本設計書,而是他 給我們開會議的時候大致說了下項目的內(nèi)容,每個畫面的具體功能以及數(shù)據(jù)庫表格的設計。由于這次項目規(guī)模很小,總共就12個畫面的量,所以不采取 Struts等Framework,而是采用比較原始的JSP + JavaBeans的構造。我們每個人根據(jù)他所跟我們講解得功能寫每個人自己分配到的畫面的詳細設計,其實也不算真正的詳細設計,就是每個人把自己操作的 那塊的具體邏輯設計寫出來,然后和他一起review一次,再開始編寫代碼。詳細設計這里我做的很快,當天下午就把自己分配到的兩個畫面業(yè)務邏輯什么的都 寫好了,星期一布置得任務,我星期三的時候全部編碼自測完畢提交,所以我的感覺就好像這個小項目一瞬間就結束了。

    日本每年財務結算是在3月 份,所以我們歷來的習慣就是每年1月和2月很忙,3月開始清閑,一直可以到5月左右會接到個大項目昨。所以接下來就真正到了我的空閑時期,沒有項目的壓 力,我可以自由學我自己喜歡的東西。很久以前買了本《精通EJB》第二版,可是一直以來我覺得自己功力尚淺,所以沒有看,這次我想認真學學EJB。雖然大 家公認EJB并不是很好,不過歷來受到批評的都是EJB中的Entity Bean部分,這部分我覺得可以借助Hibernate來彌補,而會話Bean和消息驅(qū)動Bean則還是挺不錯的。這次也當學一門技術,學習其好的東西, 不是很好的東西就當作以后開發(fā)時候的借鑒。《精通EJB》這本書我的感覺是書質(zhì)量比較好,不過翻譯的水平稍微差了點,特別是有不少錯誤,而且很低級的錯誤 居然校對的時候都沒有發(fā)現(xiàn),不能不說是個比較大的瑕疵。但是它不失為一本EJB的好教材。從一開始的JNDI開始,然后講解了對象序列化,RMI- IIOP等等。這些以前都模模糊糊,或者是看過了但是還不知道究竟有什么用。但是經(jīng)過這次的學習以后,對這些分布式系統(tǒng)比較需要的東西有了進一步的了解, 感覺頭腦中比較清晰,究竟RMI是什么樣的工作原理,怎樣實現(xiàn)一個遠程方法調(diào)用等等。接下來的EJB學習,自己用Eclipse + Weblogic邊看書邊動手,寫了一個個自己的學習小程序。我個人感覺看書最好就是邊看邊自己動手寫小學習程序,這樣比光看不練能學到多得多的東西。學 了EJB后覺得腦子又清晰了很多,看見一個案例后頭腦中就會有好幾種如何解決的方法,幾種方法互相在頭腦中自己比較,經(jīng)過這樣,大大提高了自己的思維活躍 性。

        3月中旬開始由于公司比較清閑,大部分人處于沒有項目的狀態(tài),所以公司舉辦了第一屆全公司范圍的編程競賽。公司只指定了題目為一個日歷系統(tǒng),要求具有日程 記事等功能,其余功能自由發(fā)揮。這次不再采用團隊形式了,而是采取各自為戰(zhàn)的策略。自從培訓過詳細設計以后,我頭腦一直有如何寫詳細設計的思路,這次我自 己首先指定了開發(fā)計劃,保證自己控制自己的進度。接著進行了需求分析,確定了我有哪些功能。然后在自己的基本設計中開始進行數(shù)據(jù)庫結構設計。這次我決定采 用Hibernate+Struts的結構進行編寫,這樣我的數(shù)據(jù)持久層操作大大簡化,而且功能上也增強了許多。DB設計好以后我開始DEMO畫面的制 作。說實話,我美工水平實在不怎么樣,可以說雖然一般網(wǎng)頁的效果我都會自己做出來,不過具體網(wǎng)頁設計成什么樣我還真是一竅不通。還好 Dreamweaver我還算算是比較熟練,自己搗鼓搗鼓也想摸象樣把DEMO畫面給設計出來了,不過美觀不美觀我就覺得不怎么樣了,只是我能力有限,也 沒辦法設計的更好看,這個時候我感受到了一個項目中美工是多么重要啊。下面的詳細設計自己寫得很開心,把需要的功能都用文字反映了出來,這也算我寫成詳細 設計樣子的第一份詳細設計了,做完挺有成就感的。接下來首先構筑自己這個小項目的Framework,經(jīng)過公司兩個正式項目的洗禮后,那兩個項目的 Framework我都認真研讀過源代碼的,所以我自己有了自己心里一套Framework的構造方法,特別是如何把Struts和Hibernate結 合起來的結構,自己有自己的一些想法。在這次Framework構造中,我沒有復制任何公司以前的代碼段,都是憑著自己對以前看的代碼理解后寫出來的。這 次項目我覺得對自己的提高也很大,首先鍛煉了自己詳細設計的能力。其次,自己雖然學習過Hibernate,不過從來沒有這么樣應用過 Hibernate,這次讓自己大大提升了實踐運用的經(jīng)驗。公司由于知道這時也沒有一個真正的項目使用Hibernate,所以這時的我也算公司內(nèi)部 Hibernate使用經(jīng)驗最豐富的人了,這也為了后來我協(xié)助別的使用了Hibernate的項目解決問題的原因。再次,我這次自己寫了 Framework,特別在批處理方面,運用了許多剛學會理解掉的設計模式,這些模式讓我的程序更具有健壯性和可擴展性,讓我在設計方面的能力大大提升 了。

    這次的編程競賽我寫得比較認真,代碼量的確也很大,總代碼行數(shù)超過了3萬行,有效代碼行數(shù)也在1萬行以上。經(jīng)過公司專家們的評定后,我 得到了第一名,雖然沒有什么獎品,不過肯定了我這段時間以來的努力,我還是很開心的。而且這次的編程競賽讓我大大增加了編碼的熟練度,而且也在其中演練了 許多自己想出來的編程技巧,為以后的發(fā)展帶來很大的好處。

        從4月份開始,公司由于比較清閑,所以部門內(nèi)部開始進行各種培訓。我們部門開展了3項培訓,第一項就是編程能力培訓,第二項是Oracle數(shù)據(jù)庫技術培 訓,第三項是測試技巧培訓。在編程能力培訓中,主要就是把原來沒有注意的細節(jié)采取大家討論,輪流講課的方式進行的,雖然其中很多東西我原來都是知道的,不 過也有原來不清楚的地方。而且經(jīng)過了這次互相討論,更加加深了印象。在Oracle培訓中我覺得收獲很大,這個Oracle培訓采取了傳統(tǒng)的上課的模式, 由我們開發(fā)小組中一個取得OCM的老員工給我們講解。對于Oracle,我原來基本上就只會寫寫SQL語句,具體Oracle有什么特別的功能,可以做什 么我也不是很清楚。但是這次上課從Oracle的啟動原理開始,讓我知道Oracle中究竟有什么,Oracle數(shù)據(jù)庫各部分在磁盤上是如何存放的, Control File,Redo File究竟是什么意思,在數(shù)據(jù)庫中起什么作用,數(shù)據(jù)庫是怎么依賴他們運行的,還有如何對Oracle進行系統(tǒng)管理員級別的管理,如何在不停止數(shù)據(jù)庫運行 的情況下進行數(shù)據(jù)庫的更新、升級、備份等等。這些東西雖然非常有用,但在平時的開發(fā)是學不到的,這次趁著這個機會大大提升了自己Oracle的水平,感覺 非常開心。數(shù)據(jù)庫一向是我的弱項,在上大學的時候我SQL語句能力只是一般,數(shù)據(jù)庫管理配置什么基本一點都不懂,通過這次集中的培訓,我覺得自己的能力又 進一步增強了,弱項也在慢慢退卻。在三項培訓中最后進行的測試培訓我承認我沒有怎么認真去學,所以學會的也就是些測試概念,具體的測試技巧什么的還是不怎 么會。現(xiàn)在開發(fā)和測試的結合性越來越高,看來要下下功夫,以免給淘汰咯。

        提了這段時間在公司的進展,還沒說自己的學習呢,這段時間正好看見中文版的《JUnit in Action》出版了,在書的背后寫著“如果沒有看過這本書,就不要對J2EE進行單元測試”這句話。我早在去年就了解了JUnit的強大功能,再加上 Ant的話對于回歸測試是非常便利的。趁有時間,我便于3月底4月初的時候開始看這本書。當時的我看《精通EJB》第二版看了一半,發(fā)現(xiàn)其中錯誤越來越 多,而且文字也有些地方不知所云了,所以扔下不再浪費時間看那本書,專心攻讀《JUnit In Action》。憑良心說,Manning的這套In Action叢書的確很不錯,從我先前看的《Hibernate In Action》英文版就能看出來,其中對代碼的編排非常方便讀者,感覺可以很順利的看到你所想看到的代碼片斷。這套《JUnit In Action》也是一樣,博文視點的紙張還是很好的,排版使用了Manning的風格,閱讀起來很舒服,所以我讀得很快,大概就兩個多星期就讀完了這本 400多頁的書。感覺的確收獲不淺,首先,原來的自動化配置工具中只會使用一個Ant,其他的基本沒聽說過,在這本書上詳細介紹了Maven。聽過書中的 講解以及自己的試驗,的確覺得Maven功能很強大,不過感覺起來配置比Ant要麻煩,所以我自己的感覺是Ant在項目中還是會廣泛應用,不過Maven 在大型項目,特別是整個Site中有很大的用武之地,對于我們來說,使用的方法都是很簡單的,掌握如何編寫配置文件才是我們的關鍵。

    書對 JUnit與Cactus在J2EE的測試手法上給了大量的事例,給人的感覺非常好,In Action這套叢書最大的優(yōu)點就在這里,用實例代碼片斷讓你迅速了解一樣東西。在實際工作中其實JUnit應用也是比較廣泛的,特別如果采取測試驅(qū)動開 發(fā)的話,JUnit是必不可少的一部分。在TagLib測試,JSP單體測試,數(shù)據(jù)庫測試和EJB測試都是我以前根本沒有看過的東西。其實這次雖然學是學 會了,不過真正做的時候還是要有個代碼例子依葫蘆畫瓢。我想大家肯定也都有這種感覺,寫程序的時候先找一段有點相似的代碼片斷Copy過來,然后看看要修 改什么地方,真正從頭到尾自己用手寫的代碼片斷是不多的,除非你已經(jīng)爛熟于心。不過這本書快看完的時候,項目又來了。

        這次做一個企業(yè)的MIS系統(tǒng),與以往不同的是,這次客戶給了一個比較龐大的基盤,封裝了近100個Tag,基本上把各種各樣有可能遇到的操作都封裝到 Tag里面了。而且所有的畫面顯示等信息都是放在數(shù)據(jù)庫的Table中,所以這次要求不寫任何程序代碼,只是學會使用好這些Tag,然后利用這些Tag寫 出Jsp頁面。一開始的時候還真是頭疼,這些Tag一個都不明白,而且文檔不是非常齊全,Tag的Source中注釋也比較少,學習起來不是很方便。我們 一共3個人投入到這個項目的前期準備中,在第一個星期的學習中大家互相分配好個人學習的模塊,隨時互相交流。在后來的深入中發(fā)現(xiàn)這個項目的業(yè)務邏輯操作會 使用PL/SQL以及存儲過程來進行,對于我來說,PL/SQL是從來沒有做過的東西,就叫做一竅不通,于是我需要從頭開始學習PL/SQL,以及如何編 寫存儲過程。我從網(wǎng)上下了一個PL/SQL的電子書籍,然后在公司花了一天時間進行學習,個人用的是Toad來調(diào)試PL/SQL的,雖然別人喜歡用 PL/SQL Developer來進行開發(fā),不過我還是比較鐘愛Toad,而且Toad的確功能也很強大,使用起來也很方便就是了。經(jīng)過第一天的PL/SQL的學習, 基本掌握了全部語法以及存儲過程的書寫格式等等,開始能夠?qū)憣懛浅:唵蔚腜L/SQL。接下來的兩三天不斷鞏固熟練,客戶那里也發(fā)過來幾本詳細設計讓我們 練習著做一下。有了實際的詳細設計,再加上我們之間互相交流,我們提高的都很快,大概過了三四天,大家就把基本詳細設計代碼編寫完畢了,而且經(jīng)過實際鍛 煉,我的PL/SQL編寫存儲過程的水平也大大提升,已經(jīng)可以滿足開發(fā)中的需要了。

    這個項目因為如果我們一開始做的能讓客戶滿意的話,后續(xù) 的項目將會比較龐大,所以Leader決定把我們Group比較空閑的其他人也先培訓一下,讓他們有點感覺,到以后正式開發(fā)的時候也能迅速進入狀態(tài),負責 給他們培訓的任務也就交給了我。說起來是培訓,其實也就是把大概流程以及方法通過一次會議的形式告訴他們,然后把我前面已經(jīng)作好的那個畫面作為他們的作 業(yè),要他們看著設計書自己把畫面制作出來。這個時候也要放勞動節(jié)了,黃金周可以休息一個星期,想想就覺得很Happy。勞動節(jié)的時候基本沒有怎么學習,只 是先把XML-RPC仔細看了下,學會了如何去寫一個XML-RPC的應用,接著稍微看了點SOAP,看得也不錯,只是些簡單的SOAP的例子而已,那些 SOAP的復雜東西都沒有看。

        很快就五一黃金周七天放假放完,八號開始上班,上班后就開始正式做節(jié)前就定好的那個項目,這次性質(zhì)屬于試做,也就是人家先發(fā)一批設計書過來,我們?nèi)缓箝_始 Coding,大概做了一周后,我自己害了急性結膜炎,只能回家休息,這次可真的是只能休息了,眼睛覺得特別漲,不要說電腦了,連書都不能看,看了眼睛就 疼。所以在家就只能睡大覺,過了一周眼睛大概才復原,可以去公司上班了。回到公司以后,Leader通知我說我不用去做上次那個項目了,要我加入我們 Group的一個新的項目,這個項目比較大,當時還處于東京剛剛做好基本設計,我們從東京把任務接下來,準備發(fā)回來做詳細設計。我進去的時候項目才開始三 四天,基本上還沒有做什么,這次我進入了詳細設計制作小組,開始進行這個項目的詳細設計的制作。

        由于我屬于第一次在正式的項目中參與詳細設計,所以很多東西都不明白,特別是業(yè)務上面的東西,許多日語中的業(yè)務術語我根本不明白,比如什么賣切,切替,仕 入什么的。看著基本設計書,感覺跟以前看詳細設計書有很大的不同。具體的東西寫的少了,業(yè)務流程邏輯框架什么的比較多,所以需要首先把業(yè)務內(nèi)容都熟悉了, 才可能寫出詳細設計來。這次的詳細設計我也不是孤軍奮戰(zhàn),而是有一個進公司4年的老員工帶著我一起做,我的任務很輕,不過重點是學會如何去寫詳細設計,也 許下次再有一個比較大的項目,就沒有別人再帶著我,而是我自己一個人去完成詳細設計了。大概詳細設計寫了20天左右,我被通知當天把手上的一份詳細設計寫 完,第二天進入方式設計小組進行方式的設計。

    進入方式小組以后,接到的任務就是好幾個編寫DB操作方面的代碼自動化生成工具。由于這次DB 方面并沒有非常強制性的那種規(guī)約,所以SQL語句的編寫可以說比較隨意,這就給我工具的編寫帶來了很大的難度和挑戰(zhàn)。這次負責管理方式小組的人仍然是進公 司以后經(jīng)常帶著我的那位技術專家,所以也真算很巧呢。寫工具其實很對自身代碼編寫的提高也很有好處,因為首先客戶那里資料會不斷修改,這些工具你為了以后 客戶更新資料后你能順利更新工具,你需要設計一個優(yōu)良的Framework,不一定需要多么復雜的Framework,不過一定要盡量把程序各方面的耦合 度盡量降低,這樣才有利于自己對工具進行擴展。緊接著很快,項目代碼編寫開始了,我的任務算中等偏上,有2個畫面和一個批處理需要編寫,復雜度還算比較繁 一點。這次項目需要編寫JUnit程序,每天都要進行回歸測試,保證代碼Method的正確性。JUnit雖然自己會用,但是從來沒有在真正的項目中使 用,所以在真正用的時候感覺有點手足無措。以前做JUnit從來都是覺得給個參數(shù),檢測一個返回值就好了,其實不是那么回事,業(yè)務邏輯復雜了,自己需要做 大量的Stub來模擬真實的Class的返回值。設計一個好的Stub是比較困難的,特別在數(shù)據(jù)庫內(nèi)容比較豐富的時候,一張數(shù)據(jù)庫Table就有上百個 域,工作量可見一斑了。項目要到05年9月中旬才會結束,所以現(xiàn)在還在緊張的開發(fā)階段。我寫了JUnit的感覺就是難點不在如何去寫JUnit程序,而是 如何去設計測試用例。對于我們這樣不是以測試出身的程序員來說,設計測試用例是很痛苦而且很艱難的事情,估計有過相似經(jīng)驗的人肯定會表示贊同。

        當然我一邊在緊張的做項目,對于書本的學習也沒有閑著。這段時間抓緊把侯捷的Word排版藝術掃了一遍,看完覺得收獲頗豐。雖然我以前覺得我在Word上 用得挺不錯,日常的一些操作什么的我都會,不過看這本書的中間我發(fā)現(xiàn)我還是有很多地方不會的,也學到了不少東西,在以后的Word排版中會很受到好處。由 于項目用到了Spring知識,所以我也看了網(wǎng)絡上那個流傳廣泛的Spring開發(fā)指南的PDF看了一遍,感覺長了見識,對IOC以及DI有了進一步的了 解,也理解了為什么需要采用IOC以及DI。不過這個也沒有深入下去仔細看,以后等項目稍微空閑一點的時候一定再把Hibernate和Spring好好 看一下,學習人家的設計理念,提高自己能力。對了,也許最重要的是我最近在看一本書,就是《J2EE核心模式》的第二版,我當時原來準備看電子版的這本 《Core J2EE Patterns》的,不過突然在書店發(fā)現(xiàn)這本書的中文版出來了,而且譯者有熊節(jié)的名字,也就是跟侯捷一起翻譯《重構——改善既有代碼的設計》的那個譯 者,我比較相信他翻譯的水平,于是買回來看,雖然項目非常緊張,我一個月算上周末需要加班在100個小時左右的樣子,但是我相信時間是海綿里的水,只要去 擠,肯定會有的。所以我到現(xiàn)在大概看了2周的樣子,已經(jīng)看了300多頁,而且感覺自己的設計視野也開闊了許多,這本書的確很好,把J2EE中常用的一些模 塊原理都說了出來,說明了為什么這么做好,這么做如何減少了耦合性,提高了可維護性等等,總之,有1年以上J2EE開發(fā)經(jīng)驗而且覺得自己對J2EE有了比 較好的了解的開發(fā)人員我強烈推薦看這本書。看了這本書以后我都在回想以前設計的一些框架,一些模塊,覺得自己有很多地方當時設計的時候覺得很精巧,不過卻 屬于弄巧成拙,加大了模塊的耦合性,所以在修改的時候比較難于下手。

    posted @ 2005-12-26 21:43 Dion 閱讀(2592) | 評論 (8)編輯 收藏

         摘要: 華為軟件編程規(guī)范和范例 document.title="華為軟件編程規(guī)范和范例 - "+document.title 目  錄1 排版62 注釋113 標識符命名184 可讀性205 變量、結構226 函數(shù)、過程287 可測性368 程序效率409 質(zhì)量保證4410 代碼編輯、編譯、審查5011 代碼測試、維護5212 宏531 排版11-1:程序塊要采...  閱讀全文
    posted @ 2005-12-20 08:59 Dion 閱讀(2982) | 評論 (3)編輯 收藏

         摘要: AOP@Work: 用 AspectJ 進行性能監(jiān)視,第 2 部分通過裝載時織入使用Glassbox Inspector 文檔選項 將此頁作為電子郵件發(fā)送'); //--> 將此頁作為電子郵件發(fā)送未顯示需要 JavaScript 的文檔選項樣例代碼對此頁的評價幫助我們改進這些內(nèi)容級別: 高級Ron Bodkin , 創(chuàng)始人, New Aspects of Software2005 年 ...  閱讀全文
    posted @ 2005-12-19 21:53 Dion 閱讀(1264) | 評論 (0)編輯 收藏

         摘要: AOP@Work: 用 AspectJ 進行性能監(jiān)視,第 1 部分用 AspectJ 和 JMX 深入觀察 Glassbox Inspector文檔選項 將此頁作為電子郵件發(fā)送'); //--> 將此頁作為電子郵件發(fā)送未顯示需要 JavaScript 的文檔選項樣例代碼對此頁的評價幫助我們改進這些內(nèi)容級別: 中級Ron Bodkin , 創(chuàng)始人, New Aspects of Softwa...  閱讀全文
    posted @ 2005-12-19 21:52 Dion 閱讀(1105) | 評論 (0)編輯 收藏

    1.如何獲得當前文件路徑

    常用:

    字符串類型:System.getProperty("user.dir");

    綜合:

    package com.zcjl.test.base;
    import java.io.File;
    public class Test {
        public static void main(String[] args) throws Exception {
            System.out.println(
                Thread.currentThread().getContextClassLoader().getResource(""));
            System.out.println(Test.class.getClassLoader().getResource(""));
            System.out.println(ClassLoader.getSystemResource(""));
            System.out.println(Test.class.getResource(""));
            System.out.println(Test.class.getResource("/"));
            System.out.println(new File("").getAbsolutePath());
            System.out.println(System.getProperty("user.dir"));

        }
    }

    2.Web服務中

    (1).Weblogic

    WebApplication的系統(tǒng)文件根目錄是你的weblogic安裝所在根目錄。
    例如:如果你的weblogic安裝在c:\bea\weblogic700.....
    那么,你的文件根路徑就是c:\.
    所以,有兩種方式能夠讓你訪問你的服務器端的文件:
    a.使用絕對路徑:
    比如將你的參數(shù)文件放在c:\yourconfig\yourconf.properties,
    直接使用 new FileInputStream("yourconfig/yourconf.properties");
    b.使用相對路徑:
    相對路徑的根目錄就是你的webapplication的根路徑,即WEB-INF的上一級目錄,將你的參數(shù)文件放在yourwebapp\yourconfig\yourconf.properties,
    這樣使用:
    new FileInputStream("./yourconfig/yourconf.properties");
    這兩種方式均可,自己選擇。

    (2).Tomcat

    在類中輸出System.getProperty("user.dir");顯示的是%Tomcat_Home%/bin

    (3).Resin

    不是你的JSP放的相對路徑,是JSP引擎執(zhí)行這個JSP編譯成SERVLET
    的路徑為根.比如用新建文件法測試File f = new File("a.htm");
    這個a.htm在resin的安裝目錄下

    (4).如何讀相對路徑哪?

    在Java文件中getResource或getResourceAsStream均可

    例:getClass().getResourceAsStream(filePath);//filePath可以是"/filename",這里的/代表web發(fā)布根路徑下WEB-INF/classes

    (5).獲得文件真實路徑

    string  file_real_path=request.getRealPath("mypath/filename"); 

    通常使用request.getRealPath("/"); 

    3.文件操作的類

    import java.io.*;
    import java.net.*;
    import java.util.*;
    //import javax.swing.filechooser.*;
    //import org.jr.swing.filter.*;

    /**
    * 此類中封裝一些常用的文件操作。
    * 所有方法都是靜態(tài)方法,不需要生成此類的實例,
    * 為避免生成此類的實例,構造方法被申明為private類型的。
    * @since  0.1
    */

    public class FileUtil {
      /**
       * 私有構造方法,防止類的實例化,因為工具類不需要實例化。
       */
      private FileUtil() {

      }

      /**
       * 修改文件的最后訪問時間。
       * 如果文件不存在則創(chuàng)建該文件。
       * <b>目前這個方法的行為方式還不穩(wěn)定,主要是方法有些信息輸出,這些信息輸出是否保留還在考

    慮中。</b>
       * @param file 需要修改最后訪問時間的文件。
       * @since  0.1
       */
      public static void touch(File file) {
        long currentTime = System.currentTimeMillis();
        if (!file.exists()) {
          System.err.println("file not found:" + file.getName());
          System.err.println("Create a new file:" + file.getName());
          try {
            if (file.createNewFile()) {
            //  System.out.println("Succeeded!");
            }
            else {
            //  System.err.println("Create file failed!");
            }
          }
          catch (IOException e) {
          //  System.err.println("Create file failed!");
            e.printStackTrace();
          }
        }
        boolean result = file.setLastModified(currentTime);
        if (!result) {
        //  System.err.println("touch failed: " + file.getName());
        }
      }

      /**
       * 修改文件的最后訪問時間。
       * 如果文件不存在則創(chuàng)建該文件。
       * <b>目前這個方法的行為方式還不穩(wěn)定,主要是方法有些信息輸出,這些信息輸出是否保留還在考

    慮中。</b>
       * @param fileName 需要修改最后訪問時間的文件的文件名。
       * @since  0.1
       */
      public static void touch(String fileName) {
        File file = new File(fileName);
        touch(file);
      }

      /**
       * 修改文件的最后訪問時間。
       * 如果文件不存在則創(chuàng)建該文件。
       * <b>目前這個方法的行為方式還不穩(wěn)定,主要是方法有些信息輸出,這些信息輸出是否保留還在考

    慮中。</b>
       * @param files 需要修改最后訪問時間的文件數(shù)組。
       * @since  0.1
       */
      public static void touch(File[] files) {
        for (int i = 0; i < files.length; i++) {
          touch(files);
        }
      }

      /**
       * 修改文件的最后訪問時間。
       * 如果文件不存在則創(chuàng)建該文件。
       * <b>目前這個方法的行為方式還不穩(wěn)定,主要是方法有些信息輸出,這些信息輸出是否保留還在考

    慮中。</b>
       * @param fileNames 需要修改最后訪問時間的文件名數(shù)組。
       * @since  0.1
       */
      public static void touch(String[] fileNames) {
        File[] files = new File[fileNames.length];
        for (int i = 0; i < fileNames.length; i++) {
          files = new File(fileNames);
        }
        touch(files);
      }

      /**
       * 判斷指定的文件是否存在。
       * @param fileName 要判斷的文件的文件名
       * @return 存在時返回true,否則返回false。
       * @since  0.1
       */
      public static boolean isFileExist(String fileName) {
        return new File(fileName).isFile();
      }

      /**
       * 創(chuàng)建指定的目錄。
       * 如果指定的目錄的父目錄不存在則創(chuàng)建其目錄書上所有需要的父目錄。
       * <b>注意:可能會在返回false的時候創(chuàng)建部分父目錄。</b>
       * @param file 要創(chuàng)建的目錄
       * @return 完全創(chuàng)建成功時返回true,否則返回false。
       * @since  0.1
       */
      public static boolean makeDirectory(File file) {
        File parent = file.getParentFile();
        if (parent != null) {
          return parent.mkdirs();
        }
        return false;
      }

      /**
       * 創(chuàng)建指定的目錄。
       * 如果指定的目錄的父目錄不存在則創(chuàng)建其目錄書上所有需要的父目錄。
       * <b>注意:可能會在返回false的時候創(chuàng)建部分父目錄。</b>
       * @param fileName 要創(chuàng)建的目錄的目錄名
       * @return 完全創(chuàng)建成功時返回true,否則返回false。
       * @since  0.1
       */
      public static boolean makeDirectory(String fileName) {
        File file = new File(fileName);
        return makeDirectory(file);
      }

      /**
       * 清空指定目錄中的文件。
       * 這個方法將盡可能刪除所有的文件,但是只要有一個文件沒有被刪除都會返回false。
       * 另外這個方法不會迭代刪除,即不會刪除子目錄及其內(nèi)容。
       * @param directory 要清空的目錄
       * @return 目錄下的所有文件都被成功刪除時返回true,否則返回false.
       * @since  0.1
       */
      public static boolean emptyDirectory(File directory) {
        boolean result = false;
        File[] entries = directory.listFiles();
        for (int i = 0; i < entries.length; i++) {
          if (!entries.delete()) {
            result = false;
          }
        }
        return true;
      }

      /**
       * 清空指定目錄中的文件。
       * 這個方法將盡可能刪除所有的文件,但是只要有一個文件沒有被刪除都會返回false。
       * 另外這個方法不會迭代刪除,即不會刪除子目錄及其內(nèi)容。
       * @param directoryName 要清空的目錄的目錄名
       * @return 目錄下的所有文件都被成功刪除時返回true,否則返回false。
       * @since  0.1
       */
      public static boolean emptyDirectory(String directoryName) {
        File dir = new File(directoryName);
        return emptyDirectory(dir);
      }

      /**
       * 刪除指定目錄及其中的所有內(nèi)容。
       * @param dirName 要刪除的目錄的目錄名
       * @return 刪除成功時返回true,否則返回false。
       * @since  0.1
       */
      public static boolean deleteDirectory(String dirName) {
        return deleteDirectory(new File(dirName));
      }

      /**
       * 刪除指定目錄及其中的所有內(nèi)容。
       * @param dir 要刪除的目錄
       * @return 刪除成功時返回true,否則返回false。
       * @since  0.1
       */
      public static boolean deleteDirectory(File dir) {
        if ( (dir == null) || !dir.isDirectory()) {
          throw new IllegalArgumentException("Argument " + dir +
                                             " is not a directory. ");
        }

        File[] entries = dir.listFiles();
        int sz = entries.length;

        for (int i = 0; i < sz; i++) {
          if (entries.isDirectory()) {
            if (!deleteDirectory(entries)) {
              return false;
            }
          }
          else {
            if (!entries.delete()) {
              return false;
            }
          }
        }

        if (!dir.delete()) {
          return false;
        }
        return true;
      }


      /**
       * 返回文件的URL地址。
       * @param file 文件
       * @return 文件對應的的URL地址
       * @throws MalformedURLException
       * @since  0.4
       * @deprecated 在實現(xiàn)的時候沒有注意到File類本身帶一個toURL方法將文件路徑轉換為URL。
       *             請使用File.toURL方法。
       */
      public static URL getURL(File file) throws MalformedURLException {
        String fileURL = "file:/" + file.getAbsolutePath();
        URL url = new URL(fileURL);
        return url;
      }

      /**
       * 從文件路徑得到文件名。
       * @param filePath 文件的路徑,可以是相對路徑也可以是絕對路徑
       * @return 對應的文件名
       * @since  0.4
       */
      public static String getFileName(String filePath) {
        File file = new File(filePath);
        return file.getName();
      }

      /**
       * 從文件名得到文件絕對路徑。
       * @param fileName 文件名
       * @return 對應的文件路徑
       * @since  0.4
       */
      public static String getFilePath(String fileName) {
        File file = new File(fileName);
        return file.getAbsolutePath();
      }

      /**
       * 將DOS/Windows格式的路徑轉換為UNIX/Linux格式的路徑。
       * 其實就是將路徑中的"\"全部換為"/",因為在某些情況下我們轉換為這種方式比較方便,
       * 某中程度上說"/"比"\"更適合作為路徑分隔符,而且DOS/Windows也將它當作路徑分隔符。
       * @param filePath 轉換前的路徑
       * @return 轉換后的路徑
       * @since  0.4
       */
      public static String toUNIXpath(String filePath) {
        return filePath.replace('\\', '/');
      }

      /**
       * 從文件名得到UNIX風格的文件絕對路徑。
       * @param fileName 文件名
       * @return 對應的UNIX風格的文件路徑
       * @since  0.4
       * @see #toUNIXpath(String filePath) toUNIXpath
       */
      public static String getUNIXfilePath(String fileName) {
        File file = new File(fileName);
        return toUNIXpath(file.getAbsolutePath());
      }

      /**
       * 得到文件的類型。
       * 實際上就是得到文件名中最后一個“.”后面的部分。
       * @param fileName 文件名
       * @return 文件名中的類型部分
       * @since  0.5
       */
      public static String getTypePart(String fileName) {
        int point = fileName.lastIndexOf('.');
        int length = fileName.length();
        if (point == -1 || point == length - 1) {
          return "";
        }
        else {
          return fileName.substring(point + 1, length);
        }
      }

      /**
       * 得到文件的類型。
       * 實際上就是得到文件名中最后一個“.”后面的部分。
       * @param file 文件
       * @return 文件名中的類型部分
       * @since  0.5
       */
      public static String getFileType(File file) {
        return getTypePart(file.getName());
      }

      /**
       * 得到文件的名字部分。
       * 實際上就是路徑中的最后一個路徑分隔符后的部分。
       * @param fileName 文件名
       * @return 文件名中的名字部分
       * @since  0.5
       */
      public static String getNamePart(String fileName) {
        int point = getPathLsatIndex(fileName);
        int length = fileName.length();
        if (point == -1) {
          return fileName;
        }
        else if (point == length - 1) {
          int secondPoint = getPathLsatIndex(fileName, point - 1);
          if (secondPoint == -1) {
            if (length == 1) {
              return fileName;
            }
            else {
              return fileName.substring(0, point);
            }
          }
          else {
            return fileName.substring(secondPoint + 1, point);
          }
        }
        else {
          return fileName.substring(point + 1);
        }
      }

      /**
       * 得到文件名中的父路徑部分。
       * 對兩種路徑分隔符都有效。
       * 不存在時返回""。
       * 如果文件名是以路徑分隔符結尾的則不考慮該分隔符,例如"/path/"返回""。
       * @param fileName 文件名
       * @return 父路徑,不存在或者已經(jīng)是父目錄時返回""
       * @since  0.5
       */
      public static String getPathPart(String fileName) {
        int point = getPathLsatIndex(fileName);
        int length = fileName.length();
        if (point == -1) {
          return "";
        }
        else if (point == length - 1) {
          int secondPoint = getPathLsatIndex(fileName, point - 1);
          if (secondPoint == -1) {
            return "";
          }
          else {
            return fileName.substring(0, secondPoint);
          }
        }
        else {
          return fileName.substring(0, point);
        }
      }

      /**
       * 得到路徑分隔符在文件路徑中首次出現(xiàn)的位置。
       * 對于DOS或者UNIX風格的分隔符都可以。
       * @param fileName 文件路徑
       * @return 路徑分隔符在路徑中首次出現(xiàn)的位置,沒有出現(xiàn)時返回-1。
       * @since  0.5
       */
      public static int getPathIndex(String fileName) {
        int point = fileName.indexOf('/');
        if (point == -1) {
          point = fileName.indexOf('\\');
        }
        return point;
      }

      /**
       * 得到路徑分隔符在文件路徑中指定位置后首次出現(xiàn)的位置。
       * 對于DOS或者UNIX風格的分隔符都可以。
       * @param fileName 文件路徑
       * @param fromIndex 開始查找的位置
       * @return 路徑分隔符在路徑中指定位置后首次出現(xiàn)的位置,沒有出現(xiàn)時返回-1。
       * @since  0.5
       */
      public static int getPathIndex(String fileName, int fromIndex) {
        int point = fileName.indexOf('/', fromIndex);
        if (point == -1) {
          point = fileName.indexOf('\\', fromIndex);
        }
        return point;
      }

      /**
       * 得到路徑分隔符在文件路徑中最后出現(xiàn)的位置。
       * 對于DOS或者UNIX風格的分隔符都可以。
       * @param fileName 文件路徑
       * @return 路徑分隔符在路徑中最后出現(xiàn)的位置,沒有出現(xiàn)時返回-1。
       * @since  0.5
       */
      public static int getPathLsatIndex(String fileName) {
        int point = fileName.lastIndexOf('/');
        if (point == -1) {
          point = fileName.lastIndexOf('\\');
        }
        return point;
      }

      /**
       * 得到路徑分隔符在文件路徑中指定位置前最后出現(xiàn)的位置。
       * 對于DOS或者UNIX風格的分隔符都可以。
       * @param fileName 文件路徑
       * @param fromIndex 開始查找的位置
       * @return 路徑分隔符在路徑中指定位置前最后出現(xiàn)的位置,沒有出現(xiàn)時返回-1。
       * @since  0.5
       */
      public static int getPathLsatIndex(String fileName, int fromIndex) {
        int point = fileName.lastIndexOf('/', fromIndex);
        if (point == -1) {
          point = fileName.lastIndexOf('\\', fromIndex);
        }
        return point;
      }

      /**
       * 將文件名中的類型部分去掉。
       * @param filename 文件名
       * @return 去掉類型部分的結果
       * @since  0.5
       */
      public static String trimType(String filename) {
        int index = filename.lastIndexOf(".");
        if (index != -1) {
          return filename.substring(0, index);
        }
        else {
          return filename;
        }
      }
      /**
       * 得到相對路徑。
       * 文件名不是目錄名的子節(jié)點時返回文件名。
       * @param pathName 目錄名
       * @param fileName 文件名
       * @return 得到文件名相對于目錄名的相對路徑,目錄下不存在該文件時返回文件名
       * @since  0.5
       */
      public static String getSubpath(String pathName,String fileName) {
        int index = fileName.indexOf(pathName);
        if (index != -1) {
          return fileName.substring(index + pathName.length() + 1);
        }
        else {
          return fileName;
        }
      }

    }
     4.遺留問題

    目前new FileInputStream()只會使用絕對路徑,相對沒用過,因為要相對于web服務器地址,比較麻煩

    還不如寫個配置文件來的快哪

    5.按Java文件類型分類讀取配置文件

    配 置文件是應用系統(tǒng)中不可缺少的,可以增加程序的靈活性。java.util.Properties是從jdk1.2就有的類,一直到現(xiàn)在都支持load ()方法,jdk1.4以后save(output,string) ->store(output,string)。如果只是單純的讀,根本不存在煩惱的問題。web層可以通過 Thread.currentThread().getContextClassLoader().
    getResourceAsStream("xx.properties") 獲取;Application可以通過new FileInputStream("xx.properties");直接在classes一級獲取。關鍵是有時我們需要通過web修改配置文件,我們不 能將路徑寫死了。經(jīng)過測試覺得有以下心得:

    1.servlet中讀寫。如果運用Struts 或者Servlet可以直接在初始化參數(shù)中配置,調(diào)用時根據(jù)servlet的getRealPath("/")獲取真實路徑,再根據(jù)String file = this.servlet.getInitParameter("abc");獲取相對的WEB-INF的相對路徑。
    例:
    InputStream input = Thread.currentThread().getContextClassLoader().
    getResourceAsStream("abc.properties");
    Properties prop = new Properties();
    prop.load(input);
    input.close();
    OutputStream out = new FileOutputStream(path);
    prop.setProperty("abc", “test");
    prop.store(out, “–test–");
    out.close();

    2.直接在jsp中操作,通過jsp內(nèi)置對象獲取可操作的絕對地址。
    例:
    // jsp頁面
    String path = pageContext.getServletContext().getRealPath("/");
    String realPath = path+"/WEB-INF/classes/abc.properties";

    //java 程序
    InputStream in = getClass().getClassLoader().getResourceAsStream("abc.properties"); // abc.properties放在webroot/WEB-INF/classes/目錄下
    prop.load(in);
    in.close();

    OutputStream out = new FileOutputStream(path); // path為通過頁面?zhèn)魅氲穆窂?br>prop.setProperty("abc", “abcccccc");
    prop.store(out, “–test–");
    out.close();

    3.只通過Java程序操作資源文件
    InputStream in = new FileInputStream("abc.properties"); // 放在classes同級

    OutputStream out = new FileOutputStream("abc.properties");

    posted @ 2005-12-16 22:53 Dion 閱讀(31501) | 評論 (8)編輯 收藏

    六種異常處理的陋習

    你覺得自己是一個Java專家嗎?是否肯定自己已經(jīng)全面掌握了Java的異常處理機制?在下面這段代碼中,你能夠迅速找出異常處理的六個問題嗎?

    1 OutputStreamWriter out = ...
    2 java.sql.Connection conn = ...
    3 try { // ⑸
    4  Statement stat = conn.createStatement();
    5  ResultSet rs = stat.executeQuery(
    6   "select uid, name from user");
    7  while (rs.next())
    8  {
    9   out.println("ID:" + rs.getString("uid") // ⑹
    10    ",姓名:" + rs.getString("name"));
    11  }
    12  conn.close(); // ⑶
    13  out.close();
    14 }
    15 catch(Exception ex) // ⑵
    16 {
    17  ex.printStackTrace(); //⑴,⑷
    18 }


      作為一個Java程序員,你至少應該能夠找出兩個問題。但是,如果你不能找出全部六個問題,請繼續(xù)閱讀本文。

      本文討論的不是Java異常處理的一般性原則,因為這些原則已經(jīng)被大多數(shù)人熟知。我們要做的是分析各種可稱為“反例”(anti-pattern)的違背優(yōu)秀編碼規(guī)范的常見壞習慣,幫助讀者熟悉這些典型的反面例子,從而能夠在實際工作中敏銳地察覺和避免這些問題。

      反例之一:丟棄異常

      代碼:15行-18行。

       這段代碼捕獲了異常卻不作任何處理,可以算得上Java編程中的殺手。從問題出現(xiàn)的頻繁程度和禍害程度來看,它也許可以和C/C++程序的一個惡名遠播 的問題相提并論??不檢查緩沖區(qū)是否已滿。如果你看到了這種丟棄(而不是拋出)異常的情況,可以百分之九十九地肯定代碼存在問題(在極少數(shù)情況下,這段代 碼有存在的理由,但最好加上完整的注釋,以免引起別人誤解)。

      這段代碼的錯誤在于,異常(幾乎)總是意味著某些事情不對勁了,或 者說至少發(fā)生了某些不尋常的事情,我們不應該對程序發(fā)出的求救信號保持沉默和無動于衷。調(diào)用一下printStackTrace算不上“處理異常”。不 錯,調(diào)用printStackTrace對調(diào)試程序有幫助,但程序調(diào)試階段結束之后,printStackTrace就不應再在異常處理模塊中擔負主要責 任了。

      丟棄異常的情形非常普遍。打開JDK的ThreadDeath類的文檔,可以看到下面這段說明:“特別地,雖然出現(xiàn) ThreadDeath是一種‘正常的情形’,但ThreadDeath類是Error而不是Exception的子類,因為許多應用會捕獲所有的 Exception然后丟棄它不再理睬。”這段話的意思是,雖然ThreadDeath代表的是一種普通的問題,但鑒于許多應用會試圖捕獲所有異常然后不 予以適當?shù)奶幚恚訨DK把ThreadDeath定義成了Error的子類,因為Error類代表的是一般的應用不應該去捕獲的嚴重問題。可見,丟棄 異常這一壞習慣是如此常見,它甚至已經(jīng)影響到了Java本身的設計。

      那么,應該怎樣改正呢?主要有四個選擇:

      1、處理異常。針對該異常采取一些行動,例如修正問題、提醒某個人或進行其他一些處理,要根據(jù)具體的情形確定應該采取的動作。再次說明,調(diào)用printStackTrace算不上已經(jīng)“處理好了異常”。

      2、重新拋出異常。處理異常的代碼在分析異常之后,認為自己不能處理它,重新拋出異常也不失為一種選擇。

      3、把該異常轉換成另一種異常。大多數(shù)情況下,這是指把一個低級的異常轉換成應用級的異常(其含義更容易被用戶了解的異常)。

      4、不要捕獲異常。

      結論一:既然捕獲了異常,就要對它進行適當?shù)奶幚怼2灰东@異常之后又把它丟棄,不予理睬。

      反例之二:不指定具體的異常

      代碼:15行。

      許多時候人們會被這樣一種“美妙的”想法吸引:用一個catch語句捕獲所有的異常。最常見的情形就是使用catch(Exception ex)語句。但實際上,在絕大多數(shù)情況下,這種做法不值得提倡。為什么呢?

       要理解其原因,我們必須回顧一下catch語句的用途。catch語句表示我們預期會出現(xiàn)某種異常,而且希望能夠處理該異常。異常類的作用就是告訴 Java編譯器我們想要處理的是哪一種異常。由于絕大多數(shù)異常都直接或間接從java.lang.Exception派生,catch (Exception ex)就相當于說我們想要處理幾乎所有的異常。

      再來看看前面的代碼例子。我們真正想要捕獲的異常是什么 呢?最明顯的一個是SQLException,這是JDBC操作中常見的異常。另一個可能的異常是IOException,因為它要操作 OutputStreamWriter。顯然,在同一個catch塊中處理這兩種截然不同的異常是不合適的。如果用兩個catch塊分別捕獲 SQLException和IOException就要好多了。這就是說,catch語句應當盡量指定具體的異常類型,而不應該指定涵蓋范圍太廣的 Exception類。

      另一方面,除了這兩個特定的異常,還有其他許多異常也可能出現(xiàn)。例如,如果由于某種原因, executeQuery返回了null,該怎么辦?答案是讓它們繼續(xù)拋出,即不必捕獲也不必處理。實際上,我們不能也不應該去捕獲可能出現(xiàn)的所有異常, 程序的其他地方還有捕獲異常的機會??直至最后由JVM處理。

      結論二:在catch語句中盡可能指定具體的異常類型,必要時使用多個catch。不要試圖處理所有可能出現(xiàn)的異常。

      反例之三:占用資源不釋放

      代碼:3行-14行。

      異常改變了程序正常的執(zhí)行流程。這個道理雖然簡單,卻常常被人們忽視。如果程序用到了文件、Socket、JDBC連接之類的資源,即使遇到了異常,也要正確釋放占用的資源。為此,Java提供了一個簡化這類操作的關鍵詞finally。

      finally是樣好東西:不管是否出現(xiàn)了異常,F(xiàn)inally保證在try/catch/finally塊結束之前,執(zhí)行清理任務的代碼總是有機會執(zhí)行。遺憾的是有些人卻不習慣使用finally。

      當然,編寫finally塊應當多加小心,特別是要注意在finally塊之內(nèi)拋出的異常??這是執(zhí)行清理任務的最后機會,盡量不要再有難以處理的錯誤。

      結論三:保證所有資源都被正確釋放。充分運用finally關鍵詞。

    反例之四:不說明異常的詳細信息

      代碼:3行-18行。

      仔細觀察這段代碼:如果循環(huán)內(nèi)部出現(xiàn)了異常,會發(fā)生什么事情?我們可以得到足夠的信息判斷循環(huán)內(nèi)部出錯的原因嗎?不能。我們只能知道當前正在處理的類發(fā)生了某種錯誤,但卻不能獲得任何信息判斷導致當前錯誤的原因。

      printStackTrace的堆棧跟蹤功能顯示出程序運行到當前類的執(zhí)行流程,但只提供了一些最基本的信息,未能說明實際導致錯誤的原因,同時也不易解讀。

      因此,在出現(xiàn)異常時,最好能夠提供一些文字信息,例如當前正在執(zhí)行的類、方法和其他狀態(tài)信息,包括以一種更適合閱讀的方式整理和組織printStackTrace提供的信息。

      結論四:在異常處理模塊中提供適量的錯誤原因信息,組織錯誤信息使其易于理解和閱讀。

      反例之五:過于龐大的try塊

      代碼:3行-14行。

       經(jīng)常可以看到有人把大量的代碼放入單個try塊,實際上這不是好習慣。這種現(xiàn)象之所以常見,原因就在于有些人圖省事,不愿花時間分析一大塊代碼中哪幾行 代碼會拋出異常、異常的具體類型是什么。把大量的語句裝入單個巨大的try塊就象是出門旅游時把所有日常用品塞入一個大箱子,雖然東西是帶上了,但要找出 來可不容易。

      一些新手常常把大量的代碼放入單個try塊,然后再在catch語句中聲明Exception,而不是分離各個可能出現(xiàn)異常的段落并分別捕獲其異常。這種做法為分析程序拋出異常的原因帶來了困難,因為一大段代碼中有太多的地方可能拋出Exception。

      結論五:盡量減小try塊的體積。

      反例之六:輸出數(shù)據(jù)不完整

      代碼:7行-11行。

       不完整的數(shù)據(jù)是Java程序的隱形殺手。仔細觀察這段代碼,考慮一下如果循環(huán)的中間拋出了異常,會發(fā)生什么事情。循環(huán)的執(zhí)行當然是要被打斷的,其次, catch塊會執(zhí)行??就這些,再也沒有其他動作了。已經(jīng)輸出的數(shù)據(jù)怎么辦?使用這些數(shù)據(jù)的人或設備將收到一份不完整的(因而也是錯誤的)數(shù)據(jù),卻得不到 任何有關這份數(shù)據(jù)是否完整的提示。對于有些系統(tǒng)來說,數(shù)據(jù)不完整可能比系統(tǒng)停止運行帶來更大的損失。

      較為理想的處置辦法是向輸出設備寫一些信息,聲明數(shù)據(jù)的不完整性;另一種可能有效的辦法是,先緩沖要輸出的數(shù)據(jù),準備好全部數(shù)據(jù)之后再一次性輸出。

      結論六:全面考慮可能出現(xiàn)的異常以及這些異常對執(zhí)行流程的影響。

      改寫后的代碼

      根據(jù)上面的討論,下面給出改寫后的代碼。也許有人會說它稍微有點?嗦,但是它有了比較完備的異常處理機制。

    OutputStreamWriter out = ...
    java.sql.Connection conn = ...
    try {
     Statement stat = conn.createStatement();
     ResultSet rs = stat.executeQuery(
      "select uid, name from user");
     while (rs.next())
     {
      out.println("ID:" + rs.getString("uid") + ",姓名: " + rs.getString("name"));
     }
    }
    catch(SQLException sqlex)
    {
     out.println("警告:數(shù)據(jù)不完整");
     throw new ApplicationException("讀取數(shù)據(jù)時出現(xiàn)SQL錯誤", sqlex);
    }
    catch(IOException ioex)
    {
     throw new ApplicationException("寫入數(shù)據(jù)時出現(xiàn)IO錯誤", ioex);
    }
    finally
    {
     if (conn != null) {
      try {
       conn.close();
      }
      catch(SQLException sqlex2)
      {
       System.err(this.getClass().getName() + ".mymethod - 不能關閉數(shù)據(jù)庫連接: " + sqlex2.toString());
      }
     }

     if (out != null) {
      try {
       out.close();
      }
      catch(IOException ioex2)
      {
       System.err(this.getClass().getName() + ".mymethod - 不能關閉輸出文件" + ioex2.toString());
      }
     }
    }

      本文的結論不是放之四海皆準的教條,有時常識和經(jīng)驗才是最好的老師。如果你對自己的做法沒有百分之百的信心,務必加上詳細、全面的注釋。

       另一方面,不要笑話這些錯誤,不妨問問你自己是否真地徹底擺脫了這些壞習慣。即使最有經(jīng)驗的程序員偶爾也會誤入歧途,原因很簡單,因為它們確確實實帶來 了“方便”。所有這些反例都可以看作Java編程世界的惡魔,它們美麗動人,無孔不入,時刻誘惑著你。也許有人會認為這些都屬于雞皮蒜毛的小事,不足掛 齒,但請記住:勿以惡小而為之,勿以善小而不為。



    Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=553341

    posted @ 2005-12-16 22:52 Dion 閱讀(669) | 評論 (1)編輯 收藏

    在SPRING中實現(xiàn)事務暫停

    作者:Juergen Hoeller

    譯者:xMatrix





    版權聲明:任何獲得Matrix授權的網(wǎng)站,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
    作者:Juergen Hoeller;xMatrix
    原文地址:http://dev2dev.bea.com/pub/a/2005/07/spring_transactions.html
    中文地址:http://www.matrix.org.cn/resource/article/44/44054_Transaction+Spring.html
    關鍵詞: Transaction Suspension Spring

    摘要

    Spring 框架是一個流行的基于輕量級控制反轉容器的Java/J2EE應用框架,尤其在數(shù)據(jù)訪問和事務管理方面的能力是眾所周知的。Spring的聲明性事務分離 可以應用到任何POJO目標對象,并且包含所有EJB基于容器管理事務中的已聲明事務。后臺的事務管理器支持簡單的基于JDBC的事務和全功能的基于 JTA的J2EE事務。

    這篇文章詳細的討論了Spring的事務管理特性。重點是如何在使用JTA作為后臺事務策略的基礎上讓POJO利 用Spring的聲明性事務,這也顯示了Spring的事務服務可以無縫地與J2EE服務器(如BEA WebLogic Server的事務協(xié)調(diào)器)的事務協(xié)調(diào)器進行交互,作為EJB CMT傳統(tǒng)事務分離方式的一個替代者。

    POJO的聲明性事務

    作為Spring聲明性事務分離方式的樣例,讓我們來看一下Spring的樣例應用PetClinic的中心服務外觀中的配置:
    清單1:
    <bean id="dataSource" 
       class="org.springframework.jndi.JndiObjectFactoryBean">
         <property name="jndiName">
            <value>java:comp/env/jdbc/petclinic</value>
         </property>
    </bean>

    <bean id="transactionManager"
       class="org.springframework.transaction.jta.JtaTransactionManager"/>

    <bean id="clinicTarget"
       class="org.springframework.samples.petclinic.jdbc.JdbcClinic">
        <property name="dataSource"><ref bean="dataSource"/></property>
    </bean>

    <bean id="clinic"
       class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager"><ref bean="transactionManager"/></property>
        <property name="target"><ref bean="clinicTarget"/></property>
        <property name="transactionAttributes">
            <props>
                <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="store*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>


    他遵循Spring的標準XMLBean定義格式。定義了:
    1、一個DataSource引用,指向一個JNDI位置—在J2EE服務器管理下這將從JNDI環(huán)境中獲取特定的DataSource。
    2、一個應用服務實現(xiàn)—這是一個POJO,封裝了業(yè)務和數(shù)據(jù)訪問邏輯。在這里實現(xiàn)了應用中的Clinic服務接口。
    3、一個應用服務的事務代理—這個代理為目標服務定義了事務屬性,匹配特定的方法名模式并為之創(chuàng)建相應的事務。在實際的事務管理中,代理指向一個PlatformTransactionManager實現(xiàn)。
    注意:除了顯式的代理定義,Spring還支持自動代理機制和通過Commons Attributes或J2SE 5.0注解實現(xiàn)源程序級的元數(shù)據(jù)使用。這些可選方法的討論超過了本文的范圍。可以參考Spring的文檔來了解相關細節(jié)。


    業(yè)務接口和業(yè)務實現(xiàn)是特定于應用的并且不需要關心Spring或者Spring的事務管理。普通Java對象可以作為服務的目標對象,而且任何普通Java接口可以作為服務的接口。下面是一個Clinic接口的示例:
    清單2:
    public interface Clinic {
        Pet loadPet(int id);
        void storePet(Pet pet);
        ...
    }



    這個接口的實現(xiàn)如下顯示,假設他使用JDBC來執(zhí)行必要的數(shù)據(jù)訪問。他通過bean屬性的設置方法來獲取JDBC的DataSource;這與上面的配置中的dataSource屬性定義相對應。
    清單3:
    public class JdbcClinic implements Clinic {

        private DataSource dataSource;

        public void setDataSource(DataSource dataSource) {
          this.dataSource = dataSource;
        }

        public Pet loadPet(int id) {
          try {
              Connection con = this.dataSource.getConnection();
              ...
          }
          catch (SQLException ex) {
            ...
          }
        }

        public void storePet(Pet pet) {
          try {
              Connection con = this.dataSource.getConnection();
              ...
          }
          catch (SQLException ex) {
            ...
          }
        }

        ...
    }



    如你所見,代碼相當直接。我們使用一個簡單的Java對象,而事務管理由事務代理來處理,這個我們會在下面討論。
    注意在PetClinic示例應用中實際的基于JDBC的Clinic實現(xiàn)利用了Spring的JDBC支持類來避免直接使用JDBC的API。雖然Spring的事務管理也可以與普通的基于JDBC實現(xiàn)一起工作,就向上面的示例。

    定義事務代理
    除了JdbcClinic實例以外,配置中也定義了一個事務代理。如果愿意這個代理所暴露的實際接口也可以顯式定義。默認情況下,所有由目標對象實現(xiàn)的接口都暴露出來,在這個例子中就是應用的Clinic服務接口。

    從客戶端的觀點來看,"clinic" bean只是這個應用的Clinic接口的實現(xiàn)。客戶端不需要知道這會被一個事務代理所處理。這就是接口的能力:一個直接的目標對象的引用可以容易的被一個實現(xiàn)相同接口的代理所代替—在這兒就是一個隱式創(chuàng)建事務的代理。
    代理的具體事務行為會由為根據(jù)特定的方法或方法命名模式而定義的事務屬性來驅(qū)動,就像下面的例子所示:
    清單3:
    <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
    <prop key="store*">PROPAGATION_REQUIRED</prop>


    Key屬性決定代理將為方法提供什么樣的事務行為。這個屬性的最重要部分就是事務傳播行為。下面是一些可選的屬性值:
    1、PROPAGATION_REQUIRED --支持當前的事務,如果不存在就創(chuàng)建一個新的。這是最常用的選擇。
    2、PROPAGATION_SUPPORTS --支持當前的事務,如果不存在就不使用事務。
    3、PROPAGATION_MANDATORY --支持當前的事務,如果不存在就拋出異常。
    4、PROPAGATION_REQUIRES_NEW --創(chuàng)建一個新的事務,并暫停當前的事務(如果存在)。
    5、PROPAGATION_NOT_SUPPORTED --不使用事務,并暫停當前的事務(如果存在)。
    6、PROPAGATION_NEVER --不使用事務,如果當前存在事務就拋出異常。
    7、PROPAGATION_NESTED --如果當前存在事務就作為嵌入事務執(zhí)行,否則與PROPAGATION_REQUIRED類似。

    前6 個事務策略與EJB的CMT類似,而且使用相同的常量名,因此對EJB開發(fā)人員來說是很親切的。第7個策略PROPAGATION_NESTED是 Spring提供的一個變體:他需要事務管理器(如DataSourceTransactionManager)提供類似JDBC3.0那樣的保存點 API來嵌套事務行為或者通過
    JTA支持嵌套事務。

    事務屬性中的readOnly標識指示相應的事務應該作為一個只讀事務來優(yōu)化。這是一個優(yōu)化提示:一些事務策略在這種情況下可以得到很好的性能優(yōu)化,如使用ORM工具如Hibernate或TopLink時避免臟數(shù)據(jù)檢查(“flush”嘗試)。

    在事務屬性中還有一個“timeout”選項來定義事務的超時秒數(shù)。在JTA中,這個屬性會簡單地傳遞給J2EE服務器的事務協(xié)調(diào)器并被正確地解釋。

    使用事務代理
    在 運行時,客戶端會取得一個“clinic”引用并轉換為Clinic接口,然后調(diào)用如loadPet或storePet方法。這就隱式地使用了 Spring的事務代理,通過“事務解釋器”在目標對象中注冊;這樣一個新的事務就創(chuàng)建了,然后具體的工作就會代理給JdbcClinic的目標方法。
    圖1示例了一個使用“建議鏈”并到達最后目標的AOP代理的潛在概念。在這個示例中,唯一的建議是一個事務解釋器用來包裝目標方法的事務行為。這是一種用來在聲明性事務功能下使用的基于代理的AOP。



    Figure 1. An AOP proxy with an advisor chain and a target at the end

    例如,一個PetClinic應用的WEB層組件可以執(zhí)行ServletContext定位來獲取Spring WebApplicationContext的引用并且獲取受管理的“clinic”BEAN:
    清單4:
    WebApplicationContext ctx = 
       WebApplicationContexUtils.getWebApplicationContext(servletContext);
    Clinic clinic = (Clinic) ctx.getBean("clinic);

    Pet pet = new Pet();
    pet.setName("my new cat");

    clinic.storePet(pet);


    在 調(diào)用storePet()之前,Spring的事務代理隱式地創(chuàng)建一個事務。當storePet()調(diào)用返回時,事務將提交或回滾。缺省情況下任何 RuntimeException或Error將導致回滾。實際的提交或回滾可以是可以定義的:Spring的事務屬性支持“回滾規(guī)則”的概念。

    例如,我們可以可以引入一個強制的PetClinicException并且告訴事務代理在拋出異常時回滾:
    清單5:
    <prop key="load*">PROPAGATION_REQUIRED,readOnly,-PetClinicException</prop>
    <prop key="store*">PROPAGATION_REQUIRED,-PetClinicException</prop>


    這兒也有一個類似的“提交規(guī)則”語法,指示特定的異常將觸發(fā)一次提交。
    注 意上面示例的顯式定位引用的方法只是一種訪問受Spring管理BEAN的方法的變化,可以用在任何WEB資源如servlet或filter。在構建基 于Spring自身的MVC框架時,BEAN可以直接被注射到WEB控制器中。當然也支持在如Struts, WebWork, JSF, and Tapestry框架中訪問Spring管理BEAN。詳情可以參考Spring的文檔。

    PlatformTransactionManager策略

    Spring 事務支持的核心接口是org.springframework.transaction.PlatformTransactionManager。所有 Spring的事務分離功能都會委托給PlatformTransactionManager(傳給相應的TransactionDefinition實 例)來做實際的事務執(zhí)行。雖然PlatformTransactionManager接口可以直接調(diào)用,但通常應用只需要配置一個具體的事務管理器并且通 過聲明性事務來分離事務。

    Spring提供幾種不同的PlatformTransactionManager實現(xiàn),分為如下兩個類別:
    1、 本地事務策略—支持單一資源的事務(通常是單個數(shù)據(jù)庫),其包括 org.springframework.jdbc.datasource.DataSourceTransactionManager和 org.springframework.orm.hibernate.HibernateTransactionManager。
    2、全局事務管理—支持可能跨越多個資源的全局事務。其相應的類為org.springframework.transaction.jta.JtaTransactionManager,將事務委托給遵循JTA規(guī)范的事務協(xié)調(diào)器(通常為J2EE服務器,但不是強制的)。

    PlatformTransactionManager 抽象的主要價值在于應用不再被綁定在特定的事務管理環(huán)境。相反,事務策略可以很容易地切換—通過選擇不同的 PlatformTransactionManager實現(xiàn)類。這就使得應用代碼與聲明事務分離保持一致,而不需要考慮應用組件所使用的環(huán)境了。

    例 如,應用的初始版本可能布署在Tomcat上,與單個Oracle數(shù)據(jù)庫交互。這可以方便地利用Spring的事務分離特性,只要選擇基于JDBC的 DataSourceTransactionManager作為使用的事務策略。Spring會分離事務,而JDBC驅(qū)動會執(zhí)行相應的原始JDBC事務。

    相 同應用的另一個版本可能會布署在WebLogic服務器上,使用兩個Oracle數(shù)據(jù)庫。應用代碼和事務分離不需要改變。唯一不同的是選擇作為 JtaTransactionManager事務策略,讓Spring來分離事務而WebLogic服務器的事務協(xié)調(diào)器來執(zhí)行事務。

    JTA UserTransaction與JTA TransactionManager比較
    讓我們來看一下Spring對JTA支持的細節(jié)。雖然并非經(jīng)常需要考慮這個細節(jié)但了解相關的細節(jié)還有必要的。對簡單的用例如前面章節(jié)的示例,標準的JtaTransactionManager定義已經(jīng)足夠了,
    缺 省的Spring JtaTransactionManager設置會從標準JNDI位置(J2EE規(guī)范所定義的java:comp/UserTransaction)獲取 JTA的javax.transaction.UserTransaction對象。這對大部分標準J2EE環(huán)境來說已經(jīng)足夠了。

    然而, 缺省的JtaTransactionManager不能執(zhí)行事務暫停(也就是說不支持PROPAGATION_REQUIRES_NEW和 PROPAGATION_NOT_SUPPORTED)。原因就在于標準的JTA UserTransaction接口不支持事務的暫停和恢復,而只支持開始和完成新的事務。

    為了實現(xiàn)事務的暫停,需要一個 javax.transaction.TransactionManager實例,他提供了JTA定義的標準的暫停和恢復方法。不幸的是,J2EE沒有為 JTA TransactionManager定義標準的JNDI位置!因此,我們需要使用廠商自己的定位機制。
    清單6:
    <bean id="transactionManager" 
       class="org.springframework.transaction.jta.JtaTransactionManager">
         <property name="transactionManagerName">
            <value>vendorSpecificJndiLocation</value>
         </property>
    </bean>



    J2EE 本質(zhì)上沒有考慮將JTA TransactionManager接口作為公共API的一部分。JTA規(guī)范自身定義了將TransactionManager接口作為容器集成的想 法。雖然這是可以理解的,但是JTA TransactionManager的標準JNDI位置還是可以增加一定的價值,特別是對輕量級容器如Spring,這樣任何J2EE服務器就可以用統(tǒng) 一的方式來定位JTA TransactionManager了。

    不僅Spring的JtaTransactionManager可以從 訪問中獲益,O/R映射工具如Hibernate, Apache OJB, and Kodo JDO也能得到好處,因為他們需要在JTA環(huán)境中執(zhí)行緩存同步的能力(釋放緩存意味著JTA事務的完成)。這種注冊事務同步的能力只有JTA TransactionManager接口才能提供,而UserTransaction是處理不了的。因此,這些工具都需要實現(xiàn)自己的 TransactionManager定位器。

    為JTA TransactionManager定義標準的JNDI位置是許多底層軟件供應商最期望J2EE實現(xiàn)的功能。如果J2EE5.0的規(guī)范制定團隊能夠認識 到這個特性的重要性就太好了。幸運地是,高級J2EE服務器如WebLogic Server已經(jīng)考慮將JTA TransactionManager作為公共的API包含在擴展功能中。

    在WebLogic JTA中實現(xiàn)Spring的事務分離
    在WebLogic Server中,JTA TransactionManager官方的JNDI位置定義為javax.transaction.TransactionManager。這個值可以 在Spring的JtaTransactionManager中作為“transactionManagerName”使用。原則上這樣就可以在 WebLogic's JTA系統(tǒng)中實現(xiàn)事務暫停了,也就是說支持PROPAGATION_REQUIRES_NEW和PROPAGATION_NOT_SUPPORTED行 為。

    除了標準的JtaTransactionManager和其支持的通用配置選項外,Spring還提供了一個專用的WebLogicJtaTransactionManager適配器來直接利用WebLogic的JTA擴展。

    在享受自動探測WebLogic的JTA TransactionManager的便利之外,他提供超越標準JTA的三個重要特性:
    1、事務命名—暴露出Spring的事務名給WebLogic Server,使得Spring事務在WebLogic的事務監(jiān)聽器可見。缺省的,Spring會使用聲明性事務的完整方法名。
    2、每事務隔離級別—將Spring事務屬性中定義的隔離級別應用到WebLogic JTA事務中。這使得每個事務都可以定義數(shù)據(jù)庫的隔離級別,而這是標準JTA所不支持的。
    3、強制事務恢復—即使在暫停的事務被標識為回滾時也可以恢復。這需要使用WebLogic的擴展TransactionManager接口來調(diào)用forceResume()方法。

    image
    Figure 2. WebLogic Server's transaction monitor (click the image for a full-size screen shot)

    Spring的WebLogicJtaTransactionManager有效地為基于Spring的應用提供了WebLogic Server事務管理的全部功能。這使得Spring事務分離成為一種能與EJB CMT竟爭的產(chǎn)品,而且提供了相同級別的事務支持。

    Spring and EJB CMT

    如 上所示,Spring的POJO聲明性事務分離可以作為一種除傳統(tǒng)EJB CMT這外的選擇。但是Spring與EJB并不是完成互斥的,Spring的應用上下文也可以作為EJB fa&ccedil;ade的后臺來管理數(shù)據(jù)訪問(DAO)和其他細紋理的業(yè)務對象。

    在EJB情景中,事務是由EJB CMT來驅(qū)動的。對Spring來說,數(shù)據(jù)訪問支持特性會自動檢測到這樣的環(huán)境并且采用相應的事務。例如,Spring對Hibernate的支持能夠提 供隱式的資源管理,即使是EJB驅(qū)動的事務,甚至可以在不需要修改任何DAO代碼的情況下提供相同的語義。
    Spring有效的解耦了DAO實現(xiàn)與實際的運行環(huán)境。DAO可以參與Spring的事務就像參與EJB CMT事務一樣。這不僅簡化在其他環(huán)境中的重用,而且更方便在J2EE容器外進行測試。

    結論
    Spring框架為J2EE和非J2EE環(huán)境提供了全量的事務分離的特性,特別表現(xiàn)在POJO的聲明性事務上。他用一種靈活而非侵入式的方式為非EJB環(huán)境中的事務分離提供了便利。與EJB不同,這樣的事務性POJO應用對象可以很容易的被測試和在J2EE容器外補重用。

    Spring 提供了各種事務策略,如JtaTransactionManager是用來代理J2EE服務器的事務協(xié)調(diào)器,而JDBC DataSourceTransactionManager是用來為簡單的JDBC DataSource(就是單一目標數(shù)據(jù)庫)執(zhí)行事務。Spring可以很容易為不同的環(huán)境通過后臺配置的簡單修改來調(diào)整事務策略。

    超越 標準的JTA支持,Spring為WebLogic Server的JTA擴展提供了完善的集成,可以支持高級特性如事務監(jiān)視和每事務隔離級別。通過對WebLogic Server的特殊支持,基于Spring的應用可以完全利用WebLogic Server的事務管理功能。

    Spring事務分離是繼 EJB CMT之外的另一種可選方式,特別是對那些基于POJO的輕量級架構。在那只是因為選擇LSSB(本地無狀態(tài)會話BEAN)來應用聲明性事務的情況下,基 于Spring的POJO服務模型是一種可行的選擇,他提供了非常高層的靈活性、可測試性和重用性。

    資源
    &#8226;JTA - The JTA specification JTA規(guī)范
    &#8226;WebLogic JTA - Documentation of WebLogic's JTA extensions WebLogic  JTA擴展文檔

    關于作者
    Juergen Hoeller是Spring框架的創(chuàng)始人之一
    posted @ 2005-12-16 22:39 Dion 閱讀(5069) | 評論 (1)編輯 收藏

    物流軟件廠商出路何在?


    ■本報記者 凡曉芝



    “幾乎沒有一個物流企業(yè)是贏利的”、“幾乎沒有一家物流軟件提供商是賺錢的”、“幾乎沒有一個物流信息化項目是成功的”,這樣的判斷背后的事實是,物流軟件廠商的日子比黃蓮還苦。

    “大家都知道做ERP苦,我們做物流軟件的比ERP更苦。”坐在我對面的某物流軟件公司總經(jīng)理臧先生一臉無奈。三年前,他從一家國外的ERP公司跳槽到此,因為那時候他對“物流信息化高潮已經(jīng)來臨”深信不疑。

    “現(xiàn)在,物流軟件對于我們集團來說就是一塊雞肋,食之無味,棄之可惜”。事實上,從2004年起,業(yè)界就曾風傳該公司將被賣掉。熬到2005年年底,情況并沒有好轉而業(yè)績更糟。“年后我得另謀生路了,”臧先生說,“但物流軟件公司,打死我也不去。”

    比做ERP還苦

    關于物流信息化有幾段“名言”:“幾乎沒有一個物流企業(yè)是贏利的”、“幾乎沒有一家物流軟件提供商是賺錢的”、“幾乎沒有一個物流信息化項目是成功的。”這樣的判斷就如同“中國ERP實施成功率為零”一樣的偏頗。但是其背后的事實是,“物流軟件廠商的日子比黃蓮還苦”。

    隨便拿起一個物流軟件公司的宣傳冊,煙草、醫(yī)藥、石油、化工、快速消費品等行業(yè)巨頭的成功案例都赫然在目。“這些 動輒上百萬元,甚至上千萬元的項目其實沒有幾個是賺錢的。”另一家物流軟件公司的總經(jīng)理陳先生對記者說:“就好比夏奈爾每年的時裝發(fā)布會,只是為了一個品 牌形象,實在不指望它能賺錢。大型物流信息化項目基本上就是賠本賺吆喝。”

    也有人甘愿“賠本賺吆喝”,上海博科公司營銷副總經(jīng)理周龍就認為大型項目就算不賺錢也是“值”的。“我們所做的 諸如中國石油這樣的大型的物流信息化項目,哪怕利潤不是很高,我們也要堅持做,不但堅持做還要讓客戶滿意,這對我們品牌宣傳很重要。”周龍認為,博科在國 內(nèi)物流信息化領域的地位日漸強勢,與這些大項目的實施緊密相關。

    但是更多的廠商是“賠了夫人又折兵”,被無休止的“爛尾工程”拖入泥沼。臧先生所在的物流公司在2004年希望 能上演“絕地反擊”,為了項目和銷售額幾乎到了不擇手段的地步,在競標中時常被稱為“攪屎棍”,以超低價格接連接了四個大的項目,但是災難也就接踵而至, 項目實施無休無止,甚至有客戶難以忍受該公司的“折磨”而要和廠商對簿公堂。

    之所以要這樣冒險一搏的原因還是惡劣的競爭環(huán)境使然。“泥沙俱下,魚龍混雜”是一位業(yè)內(nèi)人士對目前物流軟件市場 的看法。由于2002年前后,業(yè)界爆炒物流信息化高潮來臨,引來無數(shù)的投機者和投資者,物流軟件提供商的數(shù)量急劇增多,據(jù)中國物流信息化中心初步統(tǒng)計,我 國物流軟件廠商有500多家,此外還有一些知名的國外物流商、IT公司和咨詢公司也在中國的物流軟件市場中淘金。

    北京海淀東北旺一家物流公司的老板金先生對記者說,今年至少有30家軟件公司找過他,報價最低5萬元,最高的是 300萬元。“說實話,我被他們越搞越糊涂了,不知道究竟該相信誰。也不知道他們說的是不是真話。”事實上,據(jù)業(yè)內(nèi)人士透露,聲稱做物流信息軟件的公司中 70%到80%的企業(yè)都屬于“混水摸魚”,他們就是在傳統(tǒng)倉儲管理軟件或者運輸管理軟件的基礎上加以修改,然后就宣稱這是一套物流信息系統(tǒng)。“懂物流的企 業(yè)不多,卻有更多的企業(yè)在攪局。”

    三個錯位

    業(yè)內(nèi)分析人士認為,造成目前物流軟件廠商集體陷入困境的原因是“三個錯位”。

    AMT企業(yè)資源研究中心資深顧問趙楊說:“對物流信息化的爆炒而帶來的惡性競爭是物流軟件廠商陷入困境的主要原因。”而從另外一個角度看,其實就是市場需求滯長而廠商預期過高之間的錯位使然。

    據(jù)中國物流信息中心預測,2004~2006年,每年物流軟件市場的總體規(guī)模將維持在20億~35億元人民幣之 間,年增長率大約為5%左右。但是在此之前,大多數(shù)的物流軟件廠商都對這個市場過于樂觀,使得市場競爭者蜂擁而至。像用友金蝶這樣的專業(yè)ERP公司都成立 物流部門做企業(yè)物流業(yè)務,國外巨頭如i2、SSA、曼哈頓等憑借在自動化倉庫、物流供應鏈執(zhí)行等方面強勢進入市場,加之數(shù)不勝數(shù)的“散兵游勇”型軟件公 司,物流信息化市場掠食者眾多。

    博科公司營銷中心副總經(jīng)理周龍說:“第三方物流企業(yè)的信息化被認為是拉動物流行業(yè)信息化的關鍵,但事實上,第三 方物流企業(yè)目前多數(shù)不賺錢,在信息化方面投入很小,而且這一塊市場遠遠沒有啟動起來。”寄望已久的市場需求爆發(fā)點遲遲未到,但是參與競爭者卻“人滿為 患”,物流軟件廠商打單難,賺錢更難也就不難理解。

    其次是大系統(tǒng)和中小企業(yè)需求之間的錯位。根據(jù)2005年中國物流信息中心對中國企業(yè)物流信息化現(xiàn)狀和趨勢的調(diào)查 情況看,未建立信息系統(tǒng)的企業(yè)絕大部分是小型企業(yè),預計投資大于100萬的比例只有1.8%,37.5%的企業(yè)投資低于10萬元,這些數(shù)據(jù)可以看出,小型 企業(yè)的信息化需求潛力巨大。

    但是正如中國物流與采購聯(lián)合會副會長戴定一所指出,由于多數(shù)開發(fā)商認為,“物流信息化市場在低端不具備開發(fā)價 值”,多數(shù)廠商只是把眼光盯著幾個有限的行業(yè)和著名企業(yè),對小企業(yè)、小項目并不上心。比如海運、船運和集裝箱運輸這些高端物流項目確實利潤豐厚,項目動輒 就是幾百萬,甚至10個億投入,但是這一塊市場基本上都被IBM、HP、SAP等等廠商所把持。

    國內(nèi)很多專業(yè)的物流軟件廠商寧愿為極少的“高端客戶”和大項目打得頭破血流,也不愿意針對中小企業(yè)客戶開發(fā)“適銷對路”的產(chǎn)品。因此大家都做得很辛苦。

    第三是高成本與低回報之間的錯位。軟件開發(fā)成本高昂,但是物流企業(yè)出得起的錢卻很少,軟件廠商基本上處于“做一個 虧一個”的惡性循環(huán)中。趙楊對記者說:“物流行業(yè)的需求千差萬別,比如快速消費品和醫(yī)藥、煙草行業(yè)就是完全不同的流程,要把物流軟件做成標準化產(chǎn)品基本上 是不可能的。目前市場上聲稱的所謂標準化的產(chǎn)品只不過是一種噱頭而已。”由于絕大多數(shù)的物流項目都是定制化開發(fā),不但項目實施的周期長,而且項目常常陷入 無休止的修改和二次開發(fā)的泥沼之中。

    做“全”或者做“專”

    “再辛苦,我們也要堅持到底。”一家專業(yè)物流軟件廠商的總經(jīng)理對記者說 :“不經(jīng)歷風雨怎么見彩虹。”這其實是多數(shù)物流軟件廠商的心態(tài),在投入了上千萬元的資金、積累了一些客戶基礎以后,完全放棄物流信息化市場確實是很難。更何況,物流信息化可以說“前途是光明的”。

    “但是物流軟件廠商必須要改變,如果抱殘守缺肯定只有死路一條。”采訪中趙楊對物流軟件廠商的未來并不十分看好。 “如果說要求解,物流軟件廠商要么做全,要么做專。”趙楊認為,所謂做“全”就是不要局限于物流軟件,而是由物流供應鏈切入到包括ERP、SCM、CRM 等業(yè)務系 ;而做“專”就是盯準一個行業(yè)做產(chǎn)品和客戶。

    物流信息化市場涉及的目標客戶非常廣泛,如儲運公司、國際貨代公司、船代公司、包裹快遞公司、郵政部門、零售公司、大型工業(yè)企業(yè)中的物流部門等等。不同市場對信息化的需求既有一定的共性,又存在一定差異。物流軟件廠商必須要立足自身實際情況,確定合適的目標市場。

    也有分析人士認為,做專的另一種方式是提供細分化產(chǎn)品。物流管理是一個綜合的系統(tǒng)工程,整個物流運作是運輸、儲 存、裝卸、搬運、包裝、流通加工、配送等基本功能的有機結合。如果廠商能圍繞部門級信息系統(tǒng)如倉儲管理系統(tǒng)、運輸管理系統(tǒng)、配送管理系統(tǒng)、報關管理系統(tǒng)、 貨代管理系統(tǒng)、快遞管理系統(tǒng)等開發(fā)出一些精深且有特色的產(chǎn)品,將能夠更大程度地增強自身的市場競爭力。

    事實上,諸如上海博科這樣的選擇堅守的物流軟件廠商也開始進行業(yè)務的調(diào)整。記者在采訪中了解到,針對大型企業(yè)物 流的需求,博科將改變以前的單個物流項目的做法。周龍說:“我們的物流系統(tǒng)將和博科集團財務、ERP、供應鏈等產(chǎn)品組合成為一個整體,做成一個整體解決方 案提供給客戶。”而對于中小型的物流公司,則通過平臺化產(chǎn)品,通過渠道銷售的方式滿足客戶需求。而記者在采訪中得知,另一家知名的物流軟件廠商招商迪辰也 “正在業(yè)務調(diào)整中”。

    “物流軟件廠商無論做專還是做全,都必須要做精”,趙楊強調(diào)說:“廠商的產(chǎn)品和品牌形象至關重要。”根據(jù)中國物 流信息中心最新的調(diào)查報告,物流客戶對開發(fā)商最看重的幾項條件分別是:成功案例(85%)、咨詢與服務經(jīng)驗(73.3%)、品牌46.7%、價格因素 45%。對未建系統(tǒng)的企業(yè)調(diào)查顯示,對有成功案例最看重(40%),其次是品牌(26%)。由此可見,在未來的競爭中,物流軟件廠商必須得拿“牌子”見 客,拿產(chǎn)品說話。

    (計算機世界報 2005年12月05日 第47期 E5)

    posted @ 2005-12-15 16:15 Dion 閱讀(818) | 評論 (1)編輯 收藏

    Google編程大賽入圍賽250分真題


    Problem Statement
    ????
    You are given a String[] cityMap representing the layout of a city. The city
    consists of blocks. The first element of cityMap represents the first row of
    blocks, etc. A 'B' character indicates a location where there is a bus stop.
    There will be exactly one 'X' character, indicating your location. All other
    characters will be '.'. You are also given an int walkingDistance, which is the
    maximum distance you are willing to walk to a bus stop. The distance should be
    calculated as the number of blocks vertically plus the number of blocks
    horizontally. Return the number of bus stops that are within walking distance of
    your current location. Definition
    ????
    Class:
    BusStops
    Method:
    countStops
    Parameters:
    String[], int
    Returns:
    int
    Method signature:
    int countStops(String[] cityMap, int walkingDistance)
    (be sure your method is public)
    ????

    Constraints
    -
    cityMap will contain between 1 and 50 elements, inclusive.
    -
    Each element of cityMap will contain between 1 and 50 characters, inclusive.
    -
    Each element of cityMap will contain the same number of characters.
    -
    Each character of each element of cityMap will be 'B', 'X', or '.'.
    -
    There will be exactly one 'X' character in cityMap.
    -
    walkingDistance will be between 1 and 100, inclusive.
    Examples
    0)

    ????
    {"...B.",
     ".....",
     "..X.B",
     ".....",
     "B...."}
    3
    Returns: 2
    You can reach the bus stop at the top (3 units away), or on the right (2 units
    away). The one in the lower left is 4 units away, which is too far. 1)

    ????
    {"B.B..",
     ".....",
     "B....",
     ".....",
     "....X"}
    8
    Returns: 3
    A distance of 8 can get us anywhere on the map, so we can reach all 3 bus stops.
    2)

    ????
    {"BBBBB",
     "BB.BB",
     "B.X.B",
     "BB.BB",
     "BBBBB"}
    1
    Returns: 0
    Plenty of bus stops, but unfortunately we cannot reach any of them.
    3)

    ????
    {"B..B..",
     ".B...B",
     "..B...",
     "..B.X.",
     "B.B.B.",
     ".B.B.B"}
    3
    Returns: 7

    This problem statement is the exclusive and proprietary property of TopCoder,
    Inc. Any unauthorized use or reproduction of this information without the prior
    written consent of TopCoder, Inc. is strictly prohibited. (c)2003, TopCoder,
    Inc. All rights reserved.

    posted @ 2005-12-15 13:14 Dion 閱讀(1192) | 評論 (0)編輯 收藏

    面向方面的Annotation

    作者:Bill Burke,Enterprise JavaBeans 第四版合著者

    譯者:yahveyeye





    版權聲明:任何獲得Matrix授權的網(wǎng)站,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
    作者:Bill Burke;yahveyeye
    原文地址:http://www.onjava.com/pub/a/onjava/2004/08/25/aoa.html
    中文地址:http://www.matrix.org.cn/resource/article/44/44052_Annotation+Aop.html
    關鍵詞: Annotation Aop

    Annotation 是J2SE5.0的一項新功能,它允許您附加元數(shù)據(jù)到Java構建中。同時,面向方面編程(AOP)是一個相當新的技術,它可以使您封裝某些行為,這些行 為是在使用面向?qū)ο螅∣O)技術時會更為混亂,困難甚至是不可能完成。這兩項技術結合起來給框架開發(fā)者開發(fā)的APIs更好的表達方式。本文深入結合這些技 術,使用Jboss AOP框架,以不同的代碼范例向您展示如何結合兩者來實際地擴展Java 語言。

    相關文章:
    Java Annotation入門:
    http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html

    Annotation概述

    首 先讓我們給出這兩項技術的一個概述。Annotation是JDK5.0的新功能,它在JSR-175規(guī)范中有詳細定義。它們允許您以安全的方法定義元數(shù) 據(jù)并應用到類,方法,構造程序,字段或參數(shù)中。對于你們中熟悉XDoclet的人來說,Annotation將非常直觀,您可以用來聲明標簽以產(chǎn)生代碼。 兩者的主要不同是Annotation是Java語言的一部分而XDoclet標簽可能會打錯并且難以創(chuàng)建。我喜歡用例子來說明,所以讓我們展示一個簡單 的例子。

    要定義一個Annotation,您所要做的就是聲明一個特殊類型的Java接口。

    清單1:Orange.java
    package org.jboss.collors;
    public @interface Orange{}


    定義了這個接口,您就可以用來提供更多的描述給您的Java元素。
    清單2:Foo.java
    package org.jboss.examples;
    public class Foo
    {
      @Orange void someMethod();
      @Orange private int someField;
    }


    那么我們可以用Annotation來干什么呢?一些人想用Annotation來產(chǎn)生代碼并替代XDoclet,其他人,象J2EE和EJB3.0專家組,將它視為部署描述符的替代。本文談論在AOP中如何使用Annotation

    AOP概述

    有許多的文章和書籍解釋AOP到底是什么,例如Graham O'Regan的ONJava文章“Introduction to Aspect-Oriented Programming."我將在本文給出一個快速的概覽,但我鼓勵您在線做更多的研究。

    假設您要添加代碼到一個應用程序去測試調(diào)用一個特定的java方法所需的總的時間。該代碼可能看起來如下:
    清單3:
    public class BankAccount
    {
    public void withdraw(double amount)
    {
    long startTime = System.currentTimeMillis();
    try
    {
    // Actual method body...
    }
    finally
    {
    long endTime = System.currentTimeMillis() - startTime;
    System.out.println("withdraw took: " + endTime);
    }
    }
    }


    雖然這些代碼能夠正常工作,但這個方法有一些問題:
    1.它難以打開和關閉測試,您必須在try/finally塊中對每個方法或購置函數(shù)手工增加代碼以進行基準測試。
    2。這一輪廓代碼并不真正屬于貫穿整個應用的代碼。它使得您的代碼臃腫并難以理解,因為您必須將計時放在try/finally塊中。
    3、如果您想擴展它的功能以包含一個方法或是失敗計數(shù),或甚至是注冊這些統(tǒng)計數(shù)據(jù)到一個更為復雜的報告機制中,您必須修改大量不同文件(又一次)。

    Metrics 類提供了一個什么是橫切(cross-cutting)關系的完美,簡潔的小例子。Jboss AOP以一種含蓄的方式提供了一個簡單的方法來封裝和應用這樣的關系,這樣某些象度量操作代碼不會弄亂您的編碼。讓我們稍為深入到Jboss AOP一些來看看如何實現(xiàn)。
    為了使用Jboss AOP封裝度量功能,您首先需要定義一個方面來指出該度量行為。
    清單4:
    public class Metrics
    {
    public Object profile(MethodInvocation invocation) throws Throwable
    {
    long startTime = System.currentTimeMillis();
    try
    {
    return invocation.invokeNext();
    }
    finally
    {
    long endTime = System.currentTimeMillis() - startTime;
    java.lang.reflect.Method m = invocation.getMethod();
    System.out.println("method " + m.toString() +
    " time: " + endTime + "ms");
    } }
    }


    一 個方面只是一個具有定義了您想要附加到您的對象模型的行為的普通Java類。這些方法的簽名必須返回一個java.lang.Object并且必須具有一 個(并且只有一個)Jboss AOP 調(diào)用對象參數(shù),它被用來封裝方法,構造函數(shù)或字段調(diào)用。方法名可以是任何你想要的并且當您綁定該方面到您的代碼片斷時被引用。

    下面要做的事情就是實際應用方面到您想要它勾勒一個方法的執(zhí)行的某個程序點。大多數(shù)AOP框架提供了一個指向表達式語言,在此處您可以定義您想要某個方面行為被附加到的位置。下面是在Jboss AOP中的做法。
    清單5:jboss-aop.xml
    <aop>
    <aspect class="Metrics"/>

    <bind pointcut="execution(public void BankAccount->withdraw(double amount))">
    <advice name="profile" aspect="Metrics"/>
    </bind>
    </aop>


    采用在Metrics.java中對方面的定義和jboss-aop.xml中的指向定義,該度量代碼現(xiàn)在以含蓄而又透明地應用到BankAccount.withdraw()方法中并能在勾勒代碼不再需要時輕易地移除。
    對于Jboss AOP更多的信息,請查詢分發(fā)包中的指南。其中具有大約20個例子來帶領您漫游如何使用Jboss AOP框架。

    噓!現(xiàn)在我們已經(jīng)進行了一個概覽,讓我們深入到本文的中心內(nèi)容。我將再次給您提供一些例子,因為這是我所知道的講授一個新的概念的最好的方法。
    正如我前面說的,Annotation加上AOP幾乎是給予您擴展Java語言的能力。Annotation提供了聲明新的,可兼容的,類型安全的語法機制。AOP提供了封裝和應用新的行為到一個語法表達式的機制。

    方法Annotation和AOP

    讓 我們看看如何使用方法Annotation和AOP。使用Annotation和AOP并應用到一個方法類似于使用Java的synchronized關 鍵字。當您設定一個方法為synchronized,您在告訴JVM:您想該方法在被調(diào)用時以一種特殊的方式進行。Annotation允許您定義一個新 的關鍵字來觸發(fā)您自己的特殊的定制行為。AOP給予您封裝這一行為的能力并將其“編織”進該方法的執(zhí)行中。再次的,這一概念的最佳描述是通過一個例子。

    讓我們假設我們想要添加新的語法,使用該語法使得我們可以在方法被標簽為@Oneway時,在后臺以另一個線程調(diào)用這個void方法。可以象這樣使用新的語法:

    清單6:
    Import org.jboss.aspects.Oneway;
    public class Foo
    {
      @Oneway public static void someMethord(){…}
    public static void main(String[] args){
    somMethod();//executes in
    backgroud
    }
    }


    當someMethod()在main中被調(diào)用,它將異步運行,這樣main中的代碼可以并行執(zhí)行其他任務。
    要實現(xiàn)這一功能,首先要在一個Annotation中為我們的@Oneway標簽定義新的Java語法.
    清單7:Oneway.java
    package org.jboss.aspects;

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Target;

    @Target({ElementType.METHOD})
    public @interface Oneway {}


    夠簡單的。@Target標簽允許您縮小Annotation可以應用的地方。在本例中,我們的@OnewayAnnotation只能應用到一個方法。記住,這些都是J2SE5.0百分之百可用的純Java。
    下面要做的事是定義一個封裝我們的@Oneway行為的方面類。
    清單8:OnewayAspect.java

    package org.jboss.aspects;

    public OnewayAspect
    {
    private static class Task implements Runnable
    {
    private MethodInvocation invocation;

    public Task(MethodInvocation invocation)
    {
    this.invocation = invocation;
    }
    public void run()
    {
    try { invocation.invokeNext(); }
    catch (Throwable ignore) { }
    }
    }


    public Object oneway(MethodInvocation invocation) throws Throwable
    {
    MethodInvocation copy = invocation.copy();
    Thread t = new Thread(new Task(copy));
    t.setDaemon(false);
    t.start();
    return null;
    }
    }


    這 個方面夠簡單。oneway()方法拷貝invocation,創(chuàng)建一個線程,在后臺啟動整個調(diào)用并返回。我們可以想象一個更為復雜的例子:使用J2SE 5.0 java.util.concurrent包中的某些新的Executors,但這些代碼很有希望闡明了如何基于這個例子構建更為復雜的實現(xiàn)。
    最后必須要做的事情是指定當@OnewayAnnotation在一個方法中聲明時觸發(fā)OnewayAspect應用的指向表達式。

    清單9:jboss-aop.xml
    <aop>
    <aspect class="org.jboss.aspects.OnewayAspect"/>
    <bind pointcut="execution(void *->@org.jboss.Oneway(..))">
    <advice name="oneway"
    aspect="org.jboss.aspects.OnewayAspect"/>
    </bind>
    </aop>


    該 指向表達式規(guī)定任何具有@Oneway標簽的void方法都應該有OnewayAspect.oneway()方法在它本身執(zhí)行前被執(zhí)行。隨著 Annotation,方面和現(xiàn)在定義的指向表達式,@Oneway語法現(xiàn)在可以用于您的應用程序中。一個簡單,清晰,易于實現(xiàn)的方法來擴展Java 語言!

    字段Annotation和AOP

    讓 我們看看如何使用字段Annotation和AOP。使用Annotation和AOP,您可以改變一個對象的字段或是作為一個類的靜態(tài)成員的實際存儲方 式。在這個例子里我們要完成的是當您將一個字段(靜態(tài)或是成員)標記上@ThreadBased,盡管是將它存儲在 java.lang.ThreadLocal,但它的值依然正常。當然,您可以直接使用ThreadLocal變量,但問題是ThreadLocal并非 一個類型并且您必須使用“麻煩的”(好,它們并沒有那么羅嗦)get()和set()方法。那么我們現(xiàn)在做的就是創(chuàng)建一個ThreadLocal類型的字 段。我們主要的將創(chuàng)建一個稱為@Thradbased變量的新的Java字段類型。
    象這樣使用新的類型:
    清單10:
    import org.jboss.aspects.Threadbased;
    public class Foo
    {
    @Threadbased private int counter;
    }


    為了實現(xiàn)這個功能,我們必須先定義Annotation
    清單11:Threadbased.java
    package org.jboss.aspects;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Target;
    @Target({ElementType.FIELD})
    public @interface Threadbased {}



    夠簡單。@Target標簽允許您縮小Annotation可以應用的地方。在本例中,我們的@ThreadbasedAnnotation只能應用到字段。
    下面的事情是定義封裝我們的ThreadLocal行為的方面。
    清單12:ThreadbasedAspect.java
    package org.jboss.aspects;
    import org.jboss.aop.joinpoint.*;
    import java.lang.reflect.Field;
    public class ThreadbasedAspect
    {
    private ThreadLocal threadbased = new ThreadLocal();
    public Object access(FieldReadInvocation invocation)
    throws Throwable
    {
    // just in case we have a primitive,
    // we can't return null
    if (threadbased.get() == null)
    return invocation.invokeNext();
    return threadbased.get();
    }
    public Object access(FieldWriteInvocation invocation)
    throws Throwable
    {
    threadbased.set(invocation.getValue());
    return null;
    }
    }


    ThreadbasedAspect 封裝到一個Java字段的訪問。它里面具有一個專門的ThreadLocal變量跟蹤thradlocal變?yōu)橐粋€特殊的字段。它還有一個單獨的 access()方法,該方法根據(jù)一個字段的get或set方法是否被調(diào)用決定它是否被調(diào)用。這些方法委托給ThreadLocal來獲得字段的當前值。
    最后,我們必須定義一個指向表達式,當@ThreadbasedAnnotation在某個字段被指定時觸發(fā)ThreadbasedAspect的應用。
    清單13:jboss-aop.xml
    <aop>
    <aspect class="org.jboss.aspects.ThreadbasedAspect" scope="PER_JOINPOINT"/>
    <bind pointcut="field(* *->@org.jboss.aspects.Threadbased)">
    <advice name="access"
    aspect="org.jboss.aspects.ThreadbasedAspect"/>
    </bind>
    </aop>


    只 有當我們具有多個@Threadbased變量定義在同一個類時,我們需要為每個靜態(tài)字段分配一個ThreadbasedAspect實例。對于成員變 量,我們需要為每個字段,每個對象實例分配一個ThreadbasedAspect實例。為了促進這一行為,方面定義通過設定實例為 PER_JOINPOINT限制方面類的實例何時和何地被分配出去的范圍。如果我們不做限制,Jboss
    AOP會只分配一個ThreadbasedAspect實例并且不同的字段會共享相同的ThreadLocal接口——這不是我們所希望的。

    好就這樣。一個清晰容易的擴展Java來指定一個新的特殊類型的方法。注意:該特殊的方法來自Jboss AOP束。

    依賴注入

    字 段Annotation和AOP可以使用的一個有趣的地方是依賴注入。依賴注入是關于對象聲明它們需要什么信息,配置或服務引用以及運行時自動注入這些依 賴而不是用代碼明確地在一個注冊中心查找。在J2EE領域,獲得javax.transaction.TransactionManager服務的訪問并 未標準化并且實際上不同的廠商有不同的實現(xiàn)。許多框架開發(fā)者需要使用TransactionManager來實現(xiàn)定制事務服務。使用字段 AnnotationAOP提供依賴注入并抽取出一個需要TransactionManager的組件如何引用它的細節(jié)是一個了不起的方法。讓我們定義一 個方面,它將注入一個TransactionManager引用到一個字段值中。

    首先,我們再次定義我們的Annotation。

    清單14:Inject.java
    package org.jboss.aspects;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Target;
    @Target({ElementType.FIELD})
    public @interface Inject {}


    下面我們將定義方面類,它封裝了TransactionManager的解析。該方面是特定于JBoss應用服務器,但您可以定義為每個廠商定義不同的實現(xiàn)。
    清單15:InjectTMAspect.java
    package org.jboss.aspects;
    import org.jboss.aop.joinpoint.*;
    import java.lang.reflect.Field;
    import javax.transaction.TransactionManager;
    import org.jboss.tm.TxManager;
    public InjectTMAspect
    {
    private TransactionManager tm = TxManager.getInstance();
    public Object access(FieldReadInvocation invocation)
    throws Throwable {
    return tm;
    }
    public Object access(FieldWriteInvocation invocation)
    throws Throwable {
    throw new RuntimeException(
    "Setting an @Injected variable is illegal");
    }
    }


    最后,我們必須定義XML綁定來觸發(fā)當@Inject標簽應用到一個字段時InjectTMAspect的應用。指向表達式基本上說明了對任意一個標記為@Inject的TransactionManager字段應用InjectTMAspect。
    清單16:
    <aop>
    <aspect class="org.jboss.aspects.InjectTMAspect"/>
    <bind pointcut="field(javax.transaction.TransactionManager *->@org.jboss.aspects.Inject)">
    <advice name="access"
    aspect="org.jboss.aspects.InjectTMAspect"/>
    </bind>
    </aop>


    現(xiàn)在Annotation、方面類和XML綁定已經(jīng)定義,我們可以在我們的代碼中使用了。
    清單17:
    import javax.transaction.TransactionManager;
    import org.jboss.aspects.Inject;
    public class MyTransactionalCache
    {
    @Inject private TransactionManager tm;
    ...
    }


    更多預打包例子

    Jboss AOP不僅僅是關于AOP框架。它還有一個豐富的方面庫,您可以直接在您的應用中使用。在這個庫中是一個比我們現(xiàn)在在本文展示的例子更為復雜的 Annotation方面集。這些方面包括異步調(diào)用,事務劃分,事務鎖定和基于角色的安全。讓我們簡要地瀏覽一下以提供給您一個更好的關于 Annotation和AOP共同工作的考慮。

    異步方面
    Jboss AOP異步方面允許您定義任何方法為異步的,這樣它可以在后臺被執(zhí)行。這對于我們的@Oneway例子來說有些困難,因為它使用Oswego并行包中的執(zhí) 行器工具,并為那些具有一個返回類型的方法提供了一個方法來異步地接收回響應。要使用這個方面,您只需標記一個方法為@Asybchronous.
    清單18:
    public Foo {
    @Asynchronous public int someMethod(int someArg) {...}
    }


    @Asynchronous 標簽的應用做了一些事情。與在本文中的@Oneway例子一樣,它應用一個在后臺運行該方法的方面。而且,采用@Asynchronous標簽,您并不僅 限于void方法并可于實際上返回一個值的方法進行交互。當@Asynchronous標簽被應用,它強制Foo類實現(xiàn) AsynchronousFacade接口。在AOP領域,這稱為接口引入(interface introduction)。AsynchronousFacade接口允許您預測一個響應或以超時限定等待一個響應。最好用一個例子來解釋。
    清單19:
    Foo foo = new Foo();
    someMethod(555); // executes in background
    AsynchronousFacade facade = (AsynchronousFacade)foo;
    AsynchronousResponse response = facde.waitForResponse();
    System.out.println(response.getReturnValue());


    您可以啟動多個不同對象的多個不同方法的多個調(diào)用,并異步積累它們的響應。

    事務鎖定
    有時在J2EE事務期間而不是一個方法執(zhí)行,構造函數(shù)調(diào)用或同步塊執(zhí)行期間同步一個對象或類會很有用。對這類事務同步或鎖定,Jboss AOP發(fā)明了@TxSynchronized關鍵字。您可以使用@TxSynchronized在任意成員或靜態(tài)方法已經(jīng)構造函數(shù)上。
    清單20:
    import org.jboss.aspects.txlock.TxSynchronized;
    public FooBar
    {
    @TxSynchronized public FooBar() {}
    @TxSynchronized static void staticMethod() {}
    @TxSynchronized public memberMethod() {}
    }


    如 果一個被標記為@TxSynchronized的構造函數(shù)或靜態(tài)方法被調(diào)用,類的鎖監(jiān)視器會在事務執(zhí)行期間被保持著。如果一個標記為 @TxSynchronized的成員方法被調(diào)用,該對象實例的鎖監(jiān)視器將被保持直到目前的事務提交或回退。控制該行為的方面也將做死鎖檢測并在發(fā)生死鎖 時拋出RuntimeException。

    J2EE 散餐(原文法文:a la carte)之:事務劃分
    EJB3.0已經(jīng)定義了一些Annotation進行事務劃分。Jboss AOP在此基礎上構建。這樣您可以通過指定Annotation應用事務劃分到任意方法(靜態(tài)或成員)以及任何Java類構造函數(shù)。
    清單21:
    import org.jboss.aspects.tx.*;
    public class Foo
    {
    @Tx(TxType.REQUIRED) public Foo {}
    @Tx(TxType.REQUIRESNEW) public static createFoo() {
    return new Foo();
    }
    }


    J2EE 散餐之:基于角色的安全
    EJB 3.0也定義了一些Annotation實現(xiàn)基于角色的安全。Jbos AOP是基于此構建的,所以您可以應用基于角色的安全到任何的字段或方法(靜態(tài)或成員)已經(jīng)構造函數(shù)。
    清單22:
    import org.jboss.aspects.security.*;
    @SecurityDomain("LDAP Repository")
    public class Foo
    {
    @Permissions({"admin"}) public Foo() {}
    @Permissions({"developer"}) public static int status;
    @Permissions({"anybody"}) public static void doSomething() {...}
    }


    EJB演變

    隨 著AOP與EJB規(guī)范一起漸漸成熟,我真正希望發(fā)生的是EJB規(guī)范定義的Annotation將能在任何上下文作為新的Java語言的形容詞被使用,而不 是讓它們有限的使用在會話bean中。想象一下,一個真正的無狀態(tài)bean僅僅成為一個明文Java類的一個靜態(tài)方法集。
    清單23:
    public MySessionBean
    {
    @Tx(TxType.REQUIRED) public static doSomething() {...}
    }


    無論如何,這些關于AOP和EJB的討論很可能就是為了EJB4.0。

    結論
    并非是限制J2SE5.0Annotation用于代碼生成,Annotation和AOP可以被結合起來提供新的能力給框架開發(fā)者。這一結合允許開發(fā)者定義新的具有行為附加到其上的Java語法。基本上,以安全的方式擴展Java語言的能力已盡在掌握。

    資源
    ·Matrix-Java開發(fā)者社區(qū):http://www.matrix.org.cn
    ·onjava.com:onjava.com

    關于作者
    Bill Burke :JBoss 首席架構師.
    posted @ 2005-12-15 13:12 Dion 閱讀(1626) | 評論 (0)編輯 收藏

    主站蜘蛛池模板: 亚洲私人无码综合久久网| 亚洲av日韩av无码av| 久久亚洲AV成人出白浆无码国产| 亚洲视频在线免费播放| 日韩亚洲国产综合高清| 黄色三级三级免费看| 大地影院MV在线观看视频免费| 亚洲免费在线视频播放| 国内精品免费视频自在线| 亚洲人成网站色在线入口| 亚洲av日韩av无码| 亚洲熟妇无码八V在线播放| 一日本道a高清免费播放| 国产成人久久AV免费| 免费无码黄十八禁网站在线观看| 免费在线观看黄网| 亚洲第一区香蕉_国产a| 亚洲中文字幕无码亚洲成A人片| 成人免费视频一区二区| 97公开免费视频| 又大又粗又爽a级毛片免费看| 亚洲AV无码国产在丝袜线观看| 午夜神器成在线人成在线人免费| 亚洲欧洲第一a在线观看| 亚洲一级毛片在线观| 狠狠综合亚洲综合亚洲色| 精品国产麻豆免费人成网站| 欧美日韩国产免费一区二区三区 | 3344在线看片免费| 久久受www免费人成_看片中文| 亚洲国产成人精品女人久久久| 99久久亚洲综合精品成人网| 狠狠综合亚洲综合亚洲色| 最近中文字幕完整免费视频ww | 亚洲成a人片在线不卡| 国产免费福利体检区久久| 久久福利资源网站免费看| 亚洲一区二区三区在线播放| 亚洲人成电影网站| a级毛片毛片免费观看永久| 精品国产免费观看|