예전 로직
지금까지는 예외가 발생하고 WAS까지 예외가 던져지고, WAS에서 오류페이지를 찾은 후 다시 /error로 호출했다.
하지만 이렇게 여러 필터 및 인터셉터를 거치는 과정은 매우 비효율적이고 복잡하다.
이때 ExceptionResolver를 활용하면 이러한 과정을 거치지 않고 예외를 깔끔하게 처리할 수 있다.
UserException 정의 예외 추가
RuntimeException을 상속받는 사용자 정의 예외를 추가했다.
public class UserException extends RuntimeException{
public UserException() {
super();
}
public UserException(String message) {
super(message);
}
public UserException(String message, Throwable cause) {
super(message, cause);
}
public UserException(Throwable cause) {
super(cause);
}
}
그리고 기존 Api에 UserException을 일어나는 상황을 추가했다.
@RestController
public class ApiExceptionController {
@GetMapping("/api/members/{id}")
public MemberDto getMember(@PathVariable String id) {
if (id.equals("ex")) {
throw new RuntimeException("잘못된 사용자");
}
if (id.equals("bad")) {
throw new IllegalArgumentException("잘못된 입력 값");
}
if (id.equals("user-except")) {
throw new UserException("사용자 오류");
}
return new MemberDto(id, "나의 이름은! " + id);
}
}
그리고 이러한 예외가 일어났을 때 예외를 처리하는 UserHandlerExceptionResolver를 만들었다.
@Slf4j
public class UserHandlerExceptionResolver implements HandlerExceptionResolver {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
// 정의한 예외가 발생한다면
if (ex instanceof UserException) {
log.info("UserException resolver to 400");
// 헤더에서 accept 필드를 갖고옴
String acceptHeader = request.getHeader("accept");
// 응답 상태코드를 400으로 설정
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
// 만약 accept가 json이면
if (acceptHeader.equals("application/json")) {
Map<String, Object> errorResult = new HashMap<>();
errorResult.put("ex", ex.getClass()); // 어떤 Exception인지
errorResult.put("message", ex.getMessage()); // 에러 메세지
String jsonErrorResult = objectMapper.writeValueAsString(errorResult);
// Map을 Json으로 바꾸기 위해 objectMapper의 메서드를 활용
response.setContentType("application/json");
// 응답 방식을 json으로 설정
response.setCharacterEncoding("utf-8");
response.getWriter().write(jsonErrorResult);
// 응답에 json을 보여줌
return new ModelAndView(); // 아무것도 return 안함
}else {
// ex) TEXT/HTML
return new ModelAndView("error/500"); // 500.html
}
}
} catch (IOException e) {
log.error("resolver ex", e);
}
return null;
}
}
Http 요청 헤더의 값을 비교하여 application/json이라면 json으로 오류를 내려주고 그 외 경우에는 error.html에 있는 500.html을 보여준다.
Resolver 등록
이 전과 마찬가지로 이를 등록해야 한다. extendHandlerExceptionResolver에 등록하자.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new MyHandlerExceptionResolver());
resolvers.add(new UserHandlerExceptionResolver());
}
// ... 필터.. 인터셉터.. 등 이전 코드
}
실행 결과
RuntimeException을 상속받은 정의 에러 UserException은 500 상태코드의 에러였다. 하지만 실행해 보면 400 상태코드의 에러로 확인할 수 있다.
정리하자면 ExceptionResolver를 활용하면 컨트롤러에서 예외가 발생해도 똑똑한 ExceptionResolver가 예외를 해결한다.
예외가 발생해도 서블릿 컨테이너까지 예외가 전달되지 않고, SpringMVC에서 예외 처리는 끝난다.
결과적으로 WAS 입장에서는 정상 처리가 된 것이다.
서블릿 컨테이너까지 예외가 올라가면 예전에 했던 것과 같은 필터, 인터셉터 등 필요 없는 추가 프로세스가 실행된다.
하지만 ExceptionResolver를 사용하면 예외처리가 깔끔해진다.
그런데, 예외를 해결하려면 매번 이렇게 정의를 해줘야 할까?
Spring은 이러란 예외들도 이미 해결해놨다.
다음 포스팅에서는 Spring이 제공하는 ExceptionResolver를 알아본다.
코드 링크
https://github.com/toychip/StudySpring/commit/48e62e2257eddce55eafd4ce7a553dfa7a970de4
Feat: Spring-mvc2-exception HandlerExceptionResolver 활용 · toychip/StudySpring@48e62e2
toychip committed Aug 23, 2023
github.com
김영한님 mvc2 - Exception 강의로 학습하였습니다.
공부한 내용을 상기시키기고 해당 내용을 몰랐을 때의 입장에서 쉽게 배울 수 있지 않을까 해서 작성한 글입니다. 잘못된 내용이 있을 경우 말씀해 주세요. 피드백 환영합니다.
'Spring > Spring Exception' 카테고리의 다른 글
Spring Exception 9. Spring이 제공하는 @ExceptionHandler (0) | 2023.08.21 |
---|---|
Spring Exception 8. Spring이 제공하는 @ResponseStatus, DefaultHandler (0) | 2023.08.19 |
Spring Exception 6. ExceptionResolver (HandlerExceptionResolver) (0) | 2023.08.17 |
Spring Exception 5. 스프링 부트 Api 오류처리 (BasicErrorController) (0) | 2023.08.16 |
Spring Exception 4. Api 예외 처리 (Spring mvc VS RestfulApi) (0) | 2023.08.16 |