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

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

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

    隨筆-204  評論-149  文章-0  trackbacks-0
    被ibm給鄙視后,好好看了一下Java的異常機制,的確自己太弱了,很多東西之前都沒有仔細的去了解研究。同時自己也確實沒有什么special的地方。下一步準備在放假之前好好研究一下Java網絡編程,nio,mina以前雖然用了下也沒有深入。暑期不找實習了,學學Linux編程和ACE,再復習一下基礎好好充一下電,現在是筆試都過不了。

    轉帖地址
    http://www.javaeye.com/topic/72170
    為什么要在J2EE項目中談異常處理呢?可能許多java初學者都想說:“異常處理不就是try….catch…finally嗎?這誰都會?。?#8221;。筆者在初學java時也是這樣認為的。如何在一個多層的j2ee項目中定義相應的異常類?在項目中的每一層如何進行異常處理?異常何時被拋出?異常何時被記錄?異常該怎么記錄?何時需要把checked Exception轉化成unchecked Exception ,何時需要把unChecked Exception轉化成checked Exception?異常是否應該呈現到前端頁面?如何設計一個異常框架?本文將就這些問題進行探討。
    1. JAVA異常處理
    在面向過程式的編程語言中,我們可以通過返回值來確定方法是否正常執行。比如在一個c語言編寫的程序中,如果方法正確的執行則返回1.錯誤則返回0。在vb或delphi開發的應用程序中,出現錯誤時,我們就彈出一個消息框給用戶。
    通過方法的返回值我們并不能獲得錯誤的詳細信息??赡芤驗榉椒ㄓ刹煌某绦騿T編寫,當同一類錯誤在不同的方法出現時,返回的結果和錯誤信息并不一致。
    所以java語言采取了一個統一的異常處理機制。
    什么是異常?運行時發生的可被捕獲和處理的錯誤。
    在java語言中,Exception是所有異常的父類。任何異常都擴展于Exception類。Exception就相當于一個錯誤類型。如果要定義一個新的錯誤類型就擴展一個新的Exception子類。采用異常的好處還在于可以精確的定位到導致程序出錯的源代碼位置,并獲得詳細的錯誤信息。
    Java異常處理通過五個關鍵字來實現,try,catch,throw ,throws, finally。具體的異常處理結構由try….catch….finally塊來實現。try塊存放可能出現異常的java語句,catch用來捕獲發生的異常,并對異常進行處理。Finally塊用來清除程序中未釋放的資源。不管理try塊的代碼如何返回,finally塊都總是被執行。
    一個典型的異常處理代碼
    java 代碼
    public String getPassword(String userId)throws DataAccessException{    
    String sql 
    = “select password from userinfo where userid=’”+userId +”’”;    
    String password 
    = null;    
    Connection con 
    = null;    
    Statement s 
    = null;    
    ResultSet rs 
    = null;    
    try{    
     con 
    = getConnection();//獲得數據連接    
     s = con.createStatement();    
     rs 
    = s.executeQuery(sql);    
     
    while(rs.next()){    
        password 
    = rs.getString(1);    
     }
        
     rs.close();    
     s.close();    
          
    }
        
    Catch(SqlException ex)
    {    
     
    throw new DataAccessException(ex);    
    }
        
    finally{    
     
    try{    
          
    if(con != null){    
            con.close();    
          }
        
     }
        
       Catch(SQLException sqlEx)
    {    
         
    throw new DataAccessException(“關閉連接失敗!”,sqlEx);    
       }
        
    }
        
    return password;    
    }
        
    可以看出Java的異常處理機制具有的優勢:
    給錯誤進行了統一的分類,通過擴展Exception類或其子類來實現。從而避免了相同的錯誤可能在不同的方法中具有不同的錯誤信息。在不同的方法中出現相同的錯誤時,只需要throw 相同的異常對象即可。
    獲得更為詳細的錯誤信息。通過異常類,可以給異常更為詳細,對用戶更為有用的錯誤信息。以便于用戶進行跟蹤和調試程序。
    把正確的返回結果與錯誤信息分離。降低了程序的復雜度。調用者無需要對返回結果進行更多的了解。
    強制調用者進行異常處理,提高程序的質量。當一個方法聲明需要拋出一個異常時,那么調用者必須使用try….catch塊對異常進行處理。當然調用者也可以讓異常繼續往上一層拋出。

    2. Checked 異常 還是 unChecked 異常?
    Java異常分為兩大類:checked 異常和unChecked 異常。所有繼承java.lang.Exception 的異常都屬于checked異常。所有繼承java.lang.RuntimeException的異常都屬于unChecked異常。
    當一個方法去調用一個可能拋出checked異常的方法,必須通過try…catch塊對異常進行捕獲進行處理或者重新拋出。
    我們看看Connection接口的createStatement()方法的聲明。
    public Statement createStatement() throws SQLException;

    SQLException是checked異常。當調用createStatement方法時,java強制調用者必須對SQLException進行捕獲處理。
    java 代碼
     public String getPassword(String userId){    
           
    try{    
           ……    
                  Statement s 
    = con.createStatement();    
                  ……    
           Catch(SQLException sqlEx)
    {    
                  ……    
       }
        
    ……    
    }
        
    或者
    java 代碼
    public String getPassword(String userId)throws SQLException{    
       Statement s 
    = con.createStatement();    
    }
       
    (當然,像Connection,Satement這些資源是需要及時關閉的,這里僅是為了說明checked 異常必須強制調用者進行捕獲或繼續拋出)

    unChecked異常也稱為運行時異常,通常RuntimeException都表示用戶無法恢復的異常,如無法獲得數據庫連接,不能打開文件等。雖然用戶也可以像處理checked異常一樣捕獲unChecked異常。但是如果調用者并沒有去捕獲unChecked異常時,編譯器并不會強制你那么做。

    比如一個把字符轉換為整型數值的代碼如下:

    java 代碼
    String str = “123”;    
    int value = Integer.parseInt(str); 
    parseInt的方法簽名為:
    java 代碼
    public static int parseInt(String s) throws NumberFormatException   

    當傳入的參數不能轉換成相應的整數時,將會拋出NumberFormatException。因為NumberFormatException擴展于RuntimeException,是unChecked異常。所以調用parseInt方法時無需要try…catch

    因為java不強制調用者對unChecked異常進行捕獲或往上拋出。所以程序員總是喜歡拋出unChecked異常?;蛘弋斝枰粋€新的異常類時,總是習慣的從RuntimeException擴展。當你去調用它些方法時,如果沒有相應的catch塊,編譯器也總是讓你通過,同時你也根本無需要去了解這個方法倒底會拋出什么異常。看起來這似乎倒是一個很好的辦法,但是這樣做卻是遠離了java異常處理的真實意圖。并且對調用你這個類的程序員帶來誤導,因為調用者根本不知道需要在什么情況下處理異常。而checked異常可以明確的告訴調用者,調用這個類需要處理什么異常。如果調用者不去處理,編譯器都會提示并且是無法編譯通過的。當然怎么處理是由調用者自己去決定的。

    所以Java推薦人們在應用代碼中應該使用checked異常。就像我們在上節提到運用異常的好外在于可以強制調用者必須對將會產生的異常進行處理。包括在《java Tutorial》等java官方文檔中都把checked異常作為標準用法。
    使用checked異常,應意味著有許多的try…catch在你的代碼中。當在編寫和處理越來越多的try…catch塊之后,許多人終于開始懷疑checked異常倒底是否應該作為標準用法了。
    甚至連大名鼎鼎的《thinking in java》的作者Bruce Eckel也改變了他曾經的想法。Bruce Eckel甚至主張把unChecked異常作為標準用法。并發表文章,以試驗checked異常是否應該從java中去掉。Bruce Eckel語:“當少量代碼時,checked異常無疑是十分優雅的構思,并有助于避免了許多潛在的錯誤。但是經驗表明,對大量代碼來說結果正好相反”
    關于checked異常和unChecked異常的詳細討論可以參考
    Alan Griffiths http://www.octopull.demon.co.uk/java/ExceptionalJava.html
    Bruce Eckel http://www.mindView.net/Etc/Disscussions/CheckedExceptions
    《java Tutorial》 http://java.sun.com/docs/books/tutorial/essential/exceptions/runtime.html

    使用checked異常會帶來許多的問題。
    checked異常導致了太多的try…catch 代碼
    可能有很多checked異常對開發人員來說是無法合理地進行處理的,比如SQLException。而開發人員卻不得不去進行try…catch。當開發人員對一個checked異常無法正確的處理時,通常是簡單的把異常打印出來或者是干脆什么也不干。特別是對于新手來說,過多的checked異常讓他感到無所適從。
    java 代碼
    try{    
           ……    
                  Statement s 
    = con.createStatement();    
                  ……    
           Catch(SQLException sqlEx)
    {    
                  sqlEx.PrintStackTrace();    
       }
        
       或者    
           
    try{    
           ……    
                  Statement s 
    = con.createStatement();    
                  ……    
           Catch(SQLException sqlEx)
    {    
              
    //什么也不干    
    }
        

    checked異常導致了許多難以理解的代碼產生
    當開發人員必須去捕獲一個自己無法正確處理的checked異常,通常的是重新封裝成一個新的異常后再拋出。這樣做并沒有為程序帶來任何好處。反而使代碼晚難以理解。
    就像我們使用JDBC代碼那樣,需要處理非常多的try…catch.,真正有用的代碼被包含在try…catch之內。使得理解這個方法變理困難起來
    checked異常導致異常被不斷的封裝成另一個類異常后再拋出
    java 代碼
    public void methodA()throws ExceptionA{    
             …..           
              
    throw new ExceptionA();         
    }
        
               
    public void methodB()throws ExceptionB{    
       
    try{    
          methodA();    
          ……    
       }
    catch(ExceptionA ex){    
       
    throw new ExceptionB(ex);    
       }
        
    }
        
                
            Public 
    void methodC()throws ExceptinC{    
           
    try{    
             methodB();    
             …    
           }
        
           
    catch(ExceptionB ex){    
              
    throw new ExceptionC(ex);    
            }
        
        }
        

    我們看到異常就這樣一層層無休止的被封裝和重新拋出。

    checked異常導致破壞接口方法
    一個接口上的一個方法已被多個類使用,當為這個方法額外添加一個checked異常時,那么所有調用此方法的代碼都需要修改。

    可見上面這些問題都是因為調用者無法正確的處理checked異常時而被迫去捕獲和處理,被迫封裝后再重新拋出。這樣十分不方便,并不能帶來任何好處。在這種情況下通常使用unChecked異常。
    chekced異常并不是無一是處,checked異常比傳統編程的錯誤返回值要好用得多。通過編譯器來確保正確的處理異常比通過返回值判斷要好得多。
    如果一個異常是致命的,不可恢復的?;蛘哒{用者去捕獲它沒有任何益處,使用unChecked異常。
    如果一個異常是可以恢復的,可以被調用者正確處理的,使用checked異常。
    在使用unChecked異常時,必須在在方法聲明中詳細的說明該方法可能會拋出的unChekced異常。由調用者自己去決定是否捕獲unChecked異常


    倒底什么時候使用checked異常,什么時候使用unChecked異常?并沒有一個絕對的標準。但是筆者可以給出一些建議
    當所有調用者必須處理這個異常,可以讓調用者進行重試操作;或者該異常相當于該方法的第二個返回值。使用checked異常。
    這個異常僅是少數比較高級的調用者才能處理,一般的調用者不能正確的處理。使用unchecked異常。有能力處理的調用者可以進行高級處理,一般調用者干脆就不處理。
    這個異常是一個非常嚴重的錯誤,如數據庫連接錯誤,文件無法打開等?;蛘哌@些異常是與外部環境相關的。不是重試可以解決的。使用unchecked異常。因為這種異常一旦出現,調用者根本無法處理。
    如果不能確定時,使用unchecked異常。并詳細描述可能會拋出的異常,以讓調用者決定是否進行處理。

    3. 設計一個新的異常類
    在設計一個新的異常類時,首先看看是否真正的需要這個異常類。一般情況下盡量不要去設計新的異常類,而是盡量使用java中已經存在的異常類。
    如java 代碼
    IllegalArgumentException, UnsupportedOperationException  

    不管是新的異常是chekced異常還是unChecked異常。我們都必須考慮異常的嵌套問題。
    java 代碼
    public void methodA()throws ExceptionA{    
             …..           
              
    throw new ExceptionA();         
    }
        

    方法methodA聲明會拋出ExceptionA.

    public void methodB()throws ExceptionB
    methodB聲明會拋出ExceptionB,當在methodB方法中調用methodA時,ExceptionA是無法處理的,所以ExceptionA應該繼續往上拋出。一個辦法是把methodB聲明會拋出ExceptionA.但這樣已經改變了MethodB的方法簽名。一旦改變,則所有調用methodB的方法都要進行改變。
    另一個辦法是把ExceptionA封裝成ExceptionB,然后再拋出。如果我們不把ExceptionA封裝在ExceptionB中,就丟失了根異常信息,使得無法跟蹤異常的原始出處。
    java 代碼
    public void methodB()throws ExceptionB{    
       
    try{    
          methodA();    
          ……    
       }
    catch(ExceptionA ex){    
         
    throw new ExceptionB(ex);    
       }
        
    }
       

    如上面的代碼中,ExceptionB嵌套一個ExceptionA.我們暫且把ExceptionA稱為“起因異常”,因為ExceptionA導致了ExceptionB的產生。這樣才不使異常信息丟失。
    所以我們在定義一個新的異常類時,必須提供這樣一個可以包含嵌套異常的構造函數。并有一個私有成員來保存這個“起因異常”。
    java 代碼
    public Class ExceptionB extends Exception{    
    private Throwable cause;    
         
    public ExceptionB(String msg, Throwable ex){    
     
    super(msg);    
     
    this.cause = ex;    
    }
        
         
    public ExceptionB(String msg){    
     
    super(msg);    
    }
        
         
    public ExceptionB(Throwable ex){    
     
    this.cause = ex;    
    }
        
    }
        

    當然,我們在調用printStackTrace方法時,需要把所有的“起因異常”的信息也同時打印出來。所以我們需要覆寫printStackTrace方法來顯示全部的異常棧跟蹤。包括嵌套異常的棧跟蹤。
    java 代碼
    public void printStackTrace(PrintStrean ps){    
    if(cause == null){    
     
    super.printStackTrace(ps);    
    }
    else{    
     ps.println(
    this);    
     cause.printStackTrace(ps);    
    }
        
    }
        
    一個完整的支持嵌套的checked異常類源碼如下。我們在這里暫且把它叫做NestedException

    java 代碼
    public NestedException extends Exception{    
    private Throwable cause;    
    public NestedException (String msg){    
     
    super(msg);    
    }
        
         
    public NestedException(String msg, Throwable ex){    
     
    super(msg);    
     This.cause 
    = ex;    
    }
        
         
    public Throwable getCause(){    
     
    return (this.cause == null ? this :this.cause);    
    }
        
         
    public getMessage(){    
     String message 
    = super.getMessage();    
     Throwable cause 
    = getCause();    
       
    if(cause != null){    
         message 
    = message + “;nested Exception is ” + cause;    
       }
        
     
    return message;    
    }
        
    public void printStackTrace(PrintStream ps){    
     
    if(getCause == null){    
        
    super.printStackTrace(ps);    
            
     }
    else{    
     ps.println(
    this);    
     getCause().printStackTrace(ps);    
     }
        
    }
        
         
    public void printStackTrace(PrintWrite pw){    
     
    if(getCause() == null){    
        
    super.printStackTrace(pw);    
     }
        
     
    else{    
        pw.println(
    this);    
        getCause().printStackTrace(pw);    
     }
        
    }
        
    public void printStackTrace(){    
     printStackTrace(System.error);    
    }
        
    }
        

    同樣要設計一個unChecked異常類也與上面一樣。只是需要繼承RuntimeException。
    4. 如何記錄異常
    作為一個大型的應用系統都需要用日志文件來記錄系統的運行,以便于跟蹤和記錄系統的運行情況。系統發生的異常理所當然的需要記錄在日志系統中。
    java 代碼
    public String getPassword(String userId)throws NoSuchUserException{    
    UserInfo user 
    = userDao.queryUserById(userId);    
    If(user 
    == null){    
     Logger.info(“找不到該用戶信息,userId
    =+userId);    
     
    throw new NoSuchUserException(“找不到該用戶信息,userId=+userId);    
    }
        
    else{    
     
    return user.getPassword();    
    }
        
    }
        
         
    public void sendUserPassword(String userId)throws Exception {    
    UserInfo user 
    = null;    
    try{    
      user 
    = getPassword(userId);    
       
    //……..    
     sendMail();    
     
    //    
    }
    catch(NoSuchUserException ex)(    
       logger.error(“找不到該用戶信息:”
    +userId+ex);    
       
    throw new Exception(ex);    
    }
        

    我們注意到,一個錯誤被記錄了兩次.在錯誤的起源位置我們僅是以info級別進行記錄。而在sendUserPassword方法中,我們還把整個異常信息都記錄了。
    筆者曾看到很多項目是這樣記錄異常的,不管三七二一,只有遇到異常就把整個異常全部記錄下。如果一個異常被不斷的封裝拋出多次,那么就被記錄了多次。那么異常倒底該在什么地方被記錄?
    異常應該在最初產生的位置記錄!

    如果必須捕獲一個無法正確處理的異常,僅僅是把它封裝成另外一種異常往上拋出。不必再次把已經被記錄過的異常再次記錄。

    如果捕獲到一個異常,但是這個異常是可以處理的。則無需要記錄異常

    public Date getDate(String str){    
     Date applyDate 
    = null;    
    SimpleDateFormat format 
    = new SimpleDateFormat(“MM/dd/yyyy”);    
    try{    
     applyDate 
    = format.parse(applyDateStr);    
    }
        
    catch(ParseException ex){    
     
    //乎略,當格式錯誤時,返回null    
    }
        
    return applyDate;    
    }
        

    捕獲到一個未記錄過的異常或外部系統異常時,應該記錄異常的詳細信息
    java 代碼
    try{    
           ……    
            String sql
    =”select * from userinfo”;    
                  Statement s 
    = con.createStatement();    
                  ……    
           Catch(SQLException sqlEx)
    {    
              Logger.error(“sql執行錯誤”
    +sql+sqlEx);    
    }
        

    究竟在哪里記錄異常信息,及怎么記錄異常信息,可能是見仁見智的問題了。甚至有些系統讓異常類一記錄異常。當產生一個新異常對象時,異常信息就被自動記錄。
    java 代碼
    public class BusinessException extends Exception {    
          
    private void logTrace() {    
              StringBuffer buffer
    =new StringBuffer();    
              buffer.append(
    "Business Error in Class: ");    
              buffer.append(getClassName());    
              buffer.append(
    ",method: ");    
              buffer.append(getMethodName());    
              buffer.append(
    ",messsage: ");    
              buffer.append(
    this.getMessage());    
              logger.error(buffer.toString());    
                  
    }
        
    public BusinessException(String s) {    
             
    super(s);    
             logTrace();    
    }
        

    這似乎看起來是十分美妙的,其實必然導致了異常被重復記錄。同時違反了“類的職責分配原則”,是一種不好的設計。記錄異常不屬于異常類的行為,記錄異常應該由專門的日志系統去做。并且異常的記錄信息是不斷變化的。我們在記錄異常同應該給更豐富些的信息。以利于我們能夠根據異常信息找到問題的根源,以解決問題。
    雖然我們對記錄異常討論了很多,過多的強調這些反而使開發人員更為疑惑,一種好的方式是為系統提供一個異常處理框架。由框架來決定是否記錄異常和怎么記錄異常。而不是由普通程序員去決定。但是了解些還是有益的。

    5. J2EE項目中的異常處理
    目前,J2ee項目一般都會從邏輯上分為多層。比較經典的分為三層:表示層,業務層,集成層(包括數據庫訪問和外部系統的訪問)。
    J2ee項目有著其復雜性,J2ee項目的異常處理需要特別注意幾個問題。
    在分布式應用時,我們會遇到許多checked異常。所有RMI調用(包括EJB遠程接口調用)都會拋出java.rmi.RemoteException;同時RemoteException是checked異常,當我們在業務系統中進行遠程調用時,我們都需要編寫大量的代碼來處理這些checked異常。而一旦發生RemoteException這些checked異常對系統是非常嚴重的,幾乎沒有任何進行重試的可能。也就是說,當出現RemoteException這些可怕的checked異常,我們沒有任何重試的必要性,卻必須要編寫大量的try…catch代碼去處理它。一般我們都是在最底層進行RMI調用,只要有一個RMI調用,所有上層的接口都會要求拋出RemoteException異常。因為我們處理RemoteException的方式就是把它繼續往上拋。這樣一來就破壞了我們業務接口。RemoteException這些J2EE系統級的異常嚴重的影響了我們的業務接口。我們對系統進行分層的目的就是減少系統之間的依賴,每一層的技術改變不至于影響到其它層。

    java 代碼
    //    
    public class UserSoaImpl implements UserSoa{    
       
    public UserInfo getUserInfo(String userId)throws RemoteException{    
          
    //……    
    遠程方法調用.    
          
    //……    
       }
        
    }
        
    public interface UserManager{    
       
    public UserInfo getUserInfo(Stirng userId)throws RemoteException;    
    }
        

    同樣JDBC訪問都會拋出SQLException的checked異常。

    為了避免系統級的checked異常對業務系統的深度侵入,我們可以為業務方法定義一個業務系統自己的異常。針對像SQLException,RemoteException這些非常嚴重的異常,我們可以新定義一個unChecked的異常,然后把SQLException,RemoteException封裝成unChecked異常后拋出。
    如果這個系統級的異常是要交由上一級調用者處理的,可以新定義一個checked的業務異常,然后把系統級的異常封存裝成業務級的異常后再拋出。
    一般地,我們需要定義一個unChecked異常,讓集成層接口的所有方法都聲明拋出這unChecked異常。
    java 代碼
    public DataAccessException extends RuntimeException{    
     ……    
    }
        
    public interface UserDao{    
     
    public String getPassword(String userId)throws DataAccessException;    
    }
        
         
    public class UserDaoImpl implements UserDAO{    
    public String getPassword(String userId)throws DataAccessException{    
     String sql 
    = “select password from userInfo where userId= ‘”+userId+”’”;    
    try{    
        …    
         
    //JDBC調用    
         s.executeQuery(sql);    
        …    
       }
    catch(SQLException ex){    
          
    throw new DataAccessException(“數據庫查詢失敗”+sql,ex);    
       }
        
    }
        
    }
        

    定義一個checked的業務異常,讓業務層的接口的所有方法都聲明拋出Checked異常.

    java 代碼
    public class BusinessException extends Exception{    
     …..    
    }
        
         
    public interface UserManager{    
       
    public Userinfo copyUserInfo(Userinfo user)throws BusinessException{    
          Userinfo newUser 
    = null;    
          
    try{    
            newUser 
    = (Userinfo)user.clone();    
    }
    catch(CloneNotSupportedException ex){    
     
    throw new BusinessException(“不支持clone方法:”+Userinfo.class.getName(),ex);    
    }
        
     }
        
    }
       

    J2ee表示層應該是一個很薄的層,主要的功能為:獲得頁面請求,把頁面的參數組裝成POJO對象,調用相應的業務方法,然后進行頁面轉發,把相應的業務數據呈現給頁面。表示層需要注意一個問題,表示層需要對數據的合法性進行校驗,比如某些錄入域不能為空,字符長度校驗等。
    J2ee從頁面所有傳給后臺的參數都是字符型的,如果要求輸入數值或日期類型的參數時,必須把字符值轉換為相應的數值或日期值。
    如果表示層代碼校驗參數不合法時,應該返回到原始頁面,讓用戶重新錄入數據,并提示相關的錯誤信息。

    通常把一個從頁面傳來的參數轉換為數值,我們可以看到這樣的代碼
    java 代碼
    ModeAndView handleRequest(HttpServletRequest request,HttpServletResponse response)throws Exception{    
       String ageStr 
    = request.getParameter(“age”);    
       
    int age = Integer.parse(ageStr);    
       …………    
         
     String birthDayStr 
    = request.getParameter(“birthDay”);    
    SimpleDateFormat format 
    = new SimpleDateFormat(“MM/dd/yyyy”);    
    Date birthDay 
    = format.parse(birthDayStr);    
         
    }
        

    上面的代碼應該經常見到,但是當用戶從頁面錄入一個不能轉換為整型的字符或一個錯誤的日期值。
    Integer.parse()方法被拋出一個NumberFormatException的unChecked異常。但是這個異常絕對不是一個致命的異常,一般當用戶在頁面的錄入域錄入的值不合法時,我們應該提示用戶進行重新錄入。但是一旦拋出unchecked異常,就沒有重試的機會了。像這樣的代碼造成大量的異常信息顯示到頁面。使我們的系統看起來非常的脆弱。
    同樣,SimpleDateFormat.parse()方法也會拋出ParseException的Checked異常。
    這種情況我們都應該捕獲這些unChecked異常,并給提示用戶重新錄入。
    java 代碼
    ModeAndView handleRequest(HttpServletRequest request,HttpServletResponse response)throws Exception{    
       String ageStr 
    = request.getParameter(“age”);    
    String birthDayStr 
    = request.getParameter(“birthDay”);    
       
    int age = 0;    
     Date birthDay 
    = null;    
    try{    
    age
    =Integer.parse(ageStr);    
       }
    catch(NumberFormatException ex){    
         error.reject(“age”,”不是合法的整數值”);    
       }
        
       …………    
         
     
    try{    
    SimpleDateFormat format 
    = new SimpleDateFormat(“MM/dd/yyyy”);    
     birthDay 
    = format.parse(birthDayStr);    
    }
    catch(ParseException ex){    
     error.reject(“birthDay”,”不是合法的日期,請錄入’MM
    /dd/yyy’格式的日期”);    
    }
        
         
    }
       

    在表示層一定要弄清楚調用方法的是否會拋出unChecked異常,什么情況下會拋出這些異常,并作出正確的處理。
    在表示層調用系統的業務方法,一般情況下是無需要捕獲異常的。如果調用的業務方法拋出的異常相當于第二個返回值時,在這種情況下是需要捕獲 ?這個是什么意思?
    posted on 2009-06-24 10:29 Frank_Fang 閱讀(2281) 評論(4)  編輯  收藏 所屬分類: Java編程

    評論:
    # re: [轉]J2EE項目異常處理 2009-06-24 11:18 | Frank_Fang
    最近剛好作了一個產品的異常處理規范,把我做的也拿出來曬曬,和大家討論一下。

    1、CheckException or UnCheckedException

    個人傾向用UnCheckedException。我見過的最多的處理異常的代碼就是記錄日志或轉換后拋出,好像做其他操作的少之又少。我以前還見過有人不管三七二十一,抓到什么拋什么,結果一個接口拋出了3-5種CheckException。別扭啊,呵呵。

    當然,最大的缺陷就是對接口調用者的使用。至少UnCheckedException可以讓接口調用者選擇catch還是不catch。

    因為這是一個遺留系統,都使用了CheckedException,不過好在使用的比較規范,沒有太大麻煩。

    2、異常信息

    因為開發者眾多,異常信息五花八門,有中文的有英文的,有簡單的,有復雜的。散落在各個類里,修改起來很麻煩。

    我的處理辦法是,定義異常碼,異常信息和異常碼一一對應,定義在properties文件里,拋出異常的地方只引用異常碼。這樣,如果需要修改異常信息,只要修改properties文件重新打包即可。異常碼分為原因碼和位置碼。位置碼一般用在業務邏輯類里。比如一般用到的Facad模式,可能一個接口定義了n個方法,每個方法都拋出XXXBusinessException(XXX用于區分業務子包),拋出的異常碼在代碼里順序定義即可。原因碼表示某類異常,比如SQLException引起的異常都為8000。

    異常碼中原因碼按包、類、接口定義,比如:前兩位表示一級子包,接下來兩位留給二級子包,接下來兩位表示類,最后3位表示方法。

    異常信息如果需要,可以保留現場信息。比如:“用戶[abc]已存在”。這樣的信息就比“用戶已存在”明確。這個功能可以通過java.text.MessageFormat實現。

    3、異常的鏈式拋出

    即,在調用底層接口時捕獲到異常,需要轉成其他的異常拋出時,使用帶Throwable的構造器。這樣可以保留最原始的出錯信息。

    異常基類代碼:

    java 代碼
    public class BaseException extends Exception {    
            
            
        
    protected long errorCode;    
            
            
        
    protected String[] args;    
       
        
    /**   
         * 
    @param errorCode   
         
    */
       
        
    public BaseException(long errorCode) {    
            
    super();    
            
    this.errorCode = errorCode;    
        }
        
            
        
    /**   
         * 
    @param errorCode   
         * 
    @param cause   
         
    */
       
        
    public BaseException(long errorCode,Throwable cause) {    
            
    super(cause);    
            
    this.errorCode = errorCode;    
        }
        
            
        
    /**   
         * 
    @param errorCode   
         * 
    @param cause   
         * 
    @param args @see {@link java.text.MessageFormat#format(Object)}   
         
    */
       
        
    public BaseException(long errorCode,Throwable cause,String[] args) {    
            
    super(cause);    
            
    this.errorCode = errorCode;    
            
    this.args = args;    
        }
        
            
        
    /* (non-Javadoc)   
         * @see java.lang.Throwable#getMessage()   
         
    */
       
        
    public String getMessage() {    
            String message 
    = "";    
            
    if (this.args == null || this.args.length == 0{    
                message 
    = MessageSource.getInstance().resolveCodeWithoutArguments(    
                        
    this.errorCode);    
            }
     else {    
                message 
    = MessageSource.getInstance().resolveCode(this.errorCode)    
                        .format(
    this.args);    
            }
        
       
            
    return this.errorCode+":"+message;    
        }
        
       
        
    /**   
         * 
    @return the errorCode   
         
    */
       
        
    public long getErrorCode() {    
            
    return errorCode;    
        }
        
       
        
    /**   
         * 
    @return the args   
         
    */
       
        
    public String[] getArgs() {    
            
    return args;    
        }
        
       
    }
       
    其中:MessageSource這個類就是根據是否有現場信息,分別處理,得到完整異常信息表述字符串。

    關于主貼中嵌套打印的部分,從JDK1.4開始,Throwable就是嵌套打印的,所以不必再覆蓋那幾個方法了。

    現在假使,有AException 和BException,都從BaseException繼承。在方法里,我們捕獲了AException,需要轉換成BException拋出,這時候我們就非常希望能直接用AException的信息(errorCode和args)。所以,如果再來這樣一個構造器似乎更好:

    java 代碼
    public BaseException(BaseException cause){    
        
    this.errorCode = cause.getErrorCode();    
        
    this.args = cause.getArgs();    
    }
       

    但是,因為Exception有一個構造器:public Exception(Throwable cause),所以這樣的構造器就是重載(overload),而不是覆蓋(Override)。重載,入參又是父子類關系,這樣的方法很容易混淆,所以就沒有提供。

    我按照這個異常處理規范,花了兩三天時間把代碼重構了一遍。似乎用起來挺好使
      回復  更多評論
      
    # re: [轉]J2EE項目異常處理 2009-06-24 11:25 | Frank_Fang
    我的想法:
    1/ 根據系統的分層,每層自定義一個unchecked exception, exception里面只包含簡單的message信息即可, 這樣每層實現時,只需要拋出本層定義的exception并提供信息就可以了;
    2/ 還是根據系統分層,高層在調用底層的api時,一定要捕獲底層定義的這個exception(不過推薦是catch Exception()),并根據本層的logic決定是繼續拋出本層的exception還是進行其他處理;
    3/ 提供一個util方法可以拿到root exception的message,這樣可以保證即使進行多次包裝后,也能得到最初拋出exception的信息;
    4/ checked exception能不用就不用,除非有業務邏輯需要通過exception來實現不同的流程(這種情況通常也是可以避免的),還有就是涉及到資源的情況,有可能必須使用checked exception,不過一般來說,這種情況都可以限制在本層內部,通過catch and rethrow我們自定義的異常來解決,只是需要注意在finally里面把資源釋放掉.

    曾經使用別人提供的api,里面會拋出一個自定義checked exception,可是我這邊的logic非常復雜,涉及到3到4層的調用,如果每層都try catch的話....最終只能將這個checked exception包裝成一個unchecked exception...世界清凈了

    一般來說,不管是拋出何種異常,就代表logic已經發生錯誤,而且基本不太可能恢復,所以checked exception用處真的不大.

    個人觀點,歡迎大家討論.
      回復  更多評論
      
    # re: [轉]J2EE項目異常處理[未登錄] 2009-06-29 08:54 | zy
    濤哥,相信自己,你比我強些。。
    能去IBM的也不一定是牛人
      回復  更多評論
      
    # re: [轉]J2EE項目異常處理 2010-09-03 20:32 | study
    真的非常感謝這篇文章, 這個j2ee的異常處理規范讓我煩透了  回復  更多評論
      
    主站蜘蛛池模板: 亚洲av日韩综合一区在线观看| caoporn成人免费公开| 亚洲精品无码专区久久久| 免费无码A片一区二三区| 久久这里只精品99re免费| 亚洲AV无码AV日韩AV网站| 亚洲欧洲日产专区| 亚洲熟妇丰满多毛XXXX| 国产高清在线精品免费软件 | 成人毛片免费在线观看| 国产精品免费一区二区三区四区| 免费很黄无遮挡的视频毛片| 亚洲日本在线电影| 亚洲成年人免费网站| 亚洲AV无码国产精品麻豆天美 | 亚洲一区二区在线视频| 国产啪亚洲国产精品无码| 国产色爽免费视频| 午夜免费福利影院| 91免费国产在线观看| 0588影视手机免费看片| 免费一级毛片无毒不卡| 三级黄色免费观看| h视频在线观看免费| 乱淫片免费影院观看| 羞羞的视频在线免费观看| 妇女自拍偷自拍亚洲精品| 亚洲国产精品无码中文lv| 亚洲精品无码人妻无码| 在线综合亚洲欧洲综合网站| 亚洲精品国产国语| 国产成人精品亚洲日本在线| 亚洲jjzzjjzz在线观看| 亚洲国产精品久久久久秋霞影院 | 丁香六月婷婷精品免费观看| 久久亚洲精品11p| 日韩国产精品亚洲а∨天堂免| 亚洲av片在线观看| 免费一区二区三区在线视频| 国产黄色片免费看| 中文成人久久久久影院免费观看|