1. 程式人生 > >SpringMvc in Action——處理異常以及跨重定向

SpringMvc in Action——處理異常以及跨重定向

處理異常

當處理請求時,丟擲異常應該怎麼處理呢?如果發生了這種情況,客戶端又怎麼響應呢? Spring提供了多種方式將異常轉換為響應:

  • 特定的異常會自動對映為指定的HTTP狀態碼
  • 異常上可以新增@ResponseStatus註解,從而將其對映為某一個HTTP狀態碼
  • 在方法上可以新增@RxceptionHandler註解,使用其來處理異常。

將異常對映為HTTP狀態碼 一些異常會預設對映為HTTP狀態碼 在這裡插入圖片描述 表中的異常一般會由Spring自身丟擲,作為DispatcherServlet處理過程中或執行校驗時出現問題的結果。 儘管這些內建的對映是很有用的,但是對於應用所丟擲的異常它們就無能為力了,幸好,Spring提供了一種機制,通過@ResponseStatus註解將異常對映為HTTP狀態碼。 我們在SpittleController中如下的請求處理方法:

  @RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
    public String spittle(
            @PathVariable(value = "spittleId") long spittleId,Model model) {
        Spittle spittle=spittleRepository.findOne(spittleId);
        if (spittle==null){
         throw new SpittleNotFoundException
(); } model.addAttribute( spittle); return "spittle"; }

如果呼叫spittle()方法處理請求,但給定的ID獲取到的結果為空,那麼會丟擲異常。這種異常是一種請求資源沒有找到的場景,應該返回404狀態碼。然而,預設會返回500狀態碼。所以,我們需要使用註解將其對映為404狀態碼。

package spittr.web;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.
ResponseStatus; @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Spittle Not Found") public class SpittleNotFoundException extends RuntimeException { }

結果: 在這裡插入圖片描述 編寫異常處理的方法 很多情況下,將異常對映為狀態碼是很簡單的方案,並且就功能來說也足夠了。但是如果我們想要在響應中,不僅要包括狀態碼,還要包含所產生的錯誤。 那麼,我們就不能將異常視為HTTP錯誤了,而是要按照處理請求的方式來處理異常了。

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

這個方法沒有任何問題,他有兩個路徑,每個路徑有不同的輸出。但是,如果能讓saveSpittle()只關注正確的路徑,而讓其他方法處理異常的話,那麼它就能簡單一些。 首先,我們修改saveSpittle()方法: 刪除try catch

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

現在,我們為SpittleController新增一個新方法,它會處理丟擲異常的情況:

 @ExceptionHandler(DuplicateSpittleException.class)
    public String handleDuplicateSpittle(){
        return "error/duplicate";
    }

所以它能處理所有丟擲這個異常的情況。

跨重定向請求傳遞資料

在處理完POST請求後,通常來講一個最佳實踐就是執行一下重定向:防止使用者重新整理或回退後,客戶端重新執行危險的POST請求。 redirect:讓重定向變得很簡單,你可能會想Spring很難再讓重定向變得更簡單了,但是,Spring為重定向提供了一些其他的輔助功能。 一般而言,使用了重定向後,原始的請求就結束了,原始請求所帶的模型資料也就隨著請求一起消亡了。 在這裡插入圖片描述 但是,有時候我們希望既能重定向,又能傳遞資料。我們有一些其他方案,能夠從發起重定向的方法傳遞資料給處理重定向方法中:

  • 使用URL模版以路徑變數和/或查詢引數的形式傳遞資料
  • 通過flash屬性發送資料

** 通過URL模版進行重定向** 通過路徑變數和查詢引數進行重定向很簡單,比如username的值是直接連在重定向的URL的String上面的:return "redirect:/spitter/{username}"。這能夠正常執行。但當構建URL或SQL查詢語句的時候,使用String連線還是很危險的。 除了連線String的方式來構建重定向URL,Spring還提供了模版的方式來定義重定向URL。例如: 修改前:

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String processRegistration(@Valid Spitter spitter, Errors errors){
        if (errors.hasErrors())
            return "registerForm";//重新來
        spitterRepository.save(spitter);//儲存註冊資訊
        return "redirect:/spitter/"+spitter.getUsername();
    }

修改後:

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String processRegistration(@Valid Spitter spitter, Errors errors,Model model){
        if (errors.hasErrors())
            return "registerForm";//重新來
        spitterRepository.save(spitter);//儲存註冊資訊
        model.addAttribute("username",spitter.getUsername());
        return "redirect:/spitter/{username}";
    }

由於使用redirect:/spitter/{username}替換了return "redirect:/spitter/"+spitter.getUsername();。現在username作為佔位符填充到了URL模版中,而不是直接連線到重定向的URL中,所以username中所有的不安全字元都會進行轉移,這樣會更加安全。 除此之外,模型中所有的其他的原始型別值都可以新增到URL中,作為查詢引數。假設除了Username外,模型中還有包含建立Spitter物件的id屬性,那麼,processRegistration()方法可以修改為:

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String processRegistration(@Valid Spitter spitter, Errors errors,Model model){
        if (errors.hasErrors())
            return "registerForm";//重新來
        spitterRepository.save(spitter);//儲存註冊資訊
        model.addAttribute("username",spitter.getUsername());
        model.addAttribute("spitterId",spitter.getId());
        return "redirect:/spitter/{username}";
    }

雖然,我們return返回沒有太大的變化,但是因為Model中的spitterId屬性沒有匹配重定向URL的任何佔位符,所以它會以查詢引數的形式附加到重定向的URL上:比如,username的值是habuma,並且spitterId的值是42,那麼url將會是"/spitter/habuma?spitterId=42"

使用flash屬性 假設我們不想在重定向中傳送username或者ID了,而我們想傳送一個實際的Spitter物件。如果我們只發送ID的話,那麼處理重定向的方法還需要在資料庫裡查詢一遍,事實上我們手裡本來就有這個物件了,為什麼不直接拿到呢? 因為這種複雜物件不能使用路徑變數或查詢引數那麼容易,因此,我們需要將Spitter物件放在一個位置,以便它能夠在重定向的過程中存活下來。 實際上,Spring認為將跨重定向存活的資料放到會話中是一個很不錯的方式。但是,Spring認為我們並不需要管理這些資料,相反它停工了將資料傳送為flash attribute的功能,按照定義flash屬性會一直攜帶這些資料直到下一次請求,然後才會小時。

Spring提供了通過RedirectAttributes設定flash屬性的方法,這是Model的一個子介面,RedirectAttributes提供了Model的所有功能,除此之外,還有方法是用來設定flash屬性的

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String processRegistration(@Valid Spitter spitter, Errors errors,RedirectAttributes model){
        if (errors.hasErrors())
            return "registerForm";//重新來
        spitterRepository.save(spitter);//儲存註冊資訊
        model.addAttribute("username",spitter.getUsername());
        model.addFlashAttribute("spitter",spitter);
        return "redirect:/spitter/{username}";
    }

這樣我們就達成了目的: 在這裡插入圖片描述 為了再得到flash屬性,我們首先從模型中檢查Spitter物件是否存在,如果不存在,再在資料庫裡查詢:

 @RequestMapping(value="/{username}", method=RequestMethod.GET)
    public String showSpitterProfile(
            @PathVariable String username, Model model) {
        if (!model.containsAttribute("spitter")) {
            model.addAttribute(
                    spitterRepository.findByUsername(username));
        }
        return "profile";
    }