2017年12月26日 星期二

Java Spring - Handling exceptions


  • Exception
    • 如果在request processing的時候出現exception,則outcome依然是Servlet response。
    • exception需要以某種方式被轉換成response
    • Spring提供多種方式將exception轉換為response
      • 特定的Spring exception將會自動mapping成指定的HTTP status code
      • Exception上可以加@ResponseStatus annotation,從而將其mapping為某一個HTTP status code
      • method上可以加上@ExceptionHandler,使其用來處理exception
  • 將exceptions mapping至HTTP status codes
    • default情況下,Spring會將自身的一些exception轉換成合適的status code
      • BindException 400 Bad Request
      • ConversionNotSupportedException 500 Internal Server Error
      • HttpMediaTypeNotAcceptableException 406 Not Acceptable
      • HttpMediaTypeNotSupportedException 415 Unsupported Media Type
      • HttpMessageNotReadableException 400 Bad Request
      • HttpMessageNotWritableException 500 Internal Server Error
      • HttpRequestMethodNotSupportedException 405 Method Not Allowed
      • MethodArgumentNotValidException 400 Bad Request
      • MissingServletRequestParameterException 400 Bad Request
      • MissingServletRequestPartException 400 Bad Request
      • NoSuchRequestHandlingMethodException 404 Not Found
      • TypeMismatchException 400 Bad Request
    • 例如,如果DispatcherServlet無法找到適合處理request的controller method,則會拋出NoSuchRequestHandlingMethodException,最終結果就是產生404 status code response
    • Spring提供一個機制,能夠透過@ResponseStatus annotation將exception mapping為HTTP status code
    • 範例: 
    • @RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
      public String spittle(@PathVariable("spittleId") long spittleId, Model model) {
        Spittle spittle = spittleRepository.findOne(spittleId);
        if (spittle == null) {
          throw new SpittleNotFoundException();
        }
        model.addAttribute(spittle);
        return "spittle";
      }
      
    • 如果findOne返回null,就會throw SpittleNotFoundException exception,且response code為500。實際上,如果出現任何沒有mapping的exception,response都會是500 status code。
    • mapping
      • SpittleNotFoundException是一種資源沒找到的異常,其實HTTP404是比較精確的response status code。所以,要使用@ResponseStatus將SpittleNotFoundException mapping至HTTP404
      package spittr.web;
      import org.springframework.http.HttpStatus;
      import org.springframework.web.bind.annotation.ResponseStatus;
      @ResponseStatus(value=HttpStatus.NOT_FOUND,
                      reason="Spittle Not Found") //將exception mapping至404
      public class SpittleNotFoundException extends RuntimeException {
      }
      
  • Exception handling method
    • 如果想要response不僅包含status code,還要包含產生的錯誤,此時就不能將exception視為HTTP error,而是要按照處理request的方式來處理exception
    • 假設使用者新增的Spittle和已存在的Spittle相同,SpittleRepository的save()將會拋出DuplicateSpittleException
    • 這表示SpittleController的saveSpittle() method需要處理這個exception
    • @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() method只關注在正確的path,而讓其他method處理exception,也許能夠簡單一點
      • 先將saveSpittle() method的exception handler移除(易理解及測試,因為只有一個path)
      • @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添加一新的method,會處理DuplicateSpittleException的情況,這和處理request是一致的
      • @ExceptionHandler(DuplicateSpittleException.class)
        public String handleDuplicateSpittle() {
          return "error/duplicate";
        }
        
    • 對於@ExceptionHandler來說,他能夠處理同一個controller所有method拋出的DuplicateSpittleException exception,而不需要在每一個可能拋出DuplicateSpittleException的method中增加handle code
  • Advising controllers
    • 如果controller class特定的aspect能夠運用到整個application的所有controller,這會很方便
    • 如果多個controller都會拋出某個特定的exception,可能會發現要在所有controller重複相同的@ExceptionHandler
    • 為了避免重複,可以create一個base controller,所有controller都extends這個class,從而extends common @ExceptionHandler
    • Spring3.2 introduce一個新的解決方案: controller advice,是任意帶有@ControllerAdvice的class,這個class會包含一個或多個下面annotation的method,且會運用到整個application中的所有controller中帶有@RequestMapping的method上
      • @ExceptionHandler
      • @InitBinder
      • @ModelAttribute
    • @ControllerAdvice本身已經使用了@Component因此@ControllerAdvice所標註的class將會自動被ComponentScanner掃描到,就像帶有@Component一樣
    • @ControllerAdvice主要用法:
      • 將所有的@ExceptionHandler method蒐集到一個class中,如此一來所有的exception就能在同一個地方進行一致的處理
      package spittr.web;
      
      import org.springframework.web.bind.annotation.ControllerAdvice;
      import org.springframework.web.bind.annotation.ExceptionHandler;
      
      @ControllerAdvice
      public class AppWideExceptionHandler {
          @ExceptionHandler(DuplicateSpittleException.class)
          public String duplicateSpittleHandler() {
              return "error/duplicate";
          }
      }
      
      • 如此一來,任意的controller method如果拋出了DuplicateSpittleException,都會使用duplicateSpittleHandler()來處理Exception

沒有留言:

張貼留言