# 문제상황 : MissingServletRequestParameterException
search 기능을 구현하는 와중이었다. query parameter로 search keyword를 받으려는 상황이다.
/search?keyword=검색할+키워드
controller 코드이다. @RequestParam으로 required=true인 String keyword를 받았다. /search 경로로 온 요청에서, 쿼리 파라미터로 keyword에 해당하는 값이 없으면 MissingServletRequestParameterException이 던져질 것이다.
@GetMapping("/search")
public String searchFarLog(Model model,
@RequestParam(value = "keyword",
required = true)
final String keyword,
PageRequest pageRequest) {
// controller logic ...
}
물론 실제 구현에선 defaultValue를 설정해서 keyword가 없을 경우를 대비하긴 할 것이다!
그래도 일단 테스트를 위해서 의도적으로 적절하지 않은 요청을 날려봤다.
error HTML 페이지가 나오길 기대했는데 갑자기 JSON 데이터가 뜨더니,

에러 로그에선 뜬금없이 /favicon.ico path로 request가 날아갔다고 한다.

스프링의 ExceptioinResolver가 뱉은 에러 로그를 보니 예상한대로 "MissingServletRequestParameterException"이 던져진 모양인데, 뜬금없이 /favicon.ico 요청은 왜 날아가는거니!

# 생각없이 쓴 extends ResponseEntityExceptionHandler
프로젝트에서 발생하는 예외 처리를 위해 @ControllerAdvice + @ExceptionHandler 방식으로 Exception handler method를 작성했다.
@ControllerAdvice
@Slf4j
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleRemainException(
Exception e,
HttpServletRequest request,
Model model) {
log.warn("!! Unknown error! URL: {}",
request.getRequestURI(),
log.warn("e.getMessage(): {}",e.getMessage());
model.addAttribute("error",
GreenFarmErrorResponse.builder()
.errorName("UNKNOWN ERROR!")
.detailMessage(e.getMessage())
.build());
return "error/error.html";
}
}
MyExceptionHandler 클래스는 ResponseEntityExceptionHandler를 상속받았는데, 이는 Spring MVC 환경에서 발생하는 에러들을 전역적으로 처리해주기 위해서다. @ControllerAdvice는 controller 컴포넌트에 적용되는 전역적 asepect이기 때문이다. ResponseEntityExceptionHandler를 상속함으로써 ExceptionHandler의 예외처리 method가 진행중인 프로젝트에 전역적으로 작용하게 될 것이다.
ResponseEntityExceptionHandler에서 처리하는 Exceptions들은 아래에 있다.

위 Exception들을 handleException() method가 처리하는 방식이다. handlerException()은 내부적으로 handleExceptionInternal()을 호출해 ResponseEntity 객체를 만들어내는데, 여기서 문제가 생겨버렸다!
// ResponseEntityExceptionHandler class 내부
@Nullable
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
// error response 구성 logic ...
return createResponseEntity(body, headers, statusCode, request);
}
protected ResponseEntity<Object> createResponseEntity(
@Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
return new ResponseEntity<>(body, headers, statusCode);
}
내가 진행중인 프로젝트는 JSON형식의 ResponseEntity가 아니라 ModelAndView를 제공해야 하지만, 클래스 이름 자체에도 내포되어있듯 ResponseEntityExceptionHandler는 ResponseEntity를 응답으로 내려주는 에러 핸들링 클래스였다. error 객체를 model에 붙여주는게 아니라 error 객체 자체를 내보내주는 handler를 사용했으면서 왜 error 페이지가 안나오는지 머리를 쥐어뜯은,, 바보같은 나의 모습.
패착이었다. 프로젝트 레퍼런스를 찾아가면서 ResponseEntityExceptionHandler를 extends하는 것이 좋다고 해서 일단 했는데 thymeleaf를 사용하는 현재 프로젝트와 맞지 않는 방법이었다.
+) Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MissingServletRequestParameterException]
어떻게든 custom exception handler를 사용해보겠다고 MyExceptionHandler 클래스에 핸들러 하나 박아넣고 `@ExceptionHandler(MissingServletRequestParameterExcepition.class)` 넣었다가 또한 이런 에러도 맞딱뜨린 적이 있다. 여전히 ResponseEntityExceptionHandler를 상속받은 채였다.
이전 상황을 이해했다면 당연한 결과다. ResponseEntityExceptionHandler의 handleException()이 이미 해당 예외를 처리하고 있는데 내가 핸들러 하나 더 붙이면 스프링 입장에선 뭘 따라야할지 모른다. 그러니 ambiguous 에러를 내뱉었을 뿐이다.
역시 이번에도 100% 내 책임, 내 탓 이었고~~.
# 해결
"extends ResponseEntityExceptionHandler"를 지워 나의 @ControllerAdvice가 해당 클래스를 상속받지 않도록 했다.
대신 handleException()이 처리하는 Exception들을 value로 넣어주었다.
@ExceptionHandler({MethodArgumentTypeMismatchException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
MissingServletRequestPartException.class,
ServletRequestBindingException.class,
MethodArgumentNotValidException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class,
ErrorResponseException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
BindException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleMvcException(
Exception e,
HttpServletRequest request,
Model model) {
// handle logic ...
}
# /favicon.ico 요청
Spring mvc는 reponse entity를 날릴 때 static 디렉토리의 favicon.ico을 default로 찾는다. 이는 /favicon.ico로 정적 자원 요청을 날리는 형식으로 진행된다. 그래서 ResponseEntity 구성 중 /favicon.ico 요청이 날아간 것이다.
진행중인 프로젝트에선 `@GetMapping("/{id}")`경로로 유저 상세 보기 페이지를 구현한 컨트롤러 method가 있다. favicon.ico라는 아이디를 가진 유저는 없기 때문에 해당 요청에 대한 Excetion이 발생했다.
구글링 끝에 favicon 요청 없애기를 진행했다. controller에 아래 핸들러를 추가하는 것이다.
@GetMapping("/favicon.ico")
@ResponseBody
public void returnNoFavicon() {}
사실 ResponseEntityExceptionHandler 상속을 하지 않으면 하지 않아도 될 일이긴 하나 이왕 favicon.ico 요청이 날아간다는 사실을 알았으니 공부도 할 겸 넣어줬다.
# 결론
무조건 reference가 많다고, "이렇게 하는 것이 일반적으로 좋다"라고 해서 프로젝트에 섣불리 적용시키지 말자. 구성 원리를 잘 따져보고 적용을 결정하자.
# 문제상황 : MissingServletRequestParameterException
search 기능을 구현하는 와중이었다. query parameter로 search keyword를 받으려는 상황이다.
/search?keyword=검색할+키워드
controller 코드이다. @RequestParam으로 required=true인 String keyword를 받았다. /search 경로로 온 요청에서, 쿼리 파라미터로 keyword에 해당하는 값이 없으면 MissingServletRequestParameterException이 던져질 것이다.
@GetMapping("/search")
public String searchFarLog(Model model,
@RequestParam(value = "keyword",
required = true)
final String keyword,
PageRequest pageRequest) {
// controller logic ...
}
물론 실제 구현에선 defaultValue를 설정해서 keyword가 없을 경우를 대비하긴 할 것이다!
그래도 일단 테스트를 위해서 의도적으로 적절하지 않은 요청을 날려봤다.
error HTML 페이지가 나오길 기대했는데 갑자기 JSON 데이터가 뜨더니,

에러 로그에선 뜬금없이 /favicon.ico path로 request가 날아갔다고 한다.

스프링의 ExceptioinResolver가 뱉은 에러 로그를 보니 예상한대로 "MissingServletRequestParameterException"이 던져진 모양인데, 뜬금없이 /favicon.ico 요청은 왜 날아가는거니!

# 생각없이 쓴 extends ResponseEntityExceptionHandler
프로젝트에서 발생하는 예외 처리를 위해 @ControllerAdvice + @ExceptionHandler 방식으로 Exception handler method를 작성했다.
@ControllerAdvice
@Slf4j
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleRemainException(
Exception e,
HttpServletRequest request,
Model model) {
log.warn("!! Unknown error! URL: {}",
request.getRequestURI(),
log.warn("e.getMessage(): {}",e.getMessage());
model.addAttribute("error",
GreenFarmErrorResponse.builder()
.errorName("UNKNOWN ERROR!")
.detailMessage(e.getMessage())
.build());
return "error/error.html";
}
}
MyExceptionHandler 클래스는 ResponseEntityExceptionHandler를 상속받았는데, 이는 Spring MVC 환경에서 발생하는 에러들을 전역적으로 처리해주기 위해서다. @ControllerAdvice는 controller 컴포넌트에 적용되는 전역적 asepect이기 때문이다. ResponseEntityExceptionHandler를 상속함으로써 ExceptionHandler의 예외처리 method가 진행중인 프로젝트에 전역적으로 작용하게 될 것이다.
ResponseEntityExceptionHandler에서 처리하는 Exceptions들은 아래에 있다.

위 Exception들을 handleException() method가 처리하는 방식이다. handlerException()은 내부적으로 handleExceptionInternal()을 호출해 ResponseEntity 객체를 만들어내는데, 여기서 문제가 생겨버렸다!
// ResponseEntityExceptionHandler class 내부
@Nullable
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
// error response 구성 logic ...
return createResponseEntity(body, headers, statusCode, request);
}
protected ResponseEntity<Object> createResponseEntity(
@Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
return new ResponseEntity<>(body, headers, statusCode);
}
내가 진행중인 프로젝트는 JSON형식의 ResponseEntity가 아니라 ModelAndView를 제공해야 하지만, 클래스 이름 자체에도 내포되어있듯 ResponseEntityExceptionHandler는 ResponseEntity를 응답으로 내려주는 에러 핸들링 클래스였다. error 객체를 model에 붙여주는게 아니라 error 객체 자체를 내보내주는 handler를 사용했으면서 왜 error 페이지가 안나오는지 머리를 쥐어뜯은,, 바보같은 나의 모습.
패착이었다. 프로젝트 레퍼런스를 찾아가면서 ResponseEntityExceptionHandler를 extends하는 것이 좋다고 해서 일단 했는데 thymeleaf를 사용하는 현재 프로젝트와 맞지 않는 방법이었다.
+) Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MissingServletRequestParameterException]
어떻게든 custom exception handler를 사용해보겠다고 MyExceptionHandler 클래스에 핸들러 하나 박아넣고 `@ExceptionHandler(MissingServletRequestParameterExcepition.class)` 넣었다가 또한 이런 에러도 맞딱뜨린 적이 있다. 여전히 ResponseEntityExceptionHandler를 상속받은 채였다.
이전 상황을 이해했다면 당연한 결과다. ResponseEntityExceptionHandler의 handleException()이 이미 해당 예외를 처리하고 있는데 내가 핸들러 하나 더 붙이면 스프링 입장에선 뭘 따라야할지 모른다. 그러니 ambiguous 에러를 내뱉었을 뿐이다.
역시 이번에도 100% 내 책임, 내 탓 이었고~~.
# 해결
"extends ResponseEntityExceptionHandler"를 지워 나의 @ControllerAdvice가 해당 클래스를 상속받지 않도록 했다.
대신 handleException()이 처리하는 Exception들을 value로 넣어주었다.
@ExceptionHandler({MethodArgumentTypeMismatchException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
MissingServletRequestPartException.class,
ServletRequestBindingException.class,
MethodArgumentNotValidException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class,
ErrorResponseException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
BindException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleMvcException(
Exception e,
HttpServletRequest request,
Model model) {
// handle logic ...
}
# /favicon.ico 요청
Spring mvc는 reponse entity를 날릴 때 static 디렉토리의 favicon.ico을 default로 찾는다. 이는 /favicon.ico로 정적 자원 요청을 날리는 형식으로 진행된다. 그래서 ResponseEntity 구성 중 /favicon.ico 요청이 날아간 것이다.
진행중인 프로젝트에선 `@GetMapping("/{id}")`경로로 유저 상세 보기 페이지를 구현한 컨트롤러 method가 있다. favicon.ico라는 아이디를 가진 유저는 없기 때문에 해당 요청에 대한 Excetion이 발생했다.
구글링 끝에 favicon 요청 없애기를 진행했다. controller에 아래 핸들러를 추가하는 것이다.
@GetMapping("/favicon.ico")
@ResponseBody
public void returnNoFavicon() {}
사실 ResponseEntityExceptionHandler 상속을 하지 않으면 하지 않아도 될 일이긴 하나 이왕 favicon.ico 요청이 날아간다는 사실을 알았으니 공부도 할 겸 넣어줬다.
# 결론
무조건 reference가 많다고, "이렇게 하는 것이 일반적으로 좋다"라고 해서 프로젝트에 섣불리 적용시키지 말자. 구성 원리를 잘 따져보고 적용을 결정하자.