우선 이와 같은 질문을 구글링해보면 아래와 같은 방법이 StackOverFlow에 소개되고 있긴 하다.
Feign ErrorDecoder에서 발생한 Exception은 ControllerAdvice에서 캐치를 하지 못하니 다음과 같이 try ~ catch로 feign 바깥에서 처리하라는 의견.
try{
feignClient.method();
} catch(Exception ex){
//throw exceptions you want
throw new YourException();
}
@ControllerAdvice
public class GlobalControllerAdvice {
private final Logger log = LoggerFactory.getLogger(getClass());
@ExceptionHandler(YourException.class)
public ResponseEntity<RestApiException> handleException(RestApiException exception, HttpServletRequest req) {
//impl
}
}
그런데 나는 위와 같이 했음에도, 나의 ControllerAdvice가 동작하지 않았다. 분명히 등록되어 있는 CustomException인데도 말이다 -_-; 좀 더 파보면 원인이 나오겠지만, 나는 이걸 트라이캐치로 분기하고 있으려니 너무 너무 코드가 맘에들지 않았다. 그래서 다른 방법을 생각해봤다.
ControllerAdvice로는 Feign의 ErrorDecoder에서 발생한 Exception을 잡지 못한다?
No! 잡을 수 있다. 대신 좀 다르게 해야한다.
그게 무슨 말이냐면, CustomException을 ControllerAdvice에 등록해놓고, ErrorDecoder에서 CustomException을 던지면 못받는다는 것이다.
FeignClient에서 발생한 Exception은 FeignClientException을 ControllerAdvice에 등록해야 Exception Catch가 된다.
그래서 나는 ErrorDecoder에서 FeignClientException에 status와 ErrorMessage를 넣어서 분기처리하여 해결했다.
@Slf4j
@RequiredArgsConstructor
public class FeignClientExceptionErrorDecoder implements ErrorDecoder {
private final ObjectMapper objectMapper;
private final StringDecoder stringDecoder;
@Override
public FeignClientException decode(String methodKey, Response response) {
String message = null;
Error errorForm = null;
if (response.body() != null) {
try {
message = stringDecoder.decode(response, String.class).toString();
errorForm = objectMapper.readValue(message, ErrorResponseDto.class).getError();
} catch (IOException e) {
log.error("[{}] Error Deserializing response body from failed feign request response. = {}", methodKey, e);
}
}
return new FeignClientException(response.status(), errorForm.getMessage(), response.request(), message.getBytes(StandardCharsets.UTF_8));
}
}
이 Decoder를 Bean으로 등록해주었다.
@Configuration
@RequiredArgsConstructor
public class FeignClientErrorDecoderConfiguration {
private final ObjectMapper objectMapper;
@Bean
public FeignClientExceptionErrorDecoder errorDecoder() {
return new FeignClientExceptionErrorDecoder(objectMapper, new StringDecoder());
}
}
그러면 A server -> B server 로 요청을 보내는 FeignClient가 어떤 문제가 발생하여 B server로부터 [400] ~~~ is invalid. 와 같은 메세지를 받게 되었다면, A서버는 FeignClientException이 발생하게 될 것이고, 이를 ErrorDecoder에서 B로 부터 받은 메세지를 ErrorResponseDto 와 같은 임의의 Dto로 Mapping하여, 원하는 메세지만 FeignClientException에 담아서 리턴한다.
그렇다면? 이를 처리하는 ControllerAdvice를 등록해주면 된다!
나는 다음과 같이 ControllerAdvice를 설정했다.
@Slf4j
@RestControllerAdvice(basePackages = "{{ your package }}")
public class MarketingControllerAdvice {
@ExceptionHandler(FeignException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponseDto feignExceptionHandler(FeignException ex) {
return ErrorResponseDto.error(ErrorCode.INTERNAL_SERVER_ERROR.getCode(), ex.getMessage());
}
@ExceptionHandler(FeignClientException.class)
public ErrorResponseDto FeignClientExceptionHandler(FeignClientException ex) {
final ErrorCode resolve = ErrorCode.resolve(ex.status());
return ErrorResponseDto.error(resolve.getCode(), ex.getMessage());
}
}
아 물론, ErrorCode는 Enum으로 임의로 설정했다.
즉, 정리하자면, B server로부터 status와 message를 가지고, A server에서 다시 원하는 형태로 리턴하기 위해서 ControllerAdvice를 적용한 것이고, 이 때 ErrorDecoder를 활용하여, 원하는 메시지를 실제 A server에 Request를 보낸 Client에게 Response로 B server의 message도 안전하게 출력하고, ErrorCode 및, Status도 재 설정할 수 있게 만들었다.
우선 이와 같은 질문을 구글링해보면 아래와 같은 방법이 StackOverFlow에 소개되고 있긴 하다. Feign ErrorDecoder에서 발생한 Exception은 ControllerAdvice에서 캐치를 하지 못하니 다음과 같이 try ~ catch로 feign 바깥에서 처리하라는 의견.
그런데 나는 위와 같이 했음에도, 나의 ControllerAdvice가 동작하지 않았다. 분명히 등록되어 있는 CustomException인데도 말이다 -_-; 좀 더 파보면 원인이 나오겠지만, 나는 이걸 트라이캐치로 분기하고 있으려니 너무 너무 코드가 맘에들지 않았다. 그래서 다른 방법을 생각해봤다.
ControllerAdvice로는 Feign의 ErrorDecoder에서 발생한 Exception을 잡지 못한다?
No! 잡을 수 있다. 대신 좀 다르게 해야한다.
FeignClientException을 ControllerAdvice에 등록해야 Exception Catch가 된다.
이 Decoder를 Bean으로 등록해주었다.
그러면 A server -> B server 로 요청을 보내는 FeignClient가 어떤 문제가 발생하여 B server로부터 [400] ~~~ is invalid. 와 같은 메세지를 받게 되었다면, A서버는 FeignClientException이 발생하게 될 것이고, 이를 ErrorDecoder에서 B로 부터 받은 메세지를 ErrorResponseDto 와 같은 임의의 Dto로 Mapping하여, 원하는 메세지만 FeignClientException에 담아서 리턴한다.
그렇다면? 이를 처리하는 ControllerAdvice를 등록해주면 된다! 나는 다음과 같이 ControllerAdvice를 설정했다.
아 물론, ErrorCode는 Enum으로 임의로 설정했다. 즉, 정리하자면, B server로부터 status와 message를 가지고, A server에서 다시 원하는 형태로 리턴하기 위해서 ControllerAdvice를 적용한 것이고, 이 때 ErrorDecoder를 활용하여, 원하는 메시지를 실제 A server에 Request를 보낸 Client에게 Response로 B server의 message도 안전하게 출력하고, ErrorCode 및, Status도 재 설정할 수 있게 만들었다.