-
Spring Boot MessageConverterJAVA 2024. 6. 24. 01:29
개요
Spring Boot 기반 개인 프로젝트를 진행하는중 MessageConverter 관련해 문제가 있었다. 이 문제를 해결하며 알게된 내용을 정리해보았다.
문제
@GetMapping("/test2") public String test2(){ return "test 2"; } @GetMapping("/test3") public List<String> test3(){ return List.of("t","e","s","t"); }
기존 코드의 Controller 부분이다. /test2 에 요청하면 String 을 반환하고, /test3 에 요청하면 ["t","e","s","t"] 를 반환한다.
public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { return new BaseResponse<>(ResponseStatus.SUCCESS, body); }
ResponseBodyAdvice 를 구현한 클래스에서 beforeBodyWrite 를 이용해서 최종 응답이 나가기 전에 내가 정의한 BaseResponse 형식으로 통일해서 나간다.
Advice 에서 응답 형식을 맞춰주어 Controller 에서 직접 응답마다 BaseResponse 를 만들어 주는 수고를 줄이고 공통적으로 처리하려했다. 하지만 위 코드를 테스트 해보면 문제가 생긴다.
/test3 인경우는 정상 동작하지만 /test2 에 요청을 보내면 아래와 같은 에러가 발생한다.
java.lang.ClassCastException: class com.cal.calB.dto.response.BaseResponse cannot be cast to class java.lang.String
의미를 보자면 BaseResponse를 String으로 cast 하다가 오류가 발생했다고한다. 왜 이런 에러가 발생했는지 확인해보았다.
원인
결론부터 말하면 String을 반환할때와 그렇지 않을때의 MessageConverter 가 다르기 때문이다.
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { Object body; Class<?> valueType; Type targetType; if (value instanceof CharSequence) { body = value.toString(); valueType = String.class; targetType = String.class; } else { body = value; valueType = getReturnValueType(body, returnType); targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass()); } ... }
Spring 의 AbstractMessageConverterMethodProcessor 의 writeWithMessageConverters 일부분이다.
writeWithMessageConverters 는 HandlerAdapter -> invokeAndHandle -> handleReturnValue 에서 호출된다.
저 코드에서 value 는 return 한 값인데 저 밸류가 CharSequence 라면 String 으로 처리해주는것을 볼 수 있다.
for (HttpMessageConverter<?> converter : this.messageConverters) { GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ghmc ? ghmc : null); if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) { body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage); } }
마찬가지로 writeWithMessageConverters 의 일부분이다. 위에서 정한 valueType, targetType 등에 따라 등록된 converters를 돌면서 canWrite 을 검사한다.
public boolean supports(Class<?> clazz) { return String.class == clazz; }
위 코드는 StringHttpMessageConverter 의 supports 함수이다. canWrite가 호출하는 함수인데 여기서 검사하는 Class 가 String 과 일치하는지 비교한다. 우리는 처음 String을 return 했기때문에 여기서 true가 나오고 StringHttpMessageConverter 를 Converter로 선택하게 된다.
canWrite 검사 후 advice를 돌며 beforeBodyWrite 를 해주게 된다. 이때 우리는 결과물로 body 를 내가 정의한 BaseResponse 를 받게된다. 후에 write 하게 되는데, StringConverter 를 선택해놓고 BaseResponse를 반환하니 에러가 발생하는 것이다.
해결
public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if(body instanceof String){ BaseResponse<String> res = new BaseResponse<>(ResponseStatus.SUCCESS, (String) body); try { ObjectMapper objectMapper = new ObjectMapper(); String stringRes = objectMapper.writeValueAsString(res); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); return stringRes; } catch (JsonProcessingException err){ throw new RuntimeException("Failed to convert BaseResponse to JSON"); } } return new BaseResponse<>(ResponseStatus.SUCCESS, body); }
Advice 의 beforeBodyWrite에서 String에 대한 처리를 따로 해주었다. 파싱 에러난 부분에 대해서는 추후 추가적인 처리가 필요해 보인다.
'JAVA' 카테고리의 다른 글
Spring Boot AOP를 이용해 로그인 검증하기 (0) 2024.07.29 Java mutable 과 immutable (1) 2023.10.08 Java와 일급 함수 (0) 2023.09.18 Java Generic Compile 동작 (0) 2023.09.11 JVM Memory Area (0) 2023.09.08