qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請(qǐng)?jiān)L問 http://qaseven.github.io/

          Java Web項(xiàng)目整體異常處理機(jī)制

            在實(shí)際的j2ee項(xiàng)目中,系統(tǒng)內(nèi)部難免會(huì)出現(xiàn)一些異常,如果把異常放任不管直接打印到瀏覽器可能會(huì)讓用戶感覺莫名其妙,也有可能讓某些用戶找到破解系統(tǒng)的方法。

            出來工作一年時(shí)間了,我也大概對(duì)異常處理有了一些了解,在這呢小弟簡(jiǎn)單介紹下個(gè)人對(duì)異常處理的見解,拋磚引玉,希望各位大神提出寶貴的意見和建議。

             就拿spring+struts2+hibernate項(xiàng)目說明:通常一個(gè)頁面請(qǐng)求到后臺(tái)以后,首先是到action(也就是所謂mvc的 controller),在action層會(huì)調(diào)用業(yè)務(wù)邏輯service,servce層會(huì)調(diào)用持久層dao獲取數(shù)據(jù)。最后執(zhí)行結(jié)果會(huì)匯總到 action,然后通過action控制轉(zhuǎn)發(fā)到指定頁面,執(zhí)行流程如下圖所示:

             而這三層其實(shí)都有可能發(fā)生異常,比如dao層可能會(huì)有SQLException,service可能會(huì)有 NullPointException,action可能會(huì)有IOException,一但發(fā)生異常并且程序員未做處理,那么該層不會(huì)再往下執(zhí)行,而是向 調(diào)用自己的方法拋出異常,如果dao、service、action層都未處理異常的話,異常信息會(huì)拋到服務(wù)器,然后服務(wù)器會(huì)把異常直接打印到頁面,結(jié)果 就會(huì)如下圖所示:

            其實(shí)這種錯(cuò)誤對(duì)于客戶來說毫無意義,因?yàn)樗麄兺ǔJ强床欢@是什么意思的。

            剛學(xué)java的 時(shí)候,我們處理異常通常兩種方法:①直接throws,放任不管;②寫try...catch,在catch塊中不作任何操作,或者僅僅 printStackTrace()把異常打印到控制臺(tái)。第一種方法最后就造就了上圖的結(jié)果;而第二種方法更杯具:頁面不報(bào)錯(cuò),但是也不執(zhí)行用戶的請(qǐng)求, 簡(jiǎn)單的說,其實(shí)這就是bug(委婉點(diǎn):通常是這樣)!

            那么發(fā)生異常到底應(yīng)該怎么辦呢?我想在大家對(duì)java異常有一定了解以后,會(huì)知道:異常應(yīng)該在action控制轉(zhuǎn)發(fā)之前盡量處理,同時(shí)記錄log日志,然后在頁面以友好的錯(cuò)誤提示告訴用戶出錯(cuò)了。大家看下面的代碼:

          //創(chuàng)建日志對(duì)象
          Log log = LogFactory.getLog(this.getClass());
           
          //action層執(zhí)行數(shù)據(jù)添加操作
          public String save(){
             try{
                   //調(diào)用service的save方法
                   service.save(obj);
             }catch(Exception e){
                   log.error(...);   //記錄log日志
                return "error"; 到指定error頁面
             }
             return "success";
          }

            如果按照上面的方式處理異常以后,我們用戶最后看到的頁面可能就會(huì)是下面這種形式(我想這種錯(cuò)誤提示應(yīng)該稍微友好點(diǎn)了吧):

            然后我們回到剛才處理異常的地方,如果大家積累了一些項(xiàng)目經(jīng)驗(yàn)以后會(huì)發(fā)現(xiàn)使用上面那種處理異常的方式可能還不夠靈活:

            ①因?yàn)閟pring把大多數(shù)非運(yùn)行時(shí)異常都轉(zhuǎn)換成運(yùn)行時(shí)異常(RuntimeException)最后導(dǎo)致程序員根本不知道什么地方應(yīng)該進(jìn)行try...catch操作

            ②每個(gè)方法都重復(fù)寫try...catch,而且catch塊內(nèi)的代碼都很相似,這明顯做了很多重復(fù)工作而且還很容易出錯(cuò),同時(shí)也加大了單元測(cè)試的用例數(shù)(項(xiàng)目經(jīng)理通常喜歡根據(jù)代碼行來估算UT case)

            ③發(fā)生異常有很多種情況:可能有數(shù)據(jù)庫增刪改查錯(cuò)誤,可能是文件讀寫錯(cuò)誤,等等。用戶覺得每次發(fā)生異常都是“訪問過程中產(chǎn)生錯(cuò)誤,請(qǐng)重試”的提示完全不能說明錯(cuò)誤情況,他們希望讓異常信息更詳盡些,比如:在執(zhí)行數(shù)據(jù)刪除時(shí)發(fā)生錯(cuò)誤,這樣他們可以更準(zhǔn)確地給維護(hù)人員提供bug信息。

            如何解決上面的問題呢?我是這樣做的:JDK異常或自定義異常+異常攔截器

            struts2攔截器的作用在網(wǎng)上有很多資料,在此不再贅述,我的異常攔截器原理如下圖所示:

            首先我的action類、service類和dao類如果有必要捕獲異常,我都會(huì)try...catch,catch塊內(nèi)不記錄log,通常是拋出一個(gè)新異常,并且注明錯(cuò)誤信息:

          //action層執(zhí)行數(shù)據(jù)添加操作
          public String save(){
             try{
                   //調(diào)用service的save方法
                   service.save(obj);
             }catch(Exception e){
                //你問我為什么拋出Runtime異常?因?yàn)槲覒械迷诜椒ê髮憈hrows  xx
                throw new RuntimeException("添加數(shù)據(jù)時(shí)發(fā)生錯(cuò)誤!",e);
            }
             return "success";
          }

            然后在異常攔截器對(duì)異常進(jìn)行處理,看下面的代碼:

          public String intercept(ActionInvocation actioninvocation) {
           
            String result = null; // Action的返回值
            try {
             // 運(yùn)行被攔截的Action,期間如果發(fā)生異常會(huì)被catch住
             result = actioninvocation.invoke();
             return result;
            } catch (Exception e) {
             /**
              * 處理異常
              */
             String errorMsg = "未知錯(cuò)誤!";
             //通過instanceof判斷到底是什么異常類型
             if (e instanceof BaseException) {
              BaseException be = (BaseException) e;
              be.printStackTrace(); //開發(fā)時(shí)打印異常信息,方便調(diào)試
              if(be.getMessage()!=null||Constants.BLANK.equals(be.getMessage().trim())){
               //獲得錯(cuò)誤信息
               errorMsg = be.getMessage().trim();
              }
             } else if(e instanceof RuntimeException){
              //未知的運(yùn)行時(shí)異常
              RuntimeException re = (RuntimeException)e;
              re.printStackTrace();
             } else{
              //未知的嚴(yán)重異常
              e.printStackTrace();
             }
             //把自定義錯(cuò)誤信息
             HttpServletRequest request = (HttpServletRequest) actioninvocation
               .getInvocationContext().get(StrutsStatics.HTTP_REQUEST);
             
             /**
              * 發(fā)送錯(cuò)誤消息到頁面
              */
             request.setAttribute("errorMsg", errorMsg);
            
             /**
              * log4j記錄日志
              */
             Log log = LogFactory
               .getLog(actioninvocation.getAction().getClass());
             if (e.getCause() != null){
              log.error(errorMsg, e);
             }else{
              log.error(errorMsg, e);
             }
           
             return "error";
            }// ...end of catch
           }

            需要注意的是:在使用instanceof判斷異常類型的時(shí)候一定要從子到父依次找,比如BaseException繼承與RuntimeException,則必須首先判斷是否是BaseException再判斷是否是RuntimeException。

            最后在error JSP頁面顯示具體的錯(cuò)誤消息即可:

          <body>
          <s:if test="%{#request.errorMsg==null}">
           <p>對(duì)不起,系統(tǒng)發(fā)生了未知的錯(cuò)誤</p>
          </s:if>
          <s:else>
           <p>${requestScope.errorMsg}</p>
          </s:else>
          </body>

            以上方式可以攔截后臺(tái)代碼所有的異常,但如果出現(xiàn)數(shù)據(jù)庫連接異常時(shí)不能被捕獲的,大家可以使用struts2的全局異常處理機(jī)制來處理:

          <global-results>
           <result name="error" >/Web/common/page/error.jsp</result>
          </global-results>
            
          <global-exception-mappings>
           <exception-mapping result="error" exception="java.lang.Exception"></exception-mapping>
          </global-exception-mappings>

            上面這是一個(gè)很簡(jiǎn)單的異常攔截器,大家可以使用自定義異常,那樣會(huì)更靈活一些。

            以上異常攔截器可以使用其它很多技術(shù)替換:比如spring aop,servlet filter等,根據(jù)項(xiàng)目實(shí)際情況處理。

            【補(bǔ)充】ajax也可以進(jìn)行攔截,但是因?yàn)閍jax屬于異步操作,action通過response形式直接把數(shù)據(jù)返回給ajax回調(diào)函數(shù),如果發(fā)生異常,ajax是不會(huì)執(zhí)行頁面跳轉(zhuǎn)的,所以必須把錯(cuò)誤信息返回給回調(diào)函數(shù),我針對(duì)json數(shù)據(jù)的ajax是這樣做的:

             /**
              * 讀取文件,獲取對(duì)應(yīng)錯(cuò)誤消息
              */
             HttpServletResponse response = (HttpServletResponse)actioninvocation.getInvocationContext().get(StrutsStatics.HTTP_RESPONSE);
             response.setCharacterEncoding(Constants.ENCODING_UTF8);
             /**
              * 發(fā)送錯(cuò)誤消息到頁面
              */
             PrintWriter out;
             try {
              out = response.getWriter();
              Message msg = new Message(errorMsg);
              //把異常信息轉(zhuǎn)換成json格式返回給前臺(tái)
              out.print(JSONObject.fromObject(msg).toString());
             } catch (IOException e1) {
              throw e;
             }

            以上是個(gè)人拙見,勿拍磚,謝謝。

          posted on 2012-09-17 10:51 順其自然EVO 閱讀(1319) 評(píng)論(0)  編輯  收藏


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          <2012年9月>
          2627282930311
          2345678
          9101112131415
          16171819202122
          23242526272829
          30123456

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 云南省| 塔河县| 茌平县| 视频| 延川县| 电白县| 沙河市| 瑞金市| 海阳市| 多伦县| 高邑县| 赣榆县| 连云港市| 新巴尔虎右旗| 正宁县| 乌兰县| 罗平县| 娄底市| 新宁县| 望城县| 商丘市| 安平县| 河池市| 长海县| 阿坝县| 徐水县| 从江县| 晋城| 永福县| 卓尼县| 庄河市| 随州市| 张掖市| 克东县| 孝昌县| 祥云县| 隆昌县| 临泽县| 合江县| 刚察县| 沾益县|