스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 섹션9. API 예외 처리
CS/김영한 스프링 강의

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 섹션9. API 예외 처리

아까는 웹에 대한 거였지만 이젠 API서버에서 예외 처리에 대해 알아본다.

풀어놨던 설정을 다시 활성화 한다.

 

시험용으로 간단하게 만든다.

 

postman으로 시험해본다. 에러가 떴을 때 지난 설정이 그대로 있으므로, 에러 형태에 따라 설정한 곳의 url를 반환해서 결국은 웹 뷰 html를 반환할 것이다.

 

 

같은 에러 반환용 url이지만, 클라이언트가 헤더에서 건네는 받을 수 있는 요청을 더 구체적으로 정해줘서 해준다. json으로 반환할 거니 Map<String, Object)를 반환하게 하고 이 자체를 반환할 것이므로 ResponseEntity를 사용한다. map에다가 에러 코드와 내용을 넣어주고 반환한다.

 

 

이러면 너무 번거롭고 분명히 스프링에서 만들어놨을 것이다. 일단 기본으로 만든것부터 보자.

다시 직접 만든 설정을 없앤다.

그리고 그냥 실행해본다. 그럼 스프링의 기본 설정은 /error/~~~일 테니 resource에 알맞은 위치에 넣은거고 하니까 똑같이 웹 뷰가 나오지 않을까? 라고 할 수 있지만 클라이언트 헤더의 받을수 있는 요청에 따라 달라진다.

 

이는 아까 컨트롤러에서 클라이언트의 accept를 구체적으로 정한것 우선순위를 이용해서 다르게 출력했었는데 그거랑 똑같은 방식이다. 실제로 안의 코드를 보면 그렇고, 다른점은 기본으로 json 형식으로 반환해준다.

저렇게 json으로 표시하는 것도 설정에서 더 자세하게 만들 순 있지만.. 에러메세지가 자세한건 보안상 좋지는 않다. 그냥 가능은 하다고.

 

 

 

이제 기본으로 되어있는거 말고 스프링에서 지원하는 걸 사용해보자.

extendHandlerExceptionResolver를 사용할건데, 나만의 HandlerExceptionResolver를 추가해서 사용할거다.

이렇게 원래 서버 내부에서 에러가 뜬 거라 500 에러가 떴지만 해당 오류 코드를 받아 클라이언트가 잘못 입력했으므로 400으로 뜨도록 바꾸었다. 흐름을 보자.

 

해결을 시도하려고 ExceptionResolver에게 물어보는게 특징이다. ExceptionResolver가 빈 값이라도 좋으니 ModelAndView을 호출했으면 에러가 떴지만 안에서 예상한 에러라 정상적으로 처리되었다고 인식하고 정상 응답을 한다. null을 전달하면 그대로 예외상태로 다시 넘긴다.

ExceptionResolver가 HttpServletResponse를 받으므로 response.getWriter(). ... 을 사용해서 어떤 값으로 응답할지도 정할 수 있다. 물론 일일이 getWriter()... 를 사용할 순 없으므로 스프링이 만들어놓은게 있다. 나중에 사용한다.

 

 

UserException이라는 서버만을 위한 에러를 만들어서 처리해보자.

extendHandlerExceptionResolvers에 직접 만든 유저 예외 처리기를 등록한다. 응답은 처리기 안에서 정의한다. 일단 유저의 http 헤더 요청에 accept가 json이면 json으로 반환하고, 아니면 웹 뷰로 반환하게 했다.

 

그럼 역시 다르게 되었다.

이렇게 등록해주면 서브릿 컨테이너까지 예외가 올라가서 지저분하게 추가 프로세스가 실행되지 않고 ExceptionResolver선에서 처리되서 더 깔끔해진다.

 

근데 이렇게 서버용 EXceptionResolver를 힘들게 등록하는것도 선배들은 복잡하다고 느꼈는지(특히 HttpServletResponse로 응답 반환 쓰는거) 스프링이 제공하는 ExceptionResolver를 보자.

 

기본적으로 제공되는 것들은 우선순위가 정해져 있다.

API는 대부분 ExceptionHandlerExceptionResolver로 대부분 처리한다.

일단 ResponseStatusExceptionResolver를 보자.

이렇게만 해도 아까 HttpServletResponse에서 헤더를 400으로 맞추는 걸 그냥 @ResponseStatus만 하면 된다만 사실 이것도 스프링이 에러 잡으면 기본적으로 핸들러를 실행시켜서 @ResponseStatus로 등록된게 없나 찾은다음 찾았다면 해당 내용을 출력하는 것이다. 이 내용엔 400에러이므로 결국 안에서 HttpServletResponse의 헤더를 400으로 지정하는 메소드가 있다. 즉 아까 우리가 했던 것과 똑같은 걸 알아서 해주는거임.

 

 

근데 잘 보면 messageSource.getMessage가 있다. 이건 전에 했던 messageResolver해서 error.어쩌구 를 template의 message에서 입력하면 알아서 가져와서 출력했던거 있었다. 그대로 적용 된다.

 

 

 

만약 외부 라이브러리 같은걸 써서 직접 Exception을 구현하지 못할 경우, ResponseStatusException을 사용하면 된다. 얘도 결국 안에서 sendError하면서 하는 내용은 똑같음.

 

 

DefaultHandlerExceptionResolver는 스프링에서 기본적으로 해주는거. 사람이 만드는게 대부분 비슷하다보니 그냥 아얘 기본으로 해준다. 타입이 안맞고 하는건 거의 클라이언트에서 잘못보내는건데 이런것들도 어쨌든 서버 안에서 일어난 에러니 500으로 나오지만 사실 클라이언트 잘못이니 4xx이 나와야 한다. 그래서 스프링은 타입이 안맞으면 4xx을 반환하고 하는 것들임. 

 

하지만 이런 것들은 모델뷰가 기본이다 보니 api서버를 맞드는 지금 상황과는 맞지 않다. 그래서 나중에 알아본다고 했던 1번째 우선순위 API 예외 처리 @ExceptionHandler를 알아본다.

 

그냥 저렇게 쓰면 된다. @RestController이기 때문에 json을 반환한다.

 

이게 어떻게 된거냐면 handler 처리 방식에 있다. controller도 사실 http요청이 처음 왔을 때 resolver와 handler들을 통해 버전을 맞추고 컨버터를 넣어주고 string으로 "login/login-page" 이런식으로만 반환해도 알아서 ModelAndView같은걸 알아서 실행해서 폴더 안에 있는 login-page.html 찾아서 반환해주고... 들이 있었는데 에러 처리도 마찬가지로 여러 resolver와 handler가 있는거고 위에 3개 순위는 그 중의 일부를 알아본 것이다.

Exceptions :: Spring Framework

 

Exceptions :: Spring Framework

@Controller and @ControllerAdvice classes can have @ExceptionHandler methods to handle exceptions from controller methods, as the following example shows: @Controller public class SimpleController { // ... @ExceptionHandler public ResponseEntity handle(IOE

docs.spring.io

일단 에러가 빵 터지면 해당 예외는 컨트롤러 밖으로 던져지고 예외가 발생했으므로 ExceptionResolver가 발생한다. 이 ExceptionResolver가 앞에서 봤던 3개의 순서로 Resolver들을 실행하는데, 누가 해결해줄 수 있는지 찾는데, 이 3개의 실행 순서는 ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, DefaultHandlerExceptionResolver이다. 우선 ExceptionHandlerExceptionResolver가 해당 컨트롤러 클래스 안에 해당 예외를 처리할 수 있는 @ExceptionHandler가 있는지 확인하고 있으면 그걸 바로 실행하는 것이다. @RestController이므로 @ResponseBody가 실행되서 json이 나오는 것이고, 안에서 정상적으로 처리해서 내놨으니 http 헤더는 200으로 정상 처리 되었다고 나오기 때문에 400이 나오도록 에노테이션이나 동적으로 정해준다.

ExceptionHandlerExceptionResolver가 어느 클래스에 대해서 발동할지는 구체적인거 우선대로 방침으로 하위 클래스들부터 쭉 보고 없을수록 상위 클래스를 한다.

 

이 @ExceptionHandler들을 같은 예외처리를 각각의 컨트롤러에 넣긴 번거로우니 @ControllerAdvice를 쓰면 된다. 그냥 컨트롤러에 메소드를 다른 클래스에서 더 추가하는 개념임.

이러면 마치 저 컨트롤러에 저 함수들이 정의되어 있는 것처럼 된다.

어떤 클래스를 특정해서 저걸 주입하고 싶은지도 설정할 수 있음. 지정하지 않으면 글로벌이라 보통은 클래스까지는 넣는다.