Springmv知識六------攔截器&異常處理
攔截器
攔截器概念
攔截器的主要作用就是攔截使用者的請求,在所匹配的目標方法之前進行執行。一般情況下,用作許可權驗證來判斷使用者是否登陸,還有就是商城中不登入不讓購買也可以利用攔截器進行驗證。
我們可以自定義攔截器來實現特定的功能,自定義的攔截器必須實現HandlerInterceptor介面。
並且實現以下方法:
preHandle():這個方法在業務處理器處理請求之前被呼叫,在該方法中可以對使用者請求Request進行處理。需要注意的是這個方法的返回值,如果我們在該攔截器對請求進行攔截處理後還要呼叫其他的攔截器,或者是業務處理器去進行處理,則返回true;如果我們不再需要呼叫其他的元件去處理請求,則返回false。
postHandle():*在業務處理器執行完目標方法後,但是是在DispatcherServlet向客戶端返回響應前被呼叫*,在該方法中對使用者請求Request進行處理。
afterCompletion():這個方法在DispatcherServlet完全處理完請求後被呼叫,可以在該方法中進行一些資源清理的操作。
配置自定義的攔截器
二步走:
第一步:編寫自定義攔截器
第二步:註冊自定義攔截器
<!-- 配置自定義攔截器 實現 HandlerInterceptor-->
public class FirstIntercept implements HandlerInterceptor{
/* 該方法在目標方法之前之前進行攔截呼叫
* 若該方法返回true,則會繼續呼叫後續的攔截器和目標方法
* 若方法返回false,則會終止後續攔截器和目標方法的執行。
* preHandle--》目標方法 --》postHandle--》afterCompletion
*
* 許可權 日誌 事務
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
System.out.println("FirstIntercept preHandle ");
//return false;
return true;
}
/*呼叫目標方法之後渲染檢視之前
* 可以對請求域中的屬性或者檢視做出修改
* */
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
System.out.println("FirstIntercept postHandle ");
}
/*渲染檢視之後
* 釋放資源
* */
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
System.out.println("FirstIntercept afterCompletion ");
}
}
<!-- 註冊自定義攔截器 -->
<mvc:interceptors>
<bean class="com.wf.springmvc.crud.intercept.FirstIntercept"></bean>
<bean class="com.wf.springmvc.crud.intercept.SecondIntercept"></bean>
</mvc:interceptors>
<!-- 配置自定義攔截器 方法二-->
<mvc:interceptors>
<!-- 配置攔截器(不)作用路徑 是requestmapping路徑 -->
<mvc:interceptor>
<!-- <mvc:exclude-mapping path="/list"/> -->
<mvc:mapping path="/list"/>
<bean class="com.wf.springmvc.crud.intercept.SecondIntercept"></bean>
</mvc:interceptor>
</mvc:interceptors>
注意:
在配置攔截器時,我們可以利用【mvc:interceptor】子節點,進行指定攔截器去作用在那些請求上面,例如上面的配置自定義攔截器 方法二 ,其實這樣可以大大優化我們的效能,因為這樣的話,我們可以指定在某些需要攔截器的目標方法上進行攔截,而對於不需要攔截的目標方法則不進行攔截。
攔截器執行順序
對於 單個攔截器,攔截的順序是按照下列順序進行,但是要注意的是此時的preHandle的返回值是true,如果返回值為false,則執行完HandlerAdapter的目標方法 之後就直接進行渲染檢視,而不再進行其他的處理
FirstIntercept的 preHandle—-》HandlerAdapter的目標方法 —-》FirstIntercept的postHandle—-》DispatcherServlet的渲染檢視 —-》FirstIntercept的afterCompletion
對於多個攔截器,執行順序就像剝洋蔥那樣層層遞進。是根據註冊自定義攔截器的順序進行執行。注意的是,下圖中的停止是直接停止,連目標方法都不會執行。
異常處理
Springmvc 通過HandlerExceptionResolver介面 處理程式的異常,包括Handler對映,資料繫結以及目標方法執行時發生的異常。
HandlerExceptionResolver介面的實現類有以下幾種,但是我們常用的是四個:
其中在我們配置了【mvc:annotation-driven】時,DispatcherServlet 會給我們裝配以下的三個預設的異常實現類,加斷點,檢視【DispatcherServlet 】的this變數中的【handlerExceptionResolver】
ExceptionHandlerExceptionResolver
1、ExceptionHandlerExceptionResolver主要處理Handler中用@ExceptionHandler註解定義的方法。
2、對於@ExceptionHandler註解的方法,如果是發生的NullPointException,但是在我們的宣告異常有RuntimeException和Exception,那麼此時會根據異常的最近繼承關係,找繼承深度最淺的那個,(就近原則) 。
3、ExceptionHandlerMethodResolver內部如果找不到@ExceptionHandler註解的話,就會找@ControllerAdvice中的@ExceptionHandler註解方法。
一般情況下,在我們的執行中出現異常的話,直接會在頁面進行報錯顯示,如果我們利用了我們的異常處理,就可以避免這樣。
<!--前臺連結 -->
<a href="TestExceptionHandlerExceptionResolver?i=2">Test ExceptionHandlerExceptionResolver </a> <br>
<!-- 後臺處理-->
@RequestMapping("TestExceptionHandlerExceptionResolver")
public String TestExceptionHandlerExceptionResolver(@RequestParam("i") int i){
System.out.println("result : "+10/i);
return "success";
}
當我們執行上述程式碼時,如果我們沒有進行異常處理的話,跳轉的頁面如下:(我們可以吧傳遞引數設定為0,讓其強制出錯)
但是如果我們在這個方法下面新增一個異常處理方法,即被@ExceptionHandler註解修飾的方法。則執行效果如下:
新增的程式碼:
@ExceptionHandler(value={ArithmeticException.class,IOException.class})
public ModelAndView TestExceptionHandlerExceptionResolver(Exception ex){
System.out.println("01----出異常了:"+ex);
ModelAndView model = new ModelAndView("error");
model.addObject("exception", ex);
return model;
}
@ExceptionHandler({ClassNotFoundException.class,ClassCastException.class})
public ModelAndView TestExceptionHandlerExceptionResolver02(Exception ex){
System.out.println("02----出異常了:"+ex);
ModelAndView model = new ModelAndView("error");
model.addObject("exception", ex);
return model;
}
注意:
1、上述我們定義了兩個@ExceptionHandler修飾的方法,但是一定要注意兩個方法的異常型別不能相同,這樣做的目的,是為了測試,當我們丟擲的異常不在這預定義的異常之中,會進行就近選擇進行處理。
2、@ExceptionHandler 註解 可以指定異常,可以為多個
3、@ExceptionHandler 修飾的方法,不能使用Map作為與前臺互動的資料儲存, 如果希望將錯誤新城傳遞到前臺,我們需要使用ModelAndView 作為檢視返回值 在model中進行異常的互動與顯示
4、@ExceptionHandler 方法有異常的優先順序,一般都是匹配相似度較高的
5、如果出現異常在本類中找不到@ExceptionHandler修飾的方法進行異常處理,則會進入到 有@ControllerAdvice修飾的類中去查詢@ExceptionHandler修飾的方法進行匹配
6、注意@ExceptionHandler可以匹配多個異常,但是在不同的方法中不能處理相同的異常,因為會不知道該用哪個方法進行處理,回報500異常
執行效果圖如下:
或者我們單獨寫出來一個類,用來存放異常處理,如下:
@ControllerAdvice
public class HandlerExceptionController {
@ExceptionHandler({ArithmeticException.class,IOException.class})
public ModelAndView TestExceptionHandlerExceptionResolver(Exception ex){
System.out.println("03----ControllerAdvice出異常了:"+ex);
ModelAndView model = new ModelAndView("error");
model.addObject("exception", ex);
return model;
}
}
注意使用類註解@ControllerAdvice和異常註解@ExceptionHandler。
這樣的話,當發生異常時,異常在本類中找不到@ExceptionHandler修飾的方法進行異常處理,則會進入到 有@ControllerAdvice修飾的類中去查詢@ExceptionHandler修飾的方法進行匹配。
ResponseStatusExceptionResolver
ResponseStatusExceptionResolver一般用於自己制定特定的響應狀態和錯誤資訊資訊顯示上面。一般我們在向外丟擲異常進行處理時,我們可以丟擲我們自己定義的異常類。
在處理器方法中丟擲了上述異常,若【ExceptionHandlerExceptionResolver】不解析我們的異常類,這是由於觸發的自定義異常帶有【@ResponseStatus註解】,因此,會被ResponseStatusExceptionResolver解析到。最後響應【@ResponseStatus註解】屬性值給客戶端。
<!-- 前臺連結 -->
<a href="TestResponseStatusExceptionResolver?i=10">Test ResponseStatusExceptionResolver </a> <br>
<!-- 後臺處理-->
// 測試ResponseStatusExceptionResolver 注意自定義異常類NameNOTINPasswordException
@RequestMapping("TestResponseStatusExceptionResolver")
public String TestResponseStatusExceptionResolver(@RequestParam("i") int i){
if(i==13 ){
throw new NameNOTINPasswordException(); // 更改瀏覽器引數i為13
// throw new RuntimeErrorException(null);
}
System.out.println("TestResponseStatusExceptionResolver ...");
return "success";
}
<!-- 自定義異常類 -->
// reason指定顯示資訊 value http的狀狀態碼 遮蔽此註解對比 拿到外部瀏覽器進行顯示
// 不要標在方法上面,儘管可以,會造成應該正常的顯示頁面也產生錯誤頁面上
@ResponseStatus(value=HttpStatus.FORBIDDEN,reason="使用者名稱與密碼不匹配")
public class NameNOTINPasswordException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
注意:ResponseStatusExceptionResolver只處理帶有@ResponseStatus註解的異常並將其對映為狀態碼。
第一次我們丟擲RuntimeErrorException,由於沒有註解@ResponseStatus,所以返回的為500
第二次我們修改我們丟擲的異常為自定義的異常,並且利用註解@ResponseStatus,所以返回我們特定的狀態碼和資訊。
DefaultHandlerExceptionResolver
DefaultHandlerExceptionResolver這個主要是處理Spring一些特定的異常並且把他們轉化為狀態碼。在原始碼中我們可以看到。
測試上述HttpRequestMethodNotSupportedException。他的意思是不支援我們提交的方法方式。
我們的超連結是GET方式,我們在後臺處理時,將方法指定為處理POST請求,這樣的話,就會交給我們的這個DefaultHandlerExceptionResolver進行處理。
<!-- 前臺連結 get 方式 -->
<a href="TestHttpRequestMethodNotSupportedException">Test HttpRequestMethodNotSupportedException </a> <br>
<!--後臺處理 post方式 -->
@RequestMapping(value="/TestHttpRequestMethodNotSupportedException",method=RequestMethod.POST)
public String TestHttpRequestMethodNotSupportedException(){
System.out.println("TestHttpRequestMethodNotSupportedException...");
return "success";
}
注意: 顯然,從這裡我們可以看出以前再出現錯誤頁面時,都是經過框架給我們處理過的。
SimpleMappingExceptionResolver
這個異常處理,主要是解決當發生我們所指定的異常情況時,跳轉到我們所指定的頁面。這個異常我們在Springmvc.xml檔案中進行註冊。
<!-- 前臺連結 -->
<a href="TestSimpleMappingExceptionResolver?i=10">Test SimpleMappingExceptionResolver </a> <br>
<!--後臺處理 -->
@RequestMapping("/TestSimpleMappingExceptionResolver")
public String TestSimpleMappingExceptionResolver(@RequestParam("i") Integer i){
int [] vals = new int[20];
System.out.println(vals[i]); // 傳遞引數 21 讓其發生 java.lang.ArrayIndexOutOfBoundsException
return"success";
}
<!--檔案配置 -->
<!-- 配置異常 SimpleMappingExceptionResolver
在這個頁面中,會將異常資訊儲存到RequestScope域中的exception屬性中,我們可以直接在頁面獲取
或者我們也可以配置這個exceptionAttribute屬性,將指定 RequestScope域中的特定屬性中
-->
<bean class=" org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings"> <!--name固定 -->
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
<!-- 配置指定的異常到指定的位置 -->
</props>
</property>
<property name="exceptionAttribute" value="ex"></property>
<!-- 配置 特定的域 ,在頁面應該是${ex}-->
</bean>
<!-- error 頁面 -->
<body>
Error Page <br>
預設儲存域: ${exception } <br>
haha <br>
配置修改儲存域:${ex}<br>
</body>
注意:配置指定的異常到指定的位置,依然經過我們的檢視解析器進行配置,所以要在指定位置編寫錯誤頁面。