오류 페이지는 단순히 고객에게 오류 화면을 보여주고 끝이지만, API는 각 오류 상황에 맞는 오류 응답 스펙을 정하고, JSON으로 데이터를 내려주어야 한다.
=> 우리가 원하는건 정상이든,오류든 JSON형식으로 받아오는것!
문제를 해결 하려면 오류 페이지 컨트롤러도 JSON 응답을 할 수 있도록 수정해야 한다
produes = MediaType.APLICATION_JSON_VALUE 를 설정해 이 컨트룰러가 JSON형식으로 오류를 리턴하도록 한다.(Http header ACCEPT 가 JOSN) 이니까
Accept = json이면 이 컨트룰러가 우선권을 같는다 같은 url이 있어도
map 에는 각 오류내용을 담아 ResponseEntity로 반환
사실 지금까지 해왔던 과정은 이미 BasicErrorController에 정의 되어있다. 자세히 보면 코드가 비슷하다.
properties를 통해 더 자세한 오류내용을 출력할 수 있지만 보안상 위험하다.
단순히 HTML 오류 페이지만 출력할거면 BasicErrorController이 좋지만 API 예외는 각각 상황에 따라 다르므로 매우 세밀한 작업이 필요하다.
- BasicErrorController-
- error-page(HTML) 자동생성
- JSON(ResponseEntity)으로 반환하도록 자동설정
- WAS까지 전달되는 500에러 예외를 4xx 에러로 처리하고 싶을때, 다른 메세지, 다른 형식으로 처리하고 싶을때사용(=예외처리 관리)
ExceptionResolver 적용전은 preHandle -> 어댑터-> 예외발생-> 디스패쳐서블릿 -> postHandler 실행 x -> WAS 예외 반환
ExceptionResolver 을 적용하면 디스패쳐 서블릿으로 예외를 받은뒤 ExceptionResolver을 호출해 예외를 처리할 수 있는지 확인을 한다. 예외를 처리하면 (postHandler 실행 x)
이 예외를 처리하는 UserHandlerExceptionResolver 생성
response.sedError 로 500에러 -> 400에러 변환 , 정상흐름 반환
이후 WAS는 서블릿 오류페이지를 찾아서 내부 호출,예를 들어서 스프링부트 가 기본 으로 설정한 /error 가 호출됨
- 빈 ModelAndView 반환: Exception 을 처리해서 정상 흐름 처럼 변경하는 것이 목적(뷰를 렌더링하지 않고 정상흐름 반환)
- null 반환 : 다음 ExceptionResolver 실행, 없으면 그냥 예외발생
500 에러가 400으로 반환되는 것을 볼 수 있다.
하지만 예외가 발생해 WAS까지 올라가고 다시 오류페이지를 찾아 /error 호출 하는 과정은 복잡하면서도 너무나 반복적이다. ExceptionResolver 을 통해 깔끔하게 해결 가능
그것을 해결 하는 ExceptionResolver 생성
HttpHeader Accpet 가 JSON 이면 JSON으로 오류를 내려주고 그외의 경우에는 error/500에 있는 HTML을 보여준다
-> 서블릿 컨테이너(WAS) 까지 예외가 올라가지 않고 여기서 다 처리 됀다.
1.ExceptionHandlerExceptionResolver
2.ResponseStatusExceptionResolver
3.DefaultHandlerExceptionResolver
= 예외에 따라서 HTTP 상태 코드를 지정해주는 역할
1. @ResponseStatus()
이 애노테이션을 내부적으로 보면 직접 구현했던HanlderExceptionResolver와 거의 흡사하다
결국에는 내부에서 response.sendError() 형식 -> WAS /error 오류페이지 내부 호출
reason -> MessageSource 에서 호출기능
TypeMismatchException 같은 클라 잘못을 오는 예외를 자동으로 500
->400에러로 바꿔줌
이것도 역시 내부적으로 response.sendError()방법을 사용 -> WAS 에서 /error 요청
- 위에 배운 예제들 HandlerExceptionResolver의 단점은 API 오류 처리시 사용하지 않는 ModelAndView를 반환 한다는점
- HttpServletResponse 에 직접 응답 데이터를 넣어주는 불편함
- 특정 컨트롤러에서만 발생하는 예외를 별도로 처리하기 어렵다.
@ExceptionHandler는 해당 컨트룰러에만 영향을 준다.
ExceptionHandlerResolver 중에서 우선순위가 1임으로 예외가 ExceptionResolver로 들어올때 항상 이것부터 먼저 체크함
- @RestController 이므로 illegalExHandle() 에도 @ResponseBody 가 적용된다. 따라서 HTTP 컨버터가 사용되고, 응답이 다음과 같은 JSON으로 반환 + @ResponseStatus 가 상태코드 400으로 변경
@ExceptionHandler에 예외를 생략할시에 파라미터 예외가 지정됀다.
ResponseEntity 를 사용해서 HTTP 메시지 바디에 직접 응답한다. 물론 HTTP 컨버터가 사용된다.ResponseEntity 를 사용하면 HTTP 응답 코드를 프로그래밍해서 동적으로 변경할 수 있다. 앞서 살펴본 @ResponseStatus 는 애노테이션이므로 HTTP 응답 코드를 동적으로 변경할 수 없다.
@ExceptionHanlder러만 사용 했을 경우 해당 컨트룰러에서 발생하는 예외만 잡는다. 전체 컨트룰러에서 공통으로 잡을 수 있게 만들어 보자
@ControllerAdvice 는 대상으로 지정한 여러 컨트롤러에 @ExceptionHandler , @InitBinder 기능을 부여해주는 역할을 한다.
@ControllerAdvice 에 대상을 지정하지 않으면 모든 컨트롤러에 적용된다. (글로벌 적용)
@RestControllerAdvice 는 @ControllerAdvice 와 같고, @ResponseBody 가 추가되어 있다. @Controller , @RestController 의 차이와 같다.