일반적인 개발 프로젝트에서는 클라이언트에게 전체 예외 메시지가 아닌 특정 형태를 갖춘 메시지를 반환한다.
현재 개발 중인 프로젝트에서도 @RestControllerAdvice와 @ExceptionHandler를 활용해서 애플리케이션 전역에서 발생하는 예외를 특정 형태로 맞춰서 클라이언트에게 반환하도록 설정되어 있었고 그동안 잘 사용하고 있었다.
[GlobalExceptionHandler]
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Object> handleIllegalArgument(IllegalArgumentException e) {
log.error("handleIllegalArgument ", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ShibaErrorResponse("4001", e.getMessage()));
}
@RequiredArgsConstructor
@Getter
public static class ShibaErrorResponse {
private final String errorCode;
private final String message;
}
}
@RestControllerAdvice(또는 @ControllerAdvice)와 @ExceptionHandler를 사용하면 다음과 같이 특정 예외(IllegalArgumentException 등)에 대해서 특정 형태로 손쉽게 처리할 수 있다.
(ControllerAdvice = 애플리케이션 전체에서 동일한 예외 처리를 사용할 수 있도록 해준다)
하지만 Spring Security에서 Filter를 추가하는 업무를 수행하던 도중 예외가 발생했는데, 특정 형태로 잡지 못하고 예외 전체가 출력되는 문제가 발견되었다. 😭
[ShibaFilter]
@Component
public class ShibaFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getRequestURI().equals("/api/shiba")) {
throw new IllegalArgumentException("예외가 발생!");
}
filterChain.doFilter(request, response);
}
}
필터는 특정 URI에 예외가 발생하도록 설정해서 테스트하였다.
예외가 원하는 특정 형태로 반환되지 않고 그대로 출력되는 것을 확인할 수 있었다.
[원인]
그러면 원인은 대체 뭘까? 🙀
원인은 ControllerAdvice 적용 범위에 있었다. ControllerAdvice는 Dispatcher Servlet 내에서 발생하는 예외만 잡는다. 하지만 Filter에서 예외가 발생하면 시점이 Dispatcher Servlet 처리 이전이므로 특정 형태로 처리가 불가능하다.
[해결]
기존 애플리케이션의 예외 처리와 동일한 특정 예외 처리 형태를 유지하려면 Controller까지 보내서(위임해서) 예외를 처리하면 된다. 즉, Spring에서 예외를 처리하는 방법 중 한 가지인 HandlerExceptionResolver에게 예외를 위임한다.
@Component
public class ShibaFilter extends OncePerRequestFilter {
private final HandlerExceptionResolver resolver;
public ShibaFilter(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolver = resolver;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getRequestURI().equals("/api/shiba")) {
try {
throw new IllegalArgumentException("예외가 발생!");
} catch (Exception ex) {
resolver.resolveException(request, response, null, ex);
}
}
filterChain.doFilter(request, response);
}
}
@Qualifier("handlerExceptionResolver")를 활용하여 정확하게 handlerExceptionResolver를 주입해 주어야 한다. 그렇지 않으면 해당하는 Bean을 여러 개 찾았다는 오류 메시지를 볼 수 있다.
위의 코드와 같이 HandlerExceptionResolver의 resolveException 메서드를 통해 Filter 처리 과정에서 발생한 예외를 처리하도록 넘기면 예외가 특정 형태로 클라이언트에 반환되는 것을 확인할 수 있다.
🐕 REFERENCE
https://stackoverflow.com/questions/34595605/how-to-manage-exceptions-thrown-in-filters-in-spring
How to manage exceptions thrown in filters in Spring?
I want to use generic way to manage 5xx error codes, let's say specifically the case when the db is down across my whole spring application. I want a pretty error json instead of a stack trace. F...
stackoverflow.com
https://stackoverflow.com/questions/17715921/exception-handling-for-filter-in-spring
exception handling for filter in spring
I am handling exceptions in spring using @ExceptionHandler. Any exception thrown by controller is caught using method annotated with @ExceptionHandler and action is taken accordingly. To avoid writ...
stackoverflow.com
Spring Handle Exception | Carrey`s 기술블로그
들어가며 Spring에서 제공하는 예외처리 방법에는 유용한 방법들이 몇가지 있다. Dispatcher Servlet내에서는 몇 가지 HandleExceptionResolver를 제공하여 예외 처리를 할 수 있도록 돕고 있다. 또한 @Controller
jaehun2841.github.io
'Spring' 카테고리의 다른 글
[Spring] Redis 세션 로그아웃, 만료시 관련 처리 작업하기 (0) | 2023.04.16 |
---|---|
Spring Redis Session 설정, 특징, 데이터 조회 (0) | 2023.04.08 |
[Spring] 데이터와 연관된 파일 데이터(multipart)를 목록으로 받기 (0) | 2023.04.02 |
Spring으로 네이버 비로그인 방식 오픈 API 사용해보기 (0) | 2023.03.26 |
[Spring] ServiceLocatorFactoryBean을 활용하여 분기 처리하기 (2) | 2023.02.19 |
댓글