springmvc篇:【HandlerExceptionResolver】

 

HandlerExceptionResolver是什麼?
用大白話說,在springmvc中,HandlerExceptionResolver就是用來處理我們controller(handler)中拋出的異常的。從何而知?看下面源碼:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    try {
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
        // 調用controller中處理請求的方法
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        }catch (Exception ex) {
            // 如果controller的處理請求的方法中拋出異常,便會在這被撲捉
            dispatchException = ex;
        }
    // 這個方法中發現dispatchException !==null,則會去看異常屬於哪個
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

看過我前面文章的都應該知道,ha.handle方法便是調用我們controller中處理請求方法的地方,那麼如果這時我們controller中拋出異常,就會在這裏被catch住,就會把異常賦值給dispatchException屬性。然後在processDispatchResult方法中,處理這個異常,我們看這個方法的原碼,如下:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
    if (exception != null) {
        // 當controller拋出異常時,便會調用這裏
        mv = processHandlerException(request, response, handler, exception);
        errorView = (mv != null);
    }
    ...其他部分省略...		
}

從源碼很清晰的看出,如果exception不爲空,那就直接通過processHandlerException方法來處理這個異常,源碼如下:

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
    Object handler, Exception ex) throws Exception {
    for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
        exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);	
	}
}

到這裏,是不是發現我們本章的豬腳HandlerExceptionResolver 出現了吧,HandlerExceptionResolver 顧名思義:異常解析器,你把一個異常扔到我的方法中,我便會處理這個異常。
從源碼中的for語句我們發現,這個HandlerExceptionResolver 會有好多個,springmvc提供了大約有哪些呢?看下圖:


至於handlerExceptionResolvers裏面會有哪些resolver,會根據情況而定。當通過遍歷拿到每個handlerExceptionResolver後,便調用resolveException方法去處理解析,那麼這個異常是不是當前這個解析器處理的範圍呢,這個可以通過在方法中自己進行判斷。比如springmvc提供的AbstractHandlerExceptionResolver這個類,看下圖的例子:

AbstractHandlerExceptionResolver的resolveException方法中便通過shouldApplyTo方法來判斷是不是有自己來處理這個異常。如果是,那麼就調用方法處理這個異常,並返回結果。這個結果就會是將在在頁面上顯示的東西了。

到此爲止,關於HandlerExceptionResolver是什麼時候執行的,怎麼執行的的整個過程就結束了,是不是很簡單吧^^。

下面,我將在通過一個實例,來給大家剖析一下:
這個例子的效果就是:
我在controller中拋出一個HttpRequestMethodNotSupportedException異常,然後會被容器中的異常解析器來處理這個異常,將對應解析器處理的結果顯示在頁面上。
例子如下:
首先,我在controller中拋出HttpRequestMethodNotSupportedException異常,代碼如下:

@Controller
public class RequestMappingHandlerMappingController {
	@RequestMapping("/hello")
	@ResponseBody
	public String helloAndView() throws  HttpRequestMethodNotSupportedException{
		int a = 2;
		if(a==2) {
			throw new HttpRequestMethodNotSupportedException("oh,no");
		}
		return "hello";
	}
}

然後,我發送請求http://localhost:8088/hello 進行處理,下面通過截來展示請求發起後到最後處理這個異常並展示的整個流程。
 第一步:DispatcherServlet的doDispatch方法會找到我們的這個controller作爲handler來處理請求,當進入controller的helloAndView方法後,通過判斷直接拋出我們的異常。
第二步:進入processDispatchResult方法來處理方法的返回結果(源碼都在上面有,自己看),因爲我們拋異常了,所以就會進出入processHandlerException方法來處理異常,這時便會從容器中將所有的異常解析器拿出來,然後遍歷來讓各個解析器來決定是否有自己來處理這個異常,此時容器中會有那些解析器呢,看下圖:

經過遍歷發現,我們controller拋出的HttpRequestMethodNotSupportedException異常由DefaultHandlerExceptionResolver來處理,爲什麼呢,看下圖:

DefaultHandlerExceptionResolver的doResolveException方法中,經判斷我們拋出的異常,正好符合它的條件,所有就會由這個resolver來處理這個異常,他是怎麼處理的呢?然後會把一個什麼結果顯示在頁面呢?看下面:

看到了吧,這裏直接把我們的異常信息進行包裝,然後通過response直接輸出到頁面了。雖然最後return了新的ModelAndView,但這個ModelAndView因爲裏面沒東西,在後面的程序中會被處理掉。這裏可以忽略。重點在於return之前的response.sendError方法,要知道這裏便是輸出我們頁面看到的內容的地方。
最終頁面的樣子如下:

觀察紅框內容,是不是有我們在controller中拋出的異常信息。
到這裏,對於HandlerExceptionResolver應該清楚多了吧。

凡事要做到舉一反三,也許有人會想,現在用的是springmvc自帶的,如果想自定義一個HandlerExceptionResolver來處理我們的某些指定的異常,然後用我們自定義的頁面來展示我們對異常的處理結果,該怎麼做?下面,我們就通過時下HandlerExceptionResolver接口,來自定義我們的Resolver,並通過我們自己的ModeAndView來顯示處理的結果。如下走起:
第一步:自定義一個實現了HandlerExceptionResolver接口的類,後面會用他來處理我們controller拋出的異常:


public class MyHandlerExceptionResolver implements HandlerExceptionResolver,Ordered{
	@Override
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
        // 如果是io異常,那麼就由我們這個resolver來處理,容器中其他resolver中都沒有處理這個異常的解析器,如果有的話,那麼就就需要看誰先執行了
		if (ex instanceof IOException) {
            // 設置通過exception.jsp頁面來展示對異常的處理結果
			ModelAndView modelAndView = new ModelAndView("exception");
            // exception.jsp頁面會打印下面這段話
			modelAndView.addObject("message", "i am a custom exception resolver page");
			return modelAndView;
		}
		return null;
	}

	/**
	 * 如果想優先讓我們自定義的異常解析器來處理異常,就實現Ordered接口,然後內容如下寫法
	 */
	@Override
	public int getOrder() {
		return Ordered.HIGHEST_PRECEDENCE;
	}
	
}

第二步:在spring-mvc.xml中聲明這個類,然後放到容器中

<bean class="com.lhb.MyHandlerExceptionResolver"/>

第三步:創建exception.jsp頁面(名字隨便起,只要跟上面代碼的ModeAndView中的參數值相同就行)

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<span>${message}</span><br/>
</body>
</html>

在這個頁面中,將會把MyHandlerExceptionResolver 中設置的message的值顯示出來。
第四步:在我們的controller中拋出IO異常,如下:

@Controller
public class RequestMappingHandlerMappingController {
	
	@RequestMapping("/hello")
	@ResponseBody
	public String helloAndView() throws  IOException{
		int a = 2;
		if(a==2) {
			throw new IOException("oh,no");
		}
		return "hello";
	}
}

第五步:上面代碼已就緒,現在訪問http://localhost:8088/hello 然後來看調用的過程,如下:
程序同樣DispatcherServlet的doDispatch方法,跑到controller的方法中,然後拋出異常,在進入到processDispatchResult方法中的processHandlerException方法,如下:

 

注意觀察紅框處,是不是發現我們自定義的HandlerExceptionResolver了。這裏經過排序,這樣就可以先使用我們自定義的異常解析類的resolveException方法中(如何排序在第一位,實現Ordered接口即可,代碼以實現)。如下:

因爲我們controller中拋出的是IOException,滿足自定義resolver中的條件,所以就有我們這個自定義類來進行處理,並且自定義了通過那個頁面來展示處理結果。這裏我們指定在exception.jsp頁面展示結果。當然你也可以進行更復雜的設定,比如可以通過handler的判斷來決定是通過頁面展示處理結果,還是通過response直接輸出一些東西。經過這一連串處理,邊跳轉到頁面,並顯示我麼指定的信息,如下:

這波操作後,對於HandlerExceptionResolver應該都明白了吧?再不明白請留言。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
在章節的最後出,我再把springmvc自帶的那些HandlerExceptionResolver來給大家簡單過一遍,來,走起:

  • ExceptionHandlerExceptionResolver
    當拋出異常的controller中找含有@ExceptionHandler註解的方法時(註解中的參數必須跟拋出異常的類型相同),那麼這個Resolver便會生效,這個Resolver便會調用哪個controller中的含有@ExceptionHandler的方法。
    如果在controller中找不到,則會去@ControllerAdvice註解的類中去找@ExceptionHandler註解的方法,如果有,並且跟這個這個註解裏的參數跟拋出異常相同,便會調用這個註解的方法來處理異常。看下面例子:
    @Controller
    public class RequestMappingHandlerMappingController {
    	@RequestMapping("/hello")
    	@ResponseBody
    	public String helloAndView() throws  IOException{
    		int a = 2;
    		if(a==2) {
    			throw new IOException("oh,no");
    		}
    		return "hello";
    	}
    	
    	@ExceptionHandler(IOException.class)
    	@ResponseBody
    	public String handleException() {
    		System.out.println();
    		return "aa";
    	}
    }

    代碼中通過@ExceptionHandler(IOException.class)註解來說明這個方法專門用來處理IOException異常的,所以會被ExceptionHandlerExceptionResolver來處理,並調用這個方法來處理異常。然後返回aa,並將aa顯示在瀏覽器上。
    這裏要說一下關於這個處理異常的返回值,這裏我麼返回的是字符串aa,其實這裏我們還可以返回jsp頁面,也可以返回ModelAndView對象。從源碼上看,調用這個controller中處理異常的方法與調用普通controller中的方法最終使用的代碼是相同的。都是通過ServletInvocableHandlerMethod的invokeAndHandle方法,最後通過反射調用我們controller中的方法,然後在獲取到返回值,再根據返回值設置view來顯示。如下:

    如果你不想把@ExceptionHandler註解的方法寫在當前controller中,可以通過專門寫一個@ControllerAdvice註解的類,然後在這個類用@ExceptionHandler來寫一些方法,然後專門來負責處理相關的異常。之所以可以這樣做,是因爲源碼中明確說明,噹噹前controller中沒有能處理相關異常的@ExceptionHandler註解的方法時,便會逐層向上去找@ControllerAdvice註解的類中是否存在相關的處理對應異常的@ExceptionHandler註解的方法,所以我們可以把所以想要處理的異常都單獨寫在一個用@ControllerAdvice註解的類中,如下:

    @ControllerAdvice
    public class MyControllerAdvice {
    	@ExceptionHandler(IOException.class)
    	@ResponseBody
    	public String handleException() {
    		System.out.println();
    		return "aa";
    	}
        
        // 處理MyException異常方法,
        @ExceptionHandler(MyException.class)
        @ResponseBody
        public String handleException1(){
        }
        
        ...其他處理異常的方法....
        @ExceptionHandler(OtherException.class)
        @ResponseBody
        public String handleExceptionOther(){
        }
    }

    這樣我們就可以刪除掉上線那個controller中的了。效果都是一樣的。下面看一下瀏覽器的效果:

     

  • ResponseStatusExceptionResolver
    當我們的handler(controller)拋出異常時,如果這個異常時自定義異常,並且異常類上有@ResponseStatus註解,那麼這個類便會使用到,這個Resolver會獲取到@ResponseStatus中的屬性值,然後將這個值顯示在頁面上。
    另外,這個@ResponseStatus註解還可以配合上面的@ExceptionHandle一起使用,這樣的話當拋出的異常與@ExceptionHandle中指定的異常匹配時,便會把相對應的@ResponseStatus中的內容返回到頁面上。
    例子如下:

    @ResponseStatus(value=HttpStatus.FORBIDDEN, reason="請求的網頁不存在")
    public class MyException extends RuntimeException{
    
    }
    @Controller
    public class RequestMappingHandlerMappingController {
    	@RequestMapping("/hello")
    	@ResponseBody
    	public String helloAndView() throws  IOException{
    		int a = 2;
    		if(a==2) {
    			 throw new MyException();
    		}
    		return "hello";
    	}
    }

    效果如下:

    這樣當controller拋出異常後,就可以直接將@ResponseStatus中的reason和code顯示在頁面上了。
     

  • DefaultHandlerExceptionResolver
    當我們handler拋出的異常,屬於這個Resolver中的某一個是,那麼便會通過這個類來處理異常,並返回給頁面(或者其他客戶端)。給大家看一下源碼明白了,如下:

    注意紅框部分,我們最開始的時候的例子就是通過拋出這個異常,然後最後使用這個resolver來處理這個異常的。這裏再把例子粘貼一次:

    @Controller
    public class RequestMappingHandlerMappingController {
    	@RequestMapping("/hello")
    	@ResponseBody
    	public String helloAndView() throws  HttpRequestMethodNotSupportedException{
    		int a = 2;
    		if(a==2) {
    			throw new HttpRequestMethodNotSupportedException("oh,no");
    		}
    		return "hello";
    	}
    }

    效果如下:



    到這裏,關於HandlerExceptionResolver的講解就結束了。如果大家還有不清楚了的,或者想了解的,請留言!
     

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章