天天看點

第7章—SpringMVC進階技術—處理異常處理異常

處理異常

不管發生什麼事情,不管是好的還是壞的,Servlet請求的輸出都是一個Servlet響應。如果在請求處理的時候,出現了異常,那它的輸出依然會是Servlet響應。異常必須要以某種方式轉換為響應。

Spring提供了多種方式将異常轉換為響應:

特定的Spring異常将會自動映射為指定的HTTP狀态碼;

  異常上可以添加@ResponseStatus注解,進而将其映射為某一個HTTP狀态碼;

在方法上可以添加@ExceptionHandler注解,使其用來處理異常。

3.1将異常映射為HTTP狀态碼

在預設情況下,Spring會将自身的一些異常自動轉換為合适的狀态碼。

Spring的一些異常會預設映射為HTTP狀态碼

Spring異常 HTTP狀态碼
BindException 400 - Bad Request
ConversionNotSupportedException 500 - Internal Server Error
HttpMediaTypeNotAcceptableException 406 - Not Acceptable
HttpMediaTypeNotSupportedException 415 - Unsupported Media Type
HttpMessageNotReadableException
MissingServletRequestParameterException
MissingServletRequestPartException
NoSuchRequestHandlingMethodException 404 - Not Found
TypeMismatchException
HttpMessageNotWritableException
HttpRequestMethodNotSupportedException 405 - Method Not Allowed

異常一般會由Spring自身抛出,作為DispatcherServlet處理過程中或執行校驗時出現問題的結果。

如果DispatcherServlet無法找到适合處理請求的控制器方法,那麼将會抛出NoSuchRequestHandlingMethodException異常,最終的結果就是産生404狀态碼的響應(Not Found)。

3.2@ResponseStatus注解

Spring提供了一種機制,能夠通過@ResponseStatus注解将異常映射為HTTP狀态碼。

public String spittle(@PathVariable("spittleId") long spittledId ,Model  model){
         Spittle spittle = sipttleRepository.findOne(spittleId);
         if(spittle == null){
             throw new SpittleNotFoundException();
         }
         model.addAttribute(spittle);
         return "spittle";
     }
           

通過ID檢索Spittle對象。如果findOne()方法能夠傳回Spittle對象的話,那麼會将Spittle放到模型中,然後名為spittle的視圖會負責将其渲染到響應之中。但是如果findOne()方法傳回null的話,那麼将會抛出SpittleNotFoundException異常。

public class SpittleNotFoundException extends RuntimeException {

}
           

如果調用spittle()方法來處理請求,并且給定ID擷取到的結果為空,那麼SpittleNotFoundException(預設)将會産生500狀态碼(Internal Server Error)的響應。實際上,如果出現任何沒有映射的異常,響應都會帶有500狀态碼,故傳回的不精确,可以修改。

使用@ResponseStatus注解将SpittleNotFoundException映射為HTTP狀态碼404。

@ResponseStatus(value=HttpStatus.NOT_FOUND,reason="Spittle Not Found")
public class SpittleNotFoundException extends RuntimeException {

}
           

在引入@ResponseStatus注解之後,如果控制器方法抛出SpittleNotFoundException異常的話,響應将會具有404狀态碼,這是因為Spittle Not Found。

3.3異常處理的方法

若在響應中不僅要包括狀态碼,還要包含所産生的錯誤,此時的話,就不能将異常視為HTTP錯誤了,而是要按照處理請求的方式來處理異常了。

假設使用者試圖建立的Spittle與已建立的Spittle文本完全相同,那麼SpittleRepository的save()方法将會抛出DuplicateSpittle Exception異常。這意味着SpittleController的saveSpittle()方法可能需要處理這個異常。

程式的處理:

@RequestMapping(method=RequestMethod.POST)
     public String saveSpittle(SpittleForm form,Model model){
         try{
             spittleRepository.save(new Spittle(null,form,getMessage(),new Date()));
             return "redirect:/spittles";
         }catch(DuplicateSpittleException e){
             return "err/duplicate";
         }
     }
           

如果能讓saveSpittle()方法隻關注正确的路徑,而讓其他方法處理異常的話,那麼它就能簡單一些。

修改:

@RequestMapping(method=RequestMethod.POST)
     public String saveSpittle(SpittleForm form,Model model){
             spittleRepository.save(new Spittle(null,form,getMessage(),new Date()));
             return "redirect:/spittles";
     }
     
     @ExceptionHandler(DuplicateSpittleException.class)
     public String handlerDuplicateSpittle(){
         return "err/duplicate";
     }
           

handleDuplicateSpittle()方法上添加了@ExceptionHandler注解,當抛出DuplicateSpittleException異常的時候,将會委托該方法來處理。它傳回的是一個String,這與處理請求的方法是一緻的,指定了要渲染的邏輯視圖名,它能夠告訴使用者他們正在試圖建立一條重複的條目。

對于@ExceptionHandler注解标注的方法來說,比較有意思的一點在于它能處理同一個控制器中所有處理器方法所抛出的異常。是以,盡管我們從saveSpittle()中抽取代碼建立了handleDuplicateSpittle()方法,但是它能夠處理SpittleController中所有方法所抛出的DuplicateSpittleException異常。我們不用在每一個可能抛出DuplicateSpittleException的方法中添加異常處理代碼,這一個方法就涵蓋了所有的功能。

3.4為控制器添加通知

如果多個控制器類中都會抛出某個特定的異常,那麼你可能會發現要在所有的控制器方法中重複相同的@ExceptionHandler方法。或者,為了避免重複,我們會建立一個基礎的控制器類,所有控制器類要擴充這個類,進而繼承通用的@ExceptionHandler方法。

但是:Spring 3.2為這類問題引入了一個新的解決方案:控制器通知。控制器通知(controller advice)是任意帶有@ControllerAdvice注解的類,這個類會包含一個或多個如下類型的方法:

@ExceptionHandler注解标注的方法;

    @InitBinder注解标注的方法;

    @ModelAttribute注解标注的方法。

在帶有@ControllerAdvice注解的類中,以上所述的這些方法會運用到整個應用程式所有控制器中帶有@RequestMapping注解的方法上。

@ControllerAdvice注解本身已經使用了@Component,是以@ControllerAdvice注解所标注的類将會自動被元件掃描擷取到,就像帶有@Component注解的類一樣。

@ControllerAdvice最為實用的一個場景就是将所有的@ExceptionHandler方法收集到一個類中,這樣所有控制器的異常就能在一個地方進行一緻的處理。

第7章—SpringMVC進階技術—處理異常處理異常

img

如果任意的控制器方法抛出了DuplicateSpittleException,不管這個方法位于哪個控制器中,都會調用這個duplicateSpittleHandler()方法來處理異常。

四:跨重定向請求傳遞資料

當控制器方法傳回的String值以“redirect:”開頭的話,那麼這個String不是用來查找視圖的,而是用來指導浏覽器進行重定向的路徑。

具體來講,正在發起重定向功能的方法該如何發送資料給重定向的目标方法呢?一般來講,當一個處理器方法完成之後,該方法所指定的模型資料将會複制到請求中,并作為請求中的屬性,請求會轉發(forward)到視圖上進行渲染。同一個請求,是以在轉發的過程中,請求屬性能夠得以儲存。

當控制器的結果是重定向的話,原始的請求就結束了,并且會發起一個新的GET請求。原始請求中所帶有的模型資料也就随着請求一起消亡了。在新的請求屬性中,沒有任何的模型資料,這個請求必須要自己計算資料。

第7章—SpringMVC進階技術—處理異常處理異常

有一些其他方案,能夠從發起重定向的方法傳遞資料給處理重定向方法中:

使用URL模闆以路徑變量和/或查詢參數的形式傳遞資料;

    通過flash屬性發送資料。

4.1通過URL模闆進行重定向

通過路徑變量和查詢參數傳遞資料看起來非常簡單。以路徑變量的形式傳遞了新建立Spitter的username。但是按照現在的寫法,username的值是直接連接配接到重定向String上的。這能夠正常運作,但是還遠遠不能說沒有問題。當建構URL或SQL查詢語句的時候,使用String連接配接是很危險的。

Spring還提供了使用模闆的方式來定義重定向URL。

第7章—SpringMVC進階技術—處理異常處理異常

username作為占位符填充到了URL模闆中,而不是直接連接配接到重定向String中,是以username中所有的不安全字元都會進行轉義。這樣會更加安全,這裡允許使用者輸入任何想要的内容作為username,并會将其附加到路徑上。

模型中所有其他的原始類型值都可以添加到URL中作為查詢參數。作為樣例,假設除了username以外,模型中還要包含新建立Spitter對象的id屬性,那processRegistration()方法可以改寫為如下的形式:

第7章—SpringMVC進階技術—處理異常處理異常

所傳回的重定向String并沒有太大的變化。但是,因為模型中的spitterId屬性沒有比對重定向URL中的任何占位符,是以它會自動以查詢參數的形式附加到重定向URL上。

如果username屬性的值是habuma并且spitterId屬性的值是42,那麼結果得到的重定向URL路徑将會是“/spitter/habuma?spitterId=42”。

通過路徑變量和查詢參數的形式跨重定向傳遞資料是很簡單直接的方式,但它也有一定的限制。它隻能用來發送簡單的值,如String和數字的值。

4.2使用flash屬性

Spitter對象要比String和int更為複雜。是以,我們不能像路徑變量或查詢參數那麼容易地發送Spitter對象。它隻能設定為模型中的屬性。

模型資料最終是以請求參數的形式複制到請求中的,當重定向發生的時候,這些資料就會丢失。是以,我們需要将Spitter對象放到一個位置,使其能夠在重定向的過程中存活下來。有個方案是将Spitter放到會話中。會話能夠長期存在,并且能夠跨多個請求。是以我們可以在重定向發生之前将Spitter放到會話中,并在重定向後,從會話中将其取出。當然,我們還要負責在重定向後在會話中将其清理掉。

Spring認為我們并不需要管理這些資料,相反,Spring提供了将資料發送為flash屬性(flash attribute)的功能。按照定義,flash屬性會一直攜帶這些資料直到下一次請求,然後才會消失。

Spring提供了通過RedirectAttributes設定flash屬性的方法,這是Spring 3.1引入的Model的一個子接口。RedirectAttributes提供了Model的所有功能。

具體來講,RedirectAttributes提供了一組addFlashAttribute()方法來添加flash屬性。重新看一下processRegistration()方法

第7章—SpringMVC進階技術—處理異常處理異常

調用了addFlashAttribute()方法,并将spitter作為key,Spitter對象作為值。另外,我們還可以不設定key參數,讓key根據值的類型自行推斷得出:因為我們傳遞了一個Spitter對象給addFlashAttribute()方法,是以推斷得到的key将會是spitter

在重定向執行之前,所有的flash屬性都會複制到會話中。在重定向後,存在會話中的flash屬性會被取出,并從會話轉移到模型之中。

第7章—SpringMVC進階技術—處理異常處理異常
第7章—SpringMVC進階技術—處理異常處理異常

showSpitterProfile()方法所做的第一件事就是檢查是否存有key為spitter的model屬性。如果模型中包含spitter屬性,那就什麼都不用做了。這裡面包含的Spitter對象将會傳遞到視圖中進行渲染。但是如果模型中不包含spitter屬性的話,那麼showSpitterProfile()将會從Repository中查找Spitter,并将其存放到模型中。

如下在controller中寫如下:

@RequestMapping("test")
    public String testDemo() {


        return "test";

    }

@RequestMapping("reset")
    public  String reset(Model model){

        model.addAttribute("test","test");

        model.addAttribute("username","張三");

        return "redirect:/stu/{test}";

}
           
第7章—SpringMVC進階技術—處理異常處理異常

image