<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 評(píng)論 :: 0 Trackbacks

    2005年12月16日 #

    ThreadLocal是什么

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

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

    當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本。

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

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

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

    ThreadLocal的接口方法

    ThreadLocal類接口很簡(jiǎn)單,只有4個(gè)方法,我們先來了解一下:

    • void set(Object value)

    設(shè)置當(dāng)前線程的線程局部變量的值。

    • public Object get()

    該方法返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量。

    • public void remove()

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

    • protected Object initialValue()

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

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

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

    代碼清單1 SimpleThreadLocal

    public class SimpleThreadLocal {

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

    public void set(Object newValue) {

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

    }

    public Object get() {

    Thread currentThread = Thread.currentThread();

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

    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這個(gè)ThreadLocal實(shí)現(xiàn)版本顯得比較幼稚,但它和JDK所提供的ThreadLocal類在實(shí)現(xiàn)思路上是相近的。

    一個(gè)TheadLocal實(shí)例

    下面,我們通過一個(gè)具體的實(shí)例了解一下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;

    }

    };

    獲取下一個(gè)序列值

    public int getNextNum(){

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

    return seqNum.get();

    }

    public static void main(String[] args)

    {

    SequenceNumber sn = new SequenceNumber();

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

    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++) {④每個(gè)線程打出3個(gè)序列值

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

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

    }

    }

    }

    }

     

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

    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]

    考察輸出的結(jié)果信息,我們發(fā)現(xiàn)每個(gè)線程所產(chǎn)生的序號(hào)雖然都共享同一個(gè)SequenceNumber實(shí)例,但它們并沒有發(fā)生相互干擾的情況,而是各自產(chǎn)生獨(dú)立的序列號(hào),這是因?yàn)槲覀兺ㄟ^ThreadLocal為每一個(gè)線程提供了單獨(dú)的副本。

    Thread同步機(jī)制的比較

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

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

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

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

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

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

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

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

    通通透透理解ThreadLocal

    1同一線程貫通三層

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

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

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

    public class TopicDao {

    private Connection conn;一個(gè)非線程安全的變量

    public void addTopic(){

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

    }

    }

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

    代碼清單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沒有本線程對(duì)應(yīng)的Connection創(chuàng)建一個(gè)新的Connection,

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

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

    Connection conn = ConnectionManager.getConnection();

    connThreadLocal.set(conn);

    return conn;

    }else{

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

    }

    }

    public void addTopic() {

    ④從ThreadLocal中獲取線程對(duì)應(yīng)的Connection

    Statement stat = getConnection().createStatement();

    }

    }

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

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

    小結(jié)

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

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

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

    前幾天跟著寫了一個(gè)簡(jiǎn)單的例子.
    覺得Drools的配置也沒有什么.
    今天在運(yùn)行house的例子的時(shí)候, 無論怎么樣, 總是異常: 沒有定義的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 );
    }

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

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

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

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

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

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

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

           一個(gè)月后,你接到一家巴西分析師公司的電話,雇傭你的公司生成一系列巴西股市的報(bào)表,但他們有更嚴(yán)格的標(biāo)準(zhǔn)。而目前在巴西,P/E比率市場(chǎng)平均值是個(gè)位 數(shù),因此你用來評(píng)估被市場(chǎng)低股票的閾值需要改變。除了較低的P/E比率,你的新客戶還要求以Price-to-Book比率作為參考標(biāo)準(zhǔn)。
           你啟動(dòng)規(guī)則編輯器,并修改規(guī)則以匹配新的評(píng)估條件。現(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ā)團(tuán)隊(duì)作任何解釋。你無需等待他們開發(fā)或測(cè)試程序。如果你的規(guī)則引擎的語義足夠強(qiáng)大,讓你描述工作數(shù)據(jù),你可以隨時(shí)按需修改商業(yè)規(guī)則。
           如果限制因素是規(guī)則的定義語言和數(shù)據(jù)模型,你可以確信這兩者將會(huì)標(biāo)準(zhǔn)化,并出現(xiàn)先進(jìn)的編輯器和工具,以簡(jiǎn)化規(guī)則的定義,保存和維護(hù)。
           現(xiàn)在,我希望你已經(jīng)清楚以下的原則:在這個(gè)例子中,哪只股票是否被選擇是一個(gè)商務(wù)決策,而不是技術(shù)決策。決定將哪只股票交給你的分析師是經(jīng)理人的邏輯 ――"logic of the bottom line"。經(jīng)理人作出這些決策,并可以按需定制應(yīng)用。這些規(guī)則因此變成了一種控制界面,一種新的商業(yè)系統(tǒng)用戶界面。

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

    Bob McWhirter的Drools項(xiàng)目
           現(xiàn)在,我要介紹Drools項(xiàng)目,Charles Forgy Rete算法的一個(gè)增強(qiáng)的Java語言實(shí)現(xiàn)。Drools是一個(gè)Bob McWhirter開發(fā)的開源項(xiàng)目,放在The Codehaus上。在我寫這篇文章時(shí),Drools發(fā)表了2.0-beata-14版。在CVS中,已完整地實(shí)現(xiàn)了JSR94 Rule Engine API并提供了單元測(cè)試代碼。
           Rete算法是Charles Forgy在1979年發(fā)明的,是目前用于生產(chǎn)系統(tǒng)的效率最高的算法(除了私有的Rete II)。Rete是唯一的,效率與執(zhí)行規(guī)則數(shù)目無關(guān)的決策支持算法。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應(yīng)用于生產(chǎn)系統(tǒng)已經(jīng)有很多年了,但在Java開源軟件中并沒有得到廣泛應(yīng)用(討論Rete算法的文檔參見http://herzberg.ca.sandia.gov/jess/docs/61/rete.html。)。
           除了應(yīng)用了Rete核心算法,開源軟件License和100%的Java實(shí)現(xiàn)之外,Drools還提供了很多有用的特性。其中包括實(shí)現(xiàn)了JSR94 API和創(chuàng)新的規(guī)則語義系統(tǒng),這個(gè)語義系統(tǒng)可用來編寫描述規(guī)則的語言。目前,Drools提供了三種語義模塊――Python模塊,Java模塊和 Groovy模塊。本文余下部分集中討論JSR94 API,我將在第二篇文章中討論語義系統(tǒng)。
           作為使用javax.rules API的開發(fā)人員,你的目標(biāo)是構(gòu)造一個(gè)RuleExecutionSet對(duì)象,并在運(yùn)行時(shí)通過它獲得一個(gè)RuleSession對(duì)象。為了簡(jiǎn)化這個(gè)過程, 我編寫了一個(gè)規(guī)則引擎API的fa?ade,可以用來解釋代表Drools的DRL文件的InputStream,并構(gòu)造一個(gè) RuleExecutionSet對(duì)象。
           在上面提到了Drools的三種語義模塊,我接下來使用它們重新編寫上面的例子XML規(guī)則文件。這個(gè)例子中我選擇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);   ( 譯注:應(yīng)該是retractObject(stock) )
        </java:consequence>
      </rule>
    </rule-set>

           現(xiàn)在的規(guī)則文件并沒有上面的簡(jiǎn)潔明了。別擔(dān)心,我們將在下一篇文章討論語義模塊。現(xiàn)在,請(qǐng)注意觀察XML文件的結(jié)構(gòu)。其中一個(gè)rule-set元素包含了 一個(gè)或多個(gè)rule元素,rule元素又包含了parameter,condition和consequence元素。Condition和 consequence元素包含的內(nèi)容和Java很象。注意,在這些元素中,有些事你可以做,有些事你不能做。目前,Drools使用 BeanShell2.0b1作為它的Java解釋器。我在這里并不想詳細(xì)的討論DRL文件和Java語義模塊的語法。我們的目標(biāo)是解釋如何使用 Drools的JSR94 API。
           在Drools項(xiàng)目CVS的drools-jsr94模塊中,單元測(cè)試代碼包含了一個(gè)ExampleRuleEngineFacade對(duì)象,它基于 Brian Topping的Dentaku項(xiàng)目。這個(gè)fa?ade對(duì)象通過javax.rules API,創(chuàng)建了供RuleExecutionSet和RuleSession使用的一系列對(duì)象。它并沒有完全包括了Drools引擎API的所有特性和細(xì) 微差別,但可以作為新手使用API的一個(gè)簡(jiǎn)單例子。
          下面的代碼片斷顯示如何使用規(guī)則引擎的facade構(gòu)造一個(gè)RuleExecutionSet對(duì)象,并通過它獲得一個(gè)RuleSession對(duì)象。
     
    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例外,這里為了簡(jiǎn)單起見省略了。你要做的只是構(gòu)建InputStream 對(duì)象,并把它輸入ExampleRuleEngineFacade,用來創(chuàng)建一個(gè)RuleExecutionSet對(duì)象。然后,你可以得到一個(gè) StatelessRuleSession,并用它來執(zhí)行所有的規(guī)則。使用StatelessRuleSession相對(duì)簡(jiǎn)單。我們可以給上面的類添加一 個(gè)方法,用來對(duì)一個(gè)對(duì)象列表執(zhí)行規(guī)則:

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

           該方法輸入一個(gè)stock對(duì)象列表給規(guī)則引擎,然后使用規(guī)則評(píng)估輸入的股票對(duì)象,并剔除那些不符合價(jià)值低估標(biāo)準(zhǔn)的股票。它是個(gè)簡(jiǎn)單的例子,但足以說明問題。
           在ExampleRuleEngineFacade類中,代碼會(huì)稍微有些復(fù)雜。ExampleRuleEngineFacade類創(chuàng)建了一個(gè) RuleServiceProvider對(duì)象,并用它創(chuàng)建RuleAdministrator,RuleExecutionSetProvider和 RuleRuntime對(duì)象。RuleExecutionSetProvider負(fù)責(zé)解釋InputStream,并創(chuàng)建一個(gè) RuleExecutionSet對(duì)象。RuleRuntime對(duì)象用來得到一個(gè)session,RuleAdministrator用來管理所有的對(duì) 象。在往下是Drools核心API,它的核心是Rete算法實(shí)現(xiàn)。我在這里不打算詳細(xì)討論,但你可以看看 ExampleRuleEngineFacade的代碼。
           現(xiàn)在你已經(jīng)看到了在商業(yè)和科研方面使用規(guī)則引擎的一些例子,并對(duì)Drools項(xiàng)目有了基本的了解。在下一篇文章里,我將討論DRL文件的結(jié)構(gòu)和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 閱讀(1766) | 評(píng)論 (0)編輯 收藏

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

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

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

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

    • janino-2.3.2.jar

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

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

    Drools and Mandarax

    兩個(gè)項(xiàng)目做了兩件不同的事情: 一個(gè)是Forward Chaining,另一個(gè)是 backward chaining. Drools 是forward chaining的,  意味著 它對(duì)assert的對(duì)象反應(yīng), 事件驅(qū)動(dòng)的. Mandarax 是 backward chaining的, 像 prologue一樣, 你問它問題, 它試圖給你它知道的答案. 舉例來說, 在使用Drools的時(shí)候, 你可能會(huì)先assert 給它今天的日期, 如果它發(fā)現(xiàn)有匹配的規(guī)則的手,它會(huì)用事件的方式通知你"今天是你的生日". 在 backward chaining 的系統(tǒng), 你可能先問 "今天是我的生日嘛?" 系統(tǒng)會(huì)搜索它知道的, 然后告訴你答案.
    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 閱讀(1184) | 評(píng)論 (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 閱讀(1350) | 評(píng)論 (0)編輯 收藏

    始終會(huì)用上的Common BeanUtils

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

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

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

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

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

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

    如果要支持多個(gè)屬性的復(fù)合排序,如"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屬性不是普通類型,構(gòu)造函數(shù)需要再傳入一個(gè)comparator對(duì)象為age變量排序。
    另外, BeanCompartor本身的ComparebleComparator, 遇到屬性為null就會(huì)拋出異常, 也不能設(shè)定升序還是降序。這個(gè)時(shí)候又要借助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中的字符串綁定到對(duì)象的屬性

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

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

    不妨寫一個(gè)Binder自動(dòng)綁定所有屬性:

        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方法其實(shí)都會(huì)調(diào)用convert進(jìn)行轉(zhuǎn)換。
         但Converter只支持一些基本的類型,甚至連java.util.Date類型也不支持。而且它比較笨的一個(gè)地方是當(dāng)遇到不認(rèn)識(shí)的類型時(shí),居然會(huì)拋 出異常來。 對(duì)于Date類型,我參考它的sqldate類型實(shí)現(xiàn)了一個(gè)Converter,而且添加了一個(gè)設(shè)置日期格式的函數(shù)。
    要把這個(gè)Converter注冊(cè),需要如下語句:

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



    //因?yàn)橐?cè)converter,所以不能再使用BeanUtils的靜態(tài)方法了,必須創(chuàng)建BeanUtilsBean實(shí)例
    BeanUtilsBean beanUtils = new BeanUtilsBean(convertUtils,new PropertyUtilsBean());
    beanUtils.setProperty(bean, name, value);
    4 其他功能
    4.1 ConstructorUtils,動(dòng)態(tài)創(chuàng)建對(duì)象
         public static Object invokeConstructor(Class klass, Object arg)
    4.2 MethodUtils,動(dòng)態(tài)調(diào)用方法
        MethodUtils.invokeMethod(bean, methodName, parameter);

    4.3 PropertyUtils,當(dāng)屬性為Collection,Map時(shí)的動(dòng)態(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 動(dòng)態(tài)Bean 用DynaBean減除不必要的VO和FormBean 
    posted @ 2006-01-15 20:20 Dion 閱讀(5109) | 評(píng)論 (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 閱讀(1500) | 評(píng)論 (1)編輯 收藏

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

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

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

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

            新代碼(可在IE和MF中運(yùn)行):
                <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中運(yùn)行,但不會(huì)出錯(cuò)。所以,這種方案 tpl 部分仍與老代碼兼容。

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

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

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

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

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


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

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

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

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

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

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

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


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

    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 閱讀(941) | 評(píng)論 (1)編輯 收藏

    這里說說我的經(jīng)歷吧。大學(xué)前以及大學(xué)前面三年的經(jīng)歷就不說了,因?yàn)榇髮W(xué)前的高中就是好好學(xué)習(xí),大學(xué)前三年就是混過來的。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    1.如何獲得當(dāng)前文件路徑

    常用:

    字符串類型: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服務(wù)中

    (1).Weblogic

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

    (2).Tomcat

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

    (3).Resin

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

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

    在Java文件中g(shù)etResource或getResourceAsStream均可

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

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

    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)方法,不需要生成此類的實(shí)例,
    * 為避免生成此類的實(shí)例,構(gòu)造方法被申明為private類型的。
    * @since  0.1
    */

    public class FileUtil {
      /**
       * 私有構(gòu)造方法,防止類的實(shí)例化,因?yàn)楣ぞ哳惒恍枰獙?shí)例化。
       */
      private FileUtil() {

      }

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

    慮中。</b>
       * @param file 需要修改最后訪問時(shí)間的文件。
       * @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());
        }
      }

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

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

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

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

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

    慮中。</b>
       * @param fileNames 需要修改最后訪問時(shí)間的文件名數(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 存在時(shí)返回true,否則返回false。
       * @since  0.1
       */
      public static boolean isFileExist(String fileName) {
        return new File(fileName).isFile();
      }

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

      /**
       * 清空指定目錄中的文件。
       * 這個(gè)方法將盡可能刪除所有的文件,但是只要有一個(gè)文件沒有被刪除都會(huì)返回false。
       * 另外這個(gè)方法不會(huì)迭代刪除,即不會(huì)刪除子目錄及其內(nèi)容。
       * @param directory 要清空的目錄
       * @return 目錄下的所有文件都被成功刪除時(shí)返回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;
      }

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

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

      /**
       * 刪除指定目錄及其中的所有內(nèi)容。
       * @param dir 要?jiǎng)h除的目錄
       * @return 刪除成功時(shí)返回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 文件對(duì)應(yīng)的的URL地址
       * @throws MalformedURLException
       * @since  0.4
       * @deprecated 在實(shí)現(xiàn)的時(shí)候沒有注意到File類本身帶一個(gè)toURL方法將文件路徑轉(zhuǎn)換為URL。
       *             請(qǐng)使用File.toURL方法。
       */
      public static URL getURL(File file) throws MalformedURLException {
        String fileURL = "file:/" + file.getAbsolutePath();
        URL url = new URL(fileURL);
        return url;
      }

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

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

      /**
       * 將DOS/Windows格式的路徑轉(zhuǎn)換為UNIX/Linux格式的路徑。
       * 其實(shí)就是將路徑中的"\"全部換為"/",因?yàn)樵谀承┣闆r下我們轉(zhuǎn)換為這種方式比較方便,
       * 某中程度上說"/"比"\"更適合作為路徑分隔符,而且DOS/Windows也將它當(dāng)作路徑分隔符。
       * @param filePath 轉(zhuǎn)換前的路徑
       * @return 轉(zhuǎn)換后的路徑
       * @since  0.4
       */
      public static String toUNIXpath(String filePath) {
        return filePath.replace('\\', '/');
      }

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

      /**
       * 得到文件的類型。
       * 實(shí)際上就是得到文件名中最后一個(gè)“.”后面的部分。
       * @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);
        }
      }

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

      /**
       * 得到文件的名字部分。
       * 實(shí)際上就是路徑中的最后一個(gè)路徑分隔符后的部分。
       * @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);
        }
      }

      /**
       * 得到文件名中的父路徑部分。
       * 對(duì)兩種路徑分隔符都有效。
       * 不存在時(shí)返回""。
       * 如果文件名是以路徑分隔符結(jié)尾的則不考慮該分隔符,例如"/path/"返回""。
       * @param fileName 文件名
       * @return 父路徑,不存在或者已經(jīng)是父目錄時(shí)返回""
       * @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)的位置。
       * 對(duì)于DOS或者UNIX風(fēng)格的分隔符都可以。
       * @param fileName 文件路徑
       * @return 路徑分隔符在路徑中首次出現(xiàn)的位置,沒有出現(xiàn)時(shí)返回-1。
       * @since  0.5
       */
      public static int getPathIndex(String fileName) {
        int point = fileName.indexOf('/');
        if (point == -1) {
          point = fileName.indexOf('\\');
        }
        return point;
      }

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

      /**
       * 得到路徑分隔符在文件路徑中指定位置前最后出現(xiàn)的位置。
       * 對(duì)于DOS或者UNIX風(fēng)格的分隔符都可以。
       * @param fileName 文件路徑
       * @param fromIndex 開始查找的位置
       * @return 路徑分隔符在路徑中指定位置前最后出現(xiàn)的位置,沒有出現(xiàn)時(shí)返回-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 去掉類型部分的結(jié)果
       * @since  0.5
       */
      public static String trimType(String filename) {
        int index = filename.lastIndexOf(".");
        if (index != -1) {
          return filename.substring(0, index);
        }
        else {
          return filename;
        }
      }
      /**
       * 得到相對(duì)路徑。
       * 文件名不是目錄名的子節(jié)點(diǎn)時(shí)返回文件名。
       * @param pathName 目錄名
       * @param fileName 文件名
       * @return 得到文件名相對(duì)于目錄名的相對(duì)路徑,目錄下不存在該文件時(shí)返回文件名
       * @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()只會(huì)使用絕對(duì)路徑,相對(duì)沒用過,因?yàn)橐鄬?duì)于web服務(wù)器地址,比較麻煩

    還不如寫個(gè)配置文件來的快哪

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

    配 置文件是應(yīng)用系統(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一級(jí)獲取。關(guān)鍵是有時(shí)我們需要通過web修改配置文件,我們不 能將路徑寫死了。經(jīng)過測(cè)試覺得有以下心得:

    1.servlet中讀寫。如果運(yùn)用Struts 或者Servlet可以直接在初始化參數(shù)中配置,調(diào)用時(shí)根據(jù)servlet的getRealPath("/")獲取真實(shí)路徑,再根據(jù)String file = this.servlet.getInitParameter("abc");獲取相對(duì)的WEB-INF的相對(duì)路徑。
    例:
    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)置對(duì)象獲取可操作的絕對(duì)地址。
    例:
    // 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同級(jí)

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

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

    六種異常處理的陋習(xí)

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

    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 }


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

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

      反例之一:丟棄異常

      代碼:15行-18行。

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

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

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

      那么,應(yīng)該怎樣改正呢?主要有四個(gè)選擇:

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

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

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

      4、不要捕獲異常。

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

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

      代碼:15行。

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

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

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

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

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

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

      代碼:3行-14行。

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

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

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

      結(jié)論三:保證所有資源都被正確釋放。充分運(yùn)用finally關(guān)鍵詞。

    反例之四:不說明異常的詳細(xì)信息

      代碼:3行-18行。

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

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

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

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

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

      代碼:3行-14行。

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

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

      結(jié)論五:盡量減小try塊的體積。

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

      代碼:7行-11行。

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

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

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

      改寫后的代碼

      根據(jù)上面的討論,下面給出改寫后的代碼。也許有人會(huì)說它稍微有點(diǎn)?嗦,但是它有了比較完備的異常處理機(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ù)時(shí)出現(xiàn)SQL錯(cuò)誤", sqlex);
    }
    catch(IOException ioex)
    {
     throw new ApplicationException("寫入數(shù)據(jù)時(shí)出現(xiàn)IO錯(cuò)誤", ioex);
    }
    finally
    {
     if (conn != null) {
      try {
       conn.close();
      }
      catch(SQLException sqlex2)
      {
       System.err(this.getClass().getName() + ".mymethod - 不能關(guān)閉數(shù)據(jù)庫連接: " + sqlex2.toString());
      }
     }

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

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

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



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

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

    在SPRING中實(shí)現(xiàn)事務(wù)暫停

    作者:Juergen Hoeller

    譯者:xMatrix





    版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請(qǐng)務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明
    作者: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
    關(guān)鍵詞: Transaction Suspension Spring

    摘要

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

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

    POJO的聲明性事務(wù)

    作為Spring聲明性事務(wù)分離方式的樣例,讓我們來看一下Spring的樣例應(yīng)用PetClinic的中心服務(wù)外觀中的配置:
    清單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的標(biāo)準(zhǔn)XMLBean定義格式。定義了:
    1、一個(gè)DataSource引用,指向一個(gè)JNDI位置—在J2EE服務(wù)器管理下這將從JNDI環(huán)境中獲取特定的DataSource。
    2、一個(gè)應(yīng)用服務(wù)實(shí)現(xiàn)—這是一個(gè)POJO,封裝了業(yè)務(wù)和數(shù)據(jù)訪問邏輯。在這里實(shí)現(xiàn)了應(yīng)用中的Clinic服務(wù)接口。
    3、一個(gè)應(yīng)用服務(wù)的事務(wù)代理—這個(gè)代理為目標(biāo)服務(wù)定義了事務(wù)屬性,匹配特定的方法名模式并為之創(chuàng)建相應(yīng)的事務(wù)。在實(shí)際的事務(wù)管理中,代理指向一個(gè)PlatformTransactionManager實(shí)現(xiàn)。
    注意:除了顯式的代理定義,Spring還支持自動(dòng)代理機(jī)制和通過Commons Attributes或J2SE 5.0注解實(shí)現(xiàn)源程序級(jí)的元數(shù)據(jù)使用。這些可選方法的討論超過了本文的范圍。可以參考Spring的文檔來了解相關(guān)細(xì)節(jié)。


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



    這個(gè)接口的實(shí)現(xiàn)如下顯示,假設(shè)他使用JDBC來執(zhí)行必要的數(shù)據(jù)訪問。他通過bean屬性的設(shè)置方法來獲取JDBC的DataSource;這與上面的配置中的dataSource屬性定義相對(duì)應(yīng)。
    清單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) {
            ...
          }
        }

        ...
    }



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

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

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


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

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

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

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

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



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

    例如,一個(gè)PetClinic應(yīng)用的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的事務(wù)代理隱式地創(chuàng)建一個(gè)事務(wù)。當(dāng)storePet()調(diào)用返回時(shí),事務(wù)將提交或回滾。缺省情況下任何 RuntimeException或Error將導(dǎo)致回滾。實(shí)際的提交或回滾可以是可以定義的:Spring的事務(wù)屬性支持“回滾規(guī)則”的概念。

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


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

    PlatformTransactionManager策略

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

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

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

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

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

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

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

    為了實(shí)現(xiàn)事務(wù)的暫停,需要一個(gè) javax.transaction.TransactionManager實(shí)例,他提供了JTA定義的標(biāo)準(zhǔn)的暫停和恢復(fù)方法。不幸的是,J2EE沒有為 JTA TransactionManager定義標(biāo)準(zhǔn)的JNDI位置!因此,我們需要使用廠商自己的定位機(jī)制。
    清單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的標(biāo)準(zhǔn)JNDI位置還是可以增加一定的價(jià)值,特別是對(duì)輕量級(jí)容器如Spring,這樣任何J2EE服務(wù)器就可以用統(tǒng) 一的方式來定位JTA TransactionManager了。

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

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

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

    除了標(biāo)準(zhǔn)的JtaTransactionManager和其支持的通用配置選項(xiàng)外,Spring還提供了一個(gè)專用的WebLogicJtaTransactionManager適配器來直接利用WebLogic的JTA擴(kuò)展。

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

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

    Spring的WebLogicJtaTransactionManager有效地為基于Spring的應(yīng)用提供了WebLogic Server事務(wù)管理的全部功能。這使得Spring事務(wù)分離成為一種能與EJB CMT竟?fàn)幍漠a(chǎn)品,而且提供了相同級(jí)別的事務(wù)支持。

    Spring and EJB CMT

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

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

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

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

    超越 標(biāo)準(zhǔn)的JTA支持,Spring為WebLogic Server的JTA擴(kuò)展提供了完善的集成,可以支持高級(jí)特性如事務(wù)監(jiān)視和每事務(wù)隔離級(jí)別。通過對(duì)WebLogic Server的特殊支持,基于Spring的應(yīng)用可以完全利用WebLogic Server的事務(wù)管理功能。

    Spring事務(wù)分離是繼 EJB CMT之外的另一種可選方式,特別是對(duì)那些基于POJO的輕量級(jí)架構(gòu)。在那只是因?yàn)檫x擇LSSB(本地?zé)o狀態(tài)會(huì)話BEAN)來應(yīng)用聲明性事務(wù)的情況下,基 于Spring的POJO服務(wù)模型是一種可行的選擇,他提供了非常高層的靈活性、可測(cè)試性和重用性。

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

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

    主站蜘蛛池模板: 日日噜噜噜噜夜夜爽亚洲精品| 亚洲精品123区在线观看| 国产va免费观看| va亚洲va日韩不卡在线观看| 亚洲第一成年免费网站| 四色在线精品免费观看| 亚洲一区无码中文字幕乱码| 曰韩亚洲av人人夜夜澡人人爽| 美女扒开屁股让男人桶爽免费| 亚洲国产成人91精品| 免费福利在线播放| 亚洲成电影在线观看青青| 91九色老熟女免费资源站| 亚洲人成人77777网站不卡| 国色精品卡一卡2卡3卡4卡免费| 破了亲妺妺的处免费视频国产 | 亚洲欧洲AV无码专区| 黄网址在线永久免费观看 | 今天免费中文字幕视频| 亚洲成色在线影院| 3344永久在线观看视频免费首页| 亚洲最新永久在线观看| 免费一级毛片在线播放视频免费观看永久| 无码欧精品亚洲日韩一区夜夜嗨 | 日韩免费高清大片在线| 免费国产不卡午夜福在线 | 免费国产高清毛不卡片基地| 亚洲男人的天堂一区二区| 两个人日本WWW免费版| 国产性生交xxxxx免费| 国产精品免费视频观看拍拍| 亚洲AV无码不卡在线播放| 又粗又长又爽又长黄免费视频| 蜜桃视频在线观看免费网址入口| 亚洲av无码日韩av无码网站冲| 亚洲AV无码一区二区三区在线观看 | 亚洲Av无码精品色午夜| av无码久久久久不卡免费网站| 国产精品亚洲精品久久精品| 精品国产_亚洲人成在线高清| 日韩版码免费福利视频|