Spring @PathVariable에 대한 에러 처리는 어떻게 할까

@RequestMapping(method = RequestMethod.GET, value = "/shuttle/{number}")
public ResponseEntity shuttle(@PathVariable int number) {
    //
}

뭐 이런 API를 만든다고 했을 때 스프링으로 number에 해당하는 부분을 손쉽게 타입 변환까지 거쳐서 int로 받아 사용할 수 있다. 그런데, 만약 입력한 값이 int 타입이 아니라면 무슨 일이 생길까. 스프링은 무슨 값이 오던 int 타입으로 타입 변환을 시도할 것이고 에러가 발생한다. 그리고 응답은 400으로 나가며 본문의 메시지는 비어있다. 깔끔한 API라고 보긴 어렵다. 뭔가 에러 상황에서는 왜 그런 에러가 발생했는지 클라이언트가 알기 쉽도록 안내해 주는게 좋은 API일 것이다. 그렇다면 무슨 에러가 발생하며 어떻게 처리할 수 있나 알아보자.

@Test
public void testMethodArgumentTypeMismatchException() throws Exception {
    mockMvc.perform(get("/shuttle/aaa"))
            .andDo(print())
            .andExpect(status().isBadRequest());
}

테스트 코드다. 실행하면 콘솔에서 이런 내용을 확인할 수 있다.

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /shuttle/aaa
       Parameters = {}
          Headers = {}

Handler:
             Type = me.whiteship.web.ShuttleController
           Method = public org.springframework.http.ResponseEntity me.whiteship.web.ShuttleController.shuttle(int)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = org.springframework.web.method.annotation.MethodArgumentTypeMismatchException

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 400
    Error message = null
          Headers = {}
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

뭐 이미 답이 다 이 안에 있다. 우선 응답이 썩 사용자에 친화적이지 않다는 것을 알 수 있고, 어떤 에러가 발생했는지도 알 수 있다. 그걸 잡아서 처리하면 되겠다.

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
    return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}

이런식으로 MATME를 잡아서 처리하는 핸들러 코드를 추가하면 원하는 메시지를 응답으로 담을 수 있다. 그냥 getMessage()를 꺼내서 보내는 것보다는 좀 더 손을 대야겠지만 그건 뭐 취향으로.. 그리고 이걸 해보며 드는 생각은 이렇다.

  • 이 예외 처리기는 공용으로 사용하기엔 뭔가 너무 일반화되어 있다는 느낌이 든다. 아무래도 컨트롤러 안에 두는게 좋겠다.
  • 저 예외 클래스가 제공하는 메서드중 getName()과 getParameter()를 잘 사용한다면 어쩌면 @ControllerAdvice에 둘 수도 있을지 모르겠다.
  • 타입이 String인 변수에 @PathVariable을 사용했다면 이 에러를 잡을 일이 없을 것이다.

[Thymeleaf] 스프링에 타임리프 연동하기

매우 쉽습니다. ViewResolver만 바꿔주면 됩니다. 기존에 JSP뷰를 사용하고 있었다면 InternelResourceVewResolver를 사용해서 JSTL뷰를 설정한 다음 prefix와 suffix 정도를 설정해서 사용하고 있으실텐데요.

ThymeleafViewResolver로 바꿔주시면 됩니다. 그리고 ThymeleafViewResolver가 필요로하는 빈도 몇개 등록해야되구요. 아.. 그전에 메이븐 의존성부터 추가하셔야겠군요. 저는 스프링 자바 설정을 사용했는데 코드 보시면 XML 설정으로도 쉽게 설정하실 수 있으실 겁니다.

좋아~ 이제야 드뎌 서버 띄우지 않고 html만 작업하면서 뷰를 만든다음 서버 띄우고나서도 계속 사용할 수도 있겠군요.

[스프링 3.2] 비동기 서블릿 지원 맛보기

https://github.com/SpringSource/spring-framework/pull/69

https://github.com/rstoyanchev/spring-mvc-async-sample

흠냐.. 맨 위에껀 스프링 3.2에 작업중인 코딩 내역이고 아래 링크는 실제로 어떻게 사용할 수 있는 보여주는 예제 프로젝트입니다. 로센이 작업했네요. 멋쟁이.

예제 프로젝트를 받아서 돌려봤는데 매우 깔끔하게 동작합니다. 뷰를 Thymeleaf라는 것을 사용했는데… 흠.. JS랑 맛물리는 뭔가가 있는것 같기도 하고 첨보는거라 뷰가 어떻게 돌아가는지 몰겠네요. 어쨌든 HTML 파일 하나를 사용하고 있습니다.

자바스립트는 두개를 사용하는데 하나는 제이쿼리, 하나는 넉아웃.js 그리고 제이쿼리 플러그인으로 하나더 jquery-stream.js이라고 있기는 한데… 소스코드를 봐도.. 크롬 개발자 도구로 봐도.. 사용하진 않는것 같습니다.

스프링 설정은 자바 설정으로 만들었는데 굳이 설정 파일은 루트와 웹을 나눴으면서 애플리케이션 컨텍스트는 나눠서 등록하지 않는 모습이 이번에도 보이는데… 왜 CCL을 사용하지 않고 DS만 사용할꺼면서 빈 설정은 나누는지 잘 모르겠네요. 그럴꺼면 걍 웹용 설정 파일만 만들어도 될텐데 말이죠. web.xml도 없고 톰캣7 기능을 사용해서 자바 웹 설정으로 web.xml을 대신합니다.

서론이 길었군요. 스프링 3.2부터는 @RequestMapping이 붙은 메서드에서 리턴할 수 있는 타입이 두개가 더 생깁니다. 비동기 서블릿과 관련된 리터타입이죠.

  • 스프링에서 만들어 둔 DeferredResult
  • 자바 컨커런시에 있는 Callable

지금 데모용 프로젝트에서는 DeferredResult를 사용하고 있습니다. DR을 사용하는 부분만 보겠습니다.