隨筆-204  評(píng)論-149  文章-0  trackbacks-0
          被ibm給鄙視后,好好看了一下Java的異常機(jī)制,的確自己太弱了,很多東西之前都沒(méi)有仔細(xì)的去了解研究。同時(shí)自己也確實(shí)沒(méi)有什么special的地方。下一步準(zhǔn)備在放假之前好好研究一下Java網(wǎng)絡(luò)編程,nio,mina以前雖然用了下也沒(méi)有深入。暑期不找實(shí)習(xí)了,學(xué)學(xué)Linux編程和ACE,再?gòu)?fù)習(xí)一下基礎(chǔ)好好充一下電,現(xiàn)在是筆試都過(guò)不了。

          轉(zhuǎn)帖地址
          http://www.javaeye.com/topic/72170
          為什么要在J2EE項(xiàng)目中談異常處理呢?可能許多java初學(xué)者都想說(shuō):“異常處理不就是try….catch…finally嗎?這誰(shuí)都會(huì)啊!”。筆者在初學(xué)java時(shí)也是這樣認(rèn)為的。如何在一個(gè)多層的j2ee項(xiàng)目中定義相應(yīng)的異常類(lèi)?在項(xiàng)目中的每一層如何進(jìn)行異常處理?異常何時(shí)被拋出?異常何時(shí)被記錄?異常該怎么記錄?何時(shí)需要把checked Exception轉(zhuǎn)化成unchecked Exception ,何時(shí)需要把unChecked Exception轉(zhuǎn)化成checked Exception?異常是否應(yīng)該呈現(xiàn)到前端頁(yè)面?如何設(shè)計(jì)一個(gè)異常框架?本文將就這些問(wèn)題進(jìn)行探討。
          1. JAVA異常處理
          在面向過(guò)程式的編程語(yǔ)言中,我們可以通過(guò)返回值來(lái)確定方法是否正常執(zhí)行。比如在一個(gè)c語(yǔ)言編寫(xiě)的程序中,如果方法正確的執(zhí)行則返回1.錯(cuò)誤則返回0。在vb或delphi開(kāi)發(fā)的應(yīng)用程序中,出現(xiàn)錯(cuò)誤時(shí),我們就彈出一個(gè)消息框給用戶(hù)。
          通過(guò)方法的返回值我們并不能獲得錯(cuò)誤的詳細(xì)信息。可能因?yàn)榉椒ㄓ刹煌某绦騿T編寫(xiě),當(dāng)同一類(lèi)錯(cuò)誤在不同的方法出現(xiàn)時(shí),返回的結(jié)果和錯(cuò)誤信息并不一致。
          所以java語(yǔ)言采取了一個(gè)統(tǒng)一的異常處理機(jī)制。
          什么是異常?運(yùn)行時(shí)發(fā)生的可被捕獲和處理的錯(cuò)誤。
          在java語(yǔ)言中,Exception是所有異常的父類(lèi)。任何異常都擴(kuò)展于Exception類(lèi)。Exception就相當(dāng)于一個(gè)錯(cuò)誤類(lèi)型。如果要定義一個(gè)新的錯(cuò)誤類(lèi)型就擴(kuò)展一個(gè)新的Exception子類(lèi)。采用異常的好處還在于可以精確的定位到導(dǎo)致程序出錯(cuò)的源代碼位置,并獲得詳細(xì)的錯(cuò)誤信息。
          Java異常處理通過(guò)五個(gè)關(guān)鍵字來(lái)實(shí)現(xiàn),try,catch,throw ,throws, finally。具體的異常處理結(jié)構(gòu)由try….catch….finally塊來(lái)實(shí)現(xiàn)。try塊存放可能出現(xiàn)異常的java語(yǔ)句,catch用來(lái)捕獲發(fā)生的異常,并對(duì)異常進(jìn)行處理。Finally塊用來(lái)清除程序中未釋放的資源。不管理try塊的代碼如何返回,finally塊都總是被執(zhí)行。
          一個(gè)典型的異常處理代碼
          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();//獲得數(shù)據(jù)連接    
           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(“關(guān)閉連接失敗!”,sqlEx);    
             }
              
          }
              
          return password;    
          }
              
          可以看出Java的異常處理機(jī)制具有的優(yōu)勢(shì):
          給錯(cuò)誤進(jìn)行了統(tǒng)一的分類(lèi),通過(guò)擴(kuò)展Exception類(lèi)或其子類(lèi)來(lái)實(shí)現(xiàn)。從而避免了相同的錯(cuò)誤可能在不同的方法中具有不同的錯(cuò)誤信息。在不同的方法中出現(xiàn)相同的錯(cuò)誤時(shí),只需要throw 相同的異常對(duì)象即可。
          獲得更為詳細(xì)的錯(cuò)誤信息。通過(guò)異常類(lèi),可以給異常更為詳細(xì),對(duì)用戶(hù)更為有用的錯(cuò)誤信息。以便于用戶(hù)進(jìn)行跟蹤和調(diào)試程序。
          把正確的返回結(jié)果與錯(cuò)誤信息分離。降低了程序的復(fù)雜度。調(diào)用者無(wú)需要對(duì)返回結(jié)果進(jìn)行更多的了解。
          強(qiáng)制調(diào)用者進(jìn)行異常處理,提高程序的質(zhì)量。當(dāng)一個(gè)方法聲明需要拋出一個(gè)異常時(shí),那么調(diào)用者必須使用try….catch塊對(duì)異常進(jìn)行處理。當(dāng)然調(diào)用者也可以讓異常繼續(xù)往上一層拋出。

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

          SQLException是checked異常。當(dāng)調(diào)用createStatement方法時(shí),java強(qiáng)制調(diào)用者必須對(duì)SQLException進(jìn)行捕獲處理。
          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();    
          }
             
          (當(dāng)然,像Connection,Satement這些資源是需要及時(shí)關(guān)閉的,這里僅是為了說(shuō)明checked 異常必須強(qiáng)制調(diào)用者進(jìn)行捕獲或繼續(xù)拋出)

          unChecked異常也稱(chēng)為運(yùn)行時(shí)異常,通常RuntimeException都表示用戶(hù)無(wú)法恢復(fù)的異常,如無(wú)法獲得數(shù)據(jù)庫(kù)連接,不能打開(kāi)文件等。雖然用戶(hù)也可以像處理checked異常一樣捕獲unChecked異常。但是如果調(diào)用者并沒(méi)有去捕獲unChecked異常時(shí),編譯器并不會(huì)強(qiáng)制你那么做。

          比如一個(gè)把字符轉(zhuǎn)換為整型數(shù)值的代碼如下:

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

          當(dāng)傳入的參數(shù)不能轉(zhuǎn)換成相應(yīng)的整數(shù)時(shí),將會(huì)拋出NumberFormatException。因?yàn)镹umberFormatException擴(kuò)展于RuntimeException,是unChecked異常。所以調(diào)用parseInt方法時(shí)無(wú)需要try…catch

          因?yàn)閖ava不強(qiáng)制調(diào)用者對(duì)unChecked異常進(jìn)行捕獲或往上拋出。所以程序員總是喜歡拋出unChecked異常。或者當(dāng)需要一個(gè)新的異常類(lèi)時(shí),總是習(xí)慣的從RuntimeException擴(kuò)展。當(dāng)你去調(diào)用它些方法時(shí),如果沒(méi)有相應(yīng)的catch塊,編譯器也總是讓你通過(guò),同時(shí)你也根本無(wú)需要去了解這個(gè)方法倒底會(huì)拋出什么異常。看起來(lái)這似乎倒是一個(gè)很好的辦法,但是這樣做卻是遠(yuǎn)離了java異常處理的真實(shí)意圖。并且對(duì)調(diào)用你這個(gè)類(lèi)的程序員帶來(lái)誤導(dǎo),因?yàn)檎{(diào)用者根本不知道需要在什么情況下處理異常。而checked異常可以明確的告訴調(diào)用者,調(diào)用這個(gè)類(lèi)需要處理什么異常。如果調(diào)用者不去處理,編譯器都會(huì)提示并且是無(wú)法編譯通過(guò)的。當(dāng)然怎么處理是由調(diào)用者自己去決定的。

          所以Java推薦人們?cè)趹?yīng)用代碼中應(yīng)該使用checked異常。就像我們?cè)谏瞎?jié)提到運(yùn)用異常的好外在于可以強(qiáng)制調(diào)用者必須對(duì)將會(huì)產(chǎn)生的異常進(jìn)行處理。包括在《java Tutorial》等java官方文檔中都把checked異常作為標(biāo)準(zhǔn)用法。
          使用checked異常,應(yīng)意味著有許多的try…catch在你的代碼中。當(dāng)在編寫(xiě)和處理越來(lái)越多的try…catch塊之后,許多人終于開(kāi)始懷疑checked異常倒底是否應(yīng)該作為標(biāo)準(zhǔn)用法了。
          甚至連大名鼎鼎的《thinking in java》的作者Bruce Eckel也改變了他曾經(jīng)的想法。Bruce Eckel甚至主張把unChecked異常作為標(biāo)準(zhǔn)用法。并發(fā)表文章,以試驗(yàn)checked異常是否應(yīng)該從java中去掉。Bruce Eckel語(yǔ):“當(dāng)少量代碼時(shí),checked異常無(wú)疑是十分優(yōu)雅的構(gòu)思,并有助于避免了許多潛在的錯(cuò)誤。但是經(jīng)驗(yàn)表明,對(duì)大量代碼來(lái)說(shuō)結(jié)果正好相反”
          關(guān)于checked異常和unChecked異常的詳細(xì)討論可以參考
          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異常會(huì)帶來(lái)許多的問(wèn)題。
          checked異常導(dǎo)致了太多的try…catch 代碼
          可能有很多checked異常對(duì)開(kāi)發(fā)人員來(lái)說(shuō)是無(wú)法合理地進(jìn)行處理的,比如SQLException。而開(kāi)發(fā)人員卻不得不去進(jìn)行try…catch。當(dāng)開(kāi)發(fā)人員對(duì)一個(gè)checked異常無(wú)法正確的處理時(shí),通常是簡(jiǎn)單的把異常打印出來(lái)或者是干脆什么也不干。特別是對(duì)于新手來(lái)說(shuō),過(guò)多的checked異常讓他感到無(wú)所適從。
          java 代碼
          try{    
                 ……    
                        Statement s 
          = con.createStatement();    
                        ……    
                 Catch(SQLException sqlEx)
          {    
                        sqlEx.PrintStackTrace();    
             }
              
             或者    
                 
          try{    
                 ……    
                        Statement s 
          = con.createStatement();    
                        ……    
                 Catch(SQLException sqlEx)
          {    
                    
          //什么也不干    
          }
              

          checked異常導(dǎo)致了許多難以理解的代碼產(chǎn)生
          當(dāng)開(kāi)發(fā)人員必須去捕獲一個(gè)自己無(wú)法正確處理的checked異常,通常的是重新封裝成一個(gè)新的異常后再拋出。這樣做并沒(méi)有為程序帶來(lái)任何好處。反而使代碼晚難以理解。
          就像我們使用JDBC代碼那樣,需要處理非常多的try…catch.,真正有用的代碼被包含在try…catch之內(nèi)。使得理解這個(gè)方法變理困難起來(lái)
          checked異常導(dǎo)致異常被不斷的封裝成另一個(gè)類(lèi)異常后再拋出
          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);    
                  }
              
              }
              

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

          checked異常導(dǎo)致破壞接口方法
          一個(gè)接口上的一個(gè)方法已被多個(gè)類(lèi)使用,當(dāng)為這個(gè)方法額外添加一個(gè)checked異常時(shí),那么所有調(diào)用此方法的代碼都需要修改。

          可見(jiàn)上面這些問(wèn)題都是因?yàn)檎{(diào)用者無(wú)法正確的處理checked異常時(shí)而被迫去捕獲和處理,被迫封裝后再重新拋出。這樣十分不方便,并不能帶來(lái)任何好處。在這種情況下通常使用unChecked異常。
          chekced異常并不是無(wú)一是處,checked異常比傳統(tǒng)編程的錯(cuò)誤返回值要好用得多。通過(guò)編譯器來(lái)確保正確的處理異常比通過(guò)返回值判斷要好得多。
          如果一個(gè)異常是致命的,不可恢復(fù)的。或者調(diào)用者去捕獲它沒(méi)有任何益處,使用unChecked異常。
          如果一個(gè)異常是可以恢復(fù)的,可以被調(diào)用者正確處理的,使用checked異常。
          在使用unChecked異常時(shí),必須在在方法聲明中詳細(xì)的說(shuō)明該方法可能會(huì)拋出的unChekced異常。由調(diào)用者自己去決定是否捕獲unChecked異常


          倒底什么時(shí)候使用checked異常,什么時(shí)候使用unChecked異常?并沒(méi)有一個(gè)絕對(duì)的標(biāo)準(zhǔn)。但是筆者可以給出一些建議
          當(dāng)所有調(diào)用者必須處理這個(gè)異常,可以讓調(diào)用者進(jìn)行重試操作;或者該異常相當(dāng)于該方法的第二個(gè)返回值。使用checked異常。
          這個(gè)異常僅是少數(shù)比較高級(jí)的調(diào)用者才能處理,一般的調(diào)用者不能正確的處理。使用unchecked異常。有能力處理的調(diào)用者可以進(jìn)行高級(jí)處理,一般調(diào)用者干脆就不處理。
          這個(gè)異常是一個(gè)非常嚴(yán)重的錯(cuò)誤,如數(shù)據(jù)庫(kù)連接錯(cuò)誤,文件無(wú)法打開(kāi)等。或者這些異常是與外部環(huán)境相關(guān)的。不是重試可以解決的。使用unchecked異常。因?yàn)檫@種異常一旦出現(xiàn),調(diào)用者根本無(wú)法處理。
          如果不能確定時(shí),使用unchecked異常。并詳細(xì)描述可能會(huì)拋出的異常,以讓調(diào)用者決定是否進(jìn)行處理。

          3. 設(shè)計(jì)一個(gè)新的異常類(lèi)
          在設(shè)計(jì)一個(gè)新的異常類(lèi)時(shí),首先看看是否真正的需要這個(gè)異常類(lèi)。一般情況下盡量不要去設(shè)計(jì)新的異常類(lèi),而是盡量使用java中已經(jīng)存在的異常類(lèi)。
          如java 代碼
          IllegalArgumentException, UnsupportedOperationException  

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

          方法methodA聲明會(huì)拋出ExceptionA.

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

          如上面的代碼中,ExceptionB嵌套一個(gè)ExceptionA.我們暫且把ExceptionA稱(chēng)為“起因異常”,因?yàn)镋xceptionA導(dǎo)致了ExceptionB的產(chǎn)生。這樣才不使異常信息丟失。
          所以我們?cè)诙x一個(gè)新的異常類(lèi)時(shí),必須提供這樣一個(gè)可以包含嵌套異常的構(gòu)造函數(shù)。并有一個(gè)私有成員來(lái)保存這個(gè)“起因異常”。
          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;    
          }
              
          }
              

          當(dāng)然,我們?cè)谡{(diào)用printStackTrace方法時(shí),需要把所有的“起因異常”的信息也同時(shí)打印出來(lái)。所以我們需要覆寫(xiě)printStackTrace方法來(lái)顯示全部的異常棧跟蹤。包括嵌套異常的棧跟蹤。
          java 代碼
          public void printStackTrace(PrintStrean ps){    
          if(cause == null){    
           
          super.printStackTrace(ps);    
          }
          else{    
           ps.println(
          this);    
           cause.printStackTrace(ps);    
          }
              
          }
              
          一個(gè)完整的支持嵌套的checked異常類(lèi)源碼如下。我們?cè)谶@里暫且把它叫做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);    
          }
              
          }
              

          同樣要設(shè)計(jì)一個(gè)unChecked異常類(lèi)也與上面一樣。只是需要繼承RuntimeException。
          4. 如何記錄異常
          作為一個(gè)大型的應(yīng)用系統(tǒng)都需要用日志文件來(lái)記錄系統(tǒng)的運(yùn)行,以便于跟蹤和記錄系統(tǒng)的運(yùn)行情況。系統(tǒng)發(fā)生的異常理所當(dāng)然的需要記錄在日志系統(tǒng)中。
          java 代碼
          public String getPassword(String userId)throws NoSuchUserException{    
          UserInfo user 
          = userDao.queryUserById(userId);    
          If(user 
          == null){    
           Logger.info(“找不到該用戶(hù)信息,userId
          =+userId);    
           
          throw new NoSuchUserException(“找不到該用戶(hù)信息,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(“找不到該用戶(hù)信息:”
          +userId+ex);    
             
          throw new Exception(ex);    
          }
              

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

          如果必須捕獲一個(gè)無(wú)法正確處理的異常,僅僅是把它封裝成另外一種異常往上拋出。不必再次把已經(jīng)被記錄過(guò)的異常再次記錄。

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

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

          捕獲到一個(gè)未記錄過(guò)的異常或外部系統(tǒng)異常時(shí),應(yīng)該記錄異常的詳細(xì)信息
          java 代碼
          try{    
                 ……    
                  String sql
          =”select * from userinfo”;    
                        Statement s 
          = con.createStatement();    
                        ……    
                 Catch(SQLException sqlEx)
          {    
                    Logger.error(“sql執(zhí)行錯(cuò)誤”
          +sql+sqlEx);    
          }
              

          究竟在哪里記錄異常信息,及怎么記錄異常信息,可能是見(jiàn)仁見(jiàn)智的問(wèn)題了。甚至有些系統(tǒng)讓異常類(lèi)一記錄異常。當(dāng)產(chǎn)生一個(gè)新異常對(duì)象時(shí),異常信息就被自動(dòng)記錄。
          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();    
          }
              

          這似乎看起來(lái)是十分美妙的,其實(shí)必然導(dǎo)致了異常被重復(fù)記錄。同時(shí)違反了“類(lèi)的職責(zé)分配原則”,是一種不好的設(shè)計(jì)。記錄異常不屬于異常類(lèi)的行為,記錄異常應(yīng)該由專(zhuān)門(mén)的日志系統(tǒng)去做。并且異常的記錄信息是不斷變化的。我們?cè)谟涗洰惓M瑧?yīng)該給更豐富些的信息。以利于我們能夠根據(jù)異常信息找到問(wèn)題的根源,以解決問(wèn)題。
          雖然我們對(duì)記錄異常討論了很多,過(guò)多的強(qiáng)調(diào)這些反而使開(kāi)發(fā)人員更為疑惑,一種好的方式是為系統(tǒng)提供一個(gè)異常處理框架。由框架來(lái)決定是否記錄異常和怎么記錄異常。而不是由普通程序員去決定。但是了解些還是有益的。

          5. J2EE項(xiàng)目中的異常處理
          目前,J2ee項(xiàng)目一般都會(huì)從邏輯上分為多層。比較經(jīng)典的分為三層:表示層,業(yè)務(wù)層,集成層(包括數(shù)據(jù)庫(kù)訪問(wèn)和外部系統(tǒng)的訪問(wèn))。
          J2ee項(xiàng)目有著其復(fù)雜性,J2ee項(xiàng)目的異常處理需要特別注意幾個(gè)問(wèn)題。
          在分布式應(yīng)用時(shí),我們會(huì)遇到許多checked異常。所有RMI調(diào)用(包括EJB遠(yuǎn)程接口調(diào)用)都會(huì)拋出java.rmi.RemoteException;同時(shí)RemoteException是checked異常,當(dāng)我們?cè)跇I(yè)務(wù)系統(tǒng)中進(jìn)行遠(yuǎn)程調(diào)用時(shí),我們都需要編寫(xiě)大量的代碼來(lái)處理這些checked異常。而一旦發(fā)生RemoteException這些checked異常對(duì)系統(tǒng)是非常嚴(yán)重的,幾乎沒(méi)有任何進(jìn)行重試的可能。也就是說(shuō),當(dāng)出現(xiàn)RemoteException這些可怕的checked異常,我們沒(méi)有任何重試的必要性,卻必須要編寫(xiě)大量的try…catch代碼去處理它。一般我們都是在最底層進(jìn)行RMI調(diào)用,只要有一個(gè)RMI調(diào)用,所有上層的接口都會(huì)要求拋出RemoteException異常。因?yàn)槲覀兲幚鞷emoteException的方式就是把它繼續(xù)往上拋。這樣一來(lái)就破壞了我們業(yè)務(wù)接口。RemoteException這些J2EE系統(tǒng)級(jí)的異常嚴(yán)重的影響了我們的業(yè)務(wù)接口。我們對(duì)系統(tǒng)進(jìn)行分層的目的就是減少系統(tǒng)之間的依賴(lài),每一層的技術(shù)改變不至于影響到其它層。

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

          同樣JDBC訪問(wèn)都會(huì)拋出SQLException的checked異常。

          為了避免系統(tǒng)級(jí)的checked異常對(duì)業(yè)務(wù)系統(tǒng)的深度侵入,我們可以為業(yè)務(wù)方法定義一個(gè)業(yè)務(wù)系統(tǒng)自己的異常。針對(duì)像SQLException,RemoteException這些非常嚴(yán)重的異常,我們可以新定義一個(gè)unChecked的異常,然后把SQLException,RemoteException封裝成unChecked異常后拋出。
          如果這個(gè)系統(tǒng)級(jí)的異常是要交由上一級(jí)調(diào)用者處理的,可以新定義一個(gè)checked的業(yè)務(wù)異常,然后把系統(tǒng)級(jí)的異常封存裝成業(yè)務(wù)級(jí)的異常后再拋出。
          一般地,我們需要定義一個(gè)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調(diào)用    
               s.executeQuery(sql);    
              …    
             }
          catch(SQLException ex){    
                
          throw new DataAccessException(“數(shù)據(jù)庫(kù)查詢(xún)失敗”+sql,ex);    
             }
              
          }
              
          }
              

          定義一個(gè)checked的業(yè)務(wù)異常,讓業(yè)務(wù)層的接口的所有方法都聲明拋出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表示層應(yīng)該是一個(gè)很薄的層,主要的功能為:獲得頁(yè)面請(qǐng)求,把頁(yè)面的參數(shù)組裝成POJO對(duì)象,調(diào)用相應(yīng)的業(yè)務(wù)方法,然后進(jìn)行頁(yè)面轉(zhuǎn)發(fā),把相應(yīng)的業(yè)務(wù)數(shù)據(jù)呈現(xiàn)給頁(yè)面。表示層需要注意一個(gè)問(wèn)題,表示層需要對(duì)數(shù)據(jù)的合法性進(jìn)行校驗(yàn),比如某些錄入域不能為空,字符長(zhǎng)度校驗(yàn)等。
          J2ee從頁(yè)面所有傳給后臺(tái)的參數(shù)都是字符型的,如果要求輸入數(shù)值或日期類(lèi)型的參數(shù)時(shí),必須把字符值轉(zhuǎn)換為相應(yīng)的數(shù)值或日期值。
          如果表示層代碼校驗(yàn)參數(shù)不合法時(shí),應(yīng)該返回到原始頁(yè)面,讓用戶(hù)重新錄入數(shù)據(jù),并提示相關(guān)的錯(cuò)誤信息。

          通常把一個(gè)從頁(yè)面?zhèn)鱽?lái)的參數(shù)轉(zhuǎn)換為數(shù)值,我們可以看到這樣的代碼
          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);    
               
          }
              

          上面的代碼應(yīng)該經(jīng)常見(jiàn)到,但是當(dāng)用戶(hù)從頁(yè)面錄入一個(gè)不能轉(zhuǎn)換為整型的字符或一個(gè)錯(cuò)誤的日期值。
          Integer.parse()方法被拋出一個(gè)NumberFormatException的unChecked異常。但是這個(gè)異常絕對(duì)不是一個(gè)致命的異常,一般當(dāng)用戶(hù)在頁(yè)面的錄入域錄入的值不合法時(shí),我們應(yīng)該提示用戶(hù)進(jìn)行重新錄入。但是一旦拋出unchecked異常,就沒(méi)有重試的機(jī)會(huì)了。像這樣的代碼造成大量的異常信息顯示到頁(yè)面。使我們的系統(tǒng)看起來(lái)非常的脆弱。
          同樣,SimpleDateFormat.parse()方法也會(huì)拋出ParseException的Checked異常
          這種情況我們都應(yīng)該捕獲這些unChecked異常,并給提示用戶(hù)重新錄入。
          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”,”不是合法的整數(shù)值”);    
             }
              
             …………    
               
           
          try{    
          SimpleDateFormat format 
          = new SimpleDateFormat(“MM/dd/yyyy”);    
           birthDay 
          = format.parse(birthDayStr);    
          }
          catch(ParseException ex){    
           error.reject(“birthDay”,”不是合法的日期,請(qǐng)錄入’MM
          /dd/yyy’格式的日期”);    
          }
              
               
          }
             

          在表示層一定要弄清楚調(diào)用方法的是否會(huì)拋出unChecked異常,什么情況下會(huì)拋出這些異常,并作出正確的處理。
          在表示層調(diào)用系統(tǒng)的業(yè)務(wù)方法,一般情況下是無(wú)需要捕獲異常的。如果調(diào)用的業(yè)務(wù)方法拋出的異常相當(dāng)于第二個(gè)返回值時(shí),在這種情況下是需要捕獲 ?這個(gè)是什么意思?
          posted on 2009-06-24 10:29 Frank_Fang 閱讀(2293) 評(píng)論(4)  編輯  收藏 所屬分類(lèi): Java編程

          評(píng)論:
          # re: [轉(zhuǎn)]J2EE項(xiàng)目異常處理 2009-06-24 11:18 | Frank_Fang
          最近剛好作了一個(gè)產(chǎn)品的異常處理規(guī)范,把我做的也拿出來(lái)曬曬,和大家討論一下。

          1、CheckException or UnCheckedException

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

          當(dāng)然,最大的缺陷就是對(duì)接口調(diào)用者的使用。至少UnCheckedException可以讓接口調(diào)用者選擇catch還是不catch。

          因?yàn)檫@是一個(gè)遺留系統(tǒng),都使用了CheckedException,不過(guò)好在使用的比較規(guī)范,沒(méi)有太大麻煩。

          2、異常信息

          因?yàn)殚_(kāi)發(fā)者眾多,異常信息五花八門(mén),有中文的有英文的,有簡(jiǎn)單的,有復(fù)雜的。散落在各個(gè)類(lèi)里,修改起來(lái)很麻煩。

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

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

          異常信息如果需要,可以保留現(xiàn)場(chǎng)信息。比如:“用戶(hù)[abc]已存在”。這樣的信息就比“用戶(hù)已存在”明確。這個(gè)功能可以通過(guò)java.text.MessageFormat實(shí)現(xiàn)。

          3、異常的鏈?zhǔn)綊伋?br />
          即,在調(diào)用底層接口時(shí)捕獲到異常,需要轉(zhuǎn)成其他的異常拋出時(shí),使用帶Throwable的構(gòu)造器。這樣可以保留最原始的出錯(cuò)信息。

          異常基類(lèi)代碼:

          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這個(gè)類(lèi)就是根據(jù)是否有現(xiàn)場(chǎng)信息,分別處理,得到完整異常信息表述字符串。

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

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

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

          但是,因?yàn)镋xception有一個(gè)構(gòu)造器:public Exception(Throwable cause),所以這樣的構(gòu)造器就是重載(overload),而不是覆蓋(Override)。重載,入?yún)⒂质歉缸宇?lèi)關(guān)系,這樣的方法很容易混淆,所以就沒(méi)有提供。

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

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

          一般來(lái)說(shuō),不管是拋出何種異常,就代表logic已經(jīng)發(fā)生錯(cuò)誤,而且基本不太可能恢復(fù),所以checked exception用處真的不大.

          個(gè)人觀點(diǎn),歡迎大家討論.
            回復(fù)  更多評(píng)論
            
          # re: [轉(zhuǎn)]J2EE項(xiàng)目異常處理[未登錄](méi) 2009-06-29 08:54 | zy
          濤哥,相信自己,你比我強(qiáng)些。。
          能去IBM的也不一定是牛人
            回復(fù)  更多評(píng)論
            
          # re: [轉(zhuǎn)]J2EE項(xiàng)目異常處理 2010-09-03 20:32 | study
          真的非常感謝這篇文章, 這個(gè)j2ee的異常處理規(guī)范讓我煩透了  回復(fù)  更多評(píng)論
            
          主站蜘蛛池模板: 澄江县| 正安县| 屯留县| 枞阳县| 饶平县| 丰宁| 永平县| 岳西县| 永德县| 罗源县| 广德县| 苍梧县| 新宾| 城步| 宁河县| 庆元县| 孙吴县| 辽阳市| 卢龙县| 汽车| 威远县| 衡阳县| 东港市| 文水县| 岑溪市| 招远市| 右玉县| 韶关市| 万年县| 本溪| 茌平县| 玛沁县| 鄂托克前旗| 左贡县| 河西区| 郯城县| 新丰县| 屏山县| 洛隆县| 吉隆县| 安西县|