이전 포스팅에선 ArgumentResolver 추가 방법을 다뤘었다.
이번 포스팅에선 ReturnValueHandler 추가하는 방법을 알아보자.
ReturnValueHandler란?
@Controller
class UserController {
@ResponseBody << 여기
@GetMapping("/user")
fun getUser(
user: UserInfoResponse
): UserInfoResponse {
return user
}
}
controller에 @ResponseBody를 붙이면 View Resolver를 타지 않고 httpMessageConverter를 사용해서 response 데이터를 처리해주는데 이걸 ReturnValueHandler에서 해준다. (@ResponseBody는 RequestResponseBodyMethodProcessor.java에서 처리)
Response Type이 HttpEntity, String 인 것도 ReturnValueHandler에서 한다.
그럼 이걸 어디서 호출할까?
이것도 핸들러 어댑터에서 호출하는데 ArgumentResolver로 받아온 인자 값으로 핸들러를 실행하고 리턴받은 값을 ReturnValueHandler가 처리해준다.
ReturnValueHandler 추가 방법
스프링에서 우리가 필요한 대부분을 추가해놔서 거의 추가할 일이 없지만 가끔 쓸일이 있다.
controller에서 리턴하는 값의 형식을 json이 아닌 xml로 리턴하는 ReturnValueHandler를 추가하는 걸 예시로 설명하겠다.
@Controller
class UserController {
@ToXml << response의 형식을 xml로 변환
@GetMapping("/user")
fun getUser(): UserInfoResponse {
return UserInfoResponse(
id = 1,
name = "유저명"
)
}
}
추가하는 방법은 다음과 같다.
ReturnValueHandler 정의 -> WebMvcConfigurer에 추가
ArgumentResolver랑 절차는 같은데 ReturnValueHandler가 좀 더 복잡하다.
1. ReturnValueHandler 정의
HandleMethodReturnValueHandler를 구현한 클래스를 만들어야 한다.
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
구현해야 할 메소드는 2개다.
supportsReturnType : 어떤 리턴 타입을 처리할 건지
handReturnValue : 리턴 값으로 어떤 작업을 할 건지
리턴 값의 형식을 xml로 변환하는 ToXmlReturnValueHandler를 만들어보자.
조금 복잡할 수 있는데 설명을 잘 읽어보자.
class ToXmlReturnValueHandler(
mappingJackson2HttpMessageConverter: MappingJackson2HttpMessageConverter
) : AbstractMessageConverterMethodProcessor(listOf(mappingJackson2HttpMessageConverter)) {
override fun supportsParameter(parameter: MethodParameter): Boolean {
throw Exception()
}
override fun resolveArgument(
parameter: MethodParameter,
mavContainer: ModelAndViewContainer?,
webRequest: NativeWebRequest,
binderFactory: WebDataBinderFactory?
): Any? {
throw Exception()
}
override fun supportsReturnType(returnType: MethodParameter): Boolean {
return returnType.hasMethodAnnotation(ToXml::class.java)
}
override fun handleReturnValue(
returnValue: Any?,
returnType: MethodParameter,
mavContainer: ModelAndViewContainer,
webRequest: NativeWebRequest
) {
mavContainer.isRequestHandled = true // viewResolver를 타지 않기 위함
writeWithMessageConverters(XML.toString(JSONObject(returnValue)), returnType, webRequest)
// implementation("org.json:json:20220924") 추가 필요
}
}
일단 다 제치고 맨 아래 2개 메소드만 보고 사용 방법을 익히자.
supportReturnType 메소드는 간단하니 바로 이해가 갈 거다.
handleReturnValue는 String을 리턴해야 하기 때문에 MessageConverter를 사용했다.
시간있으면 RequestMappingHandlerAdapter.java에 있는 invokeHandlerMethod를 쭉 한번 따라가보자.
엥? HandleMethodReturnValueHandler를 구현해야 한다고 했는데 AbstractMessageConverterMethodProcessor를 상속받았네? 에 대한 이유가 궁금하면 아래를 펼쳐보자.
AbstractMessageConverterMethodProcessor는 @ResponseBody, @RequestBody, HttpEntity를 처리할 때 사용하는 ArgumentResolver이자 ReturnValueHandler이다. 여기에 writeWithMessageConverters가 구현되어 있어서 가져다가 사용했다. 실제로 구현해서 쓸 수도 있겠지만 굳이 그럴필요는 없어보인다.
ArgumentResolver 필수 메소드 supportsParameter, resolveArgument는 사용하지 않아서 호출 시 Exception을 던지게 해놨다.
2. WebMvcConfigurer에 추가
WebMvcConfigurer를 구현한 클래스를 만들어야 한다.
위에서 만든 ToXmlReturnValueHandler를 추가해보겠다.
@Component
class WebMvcConfig : WebMvcConfigurer {
override fun addReturnValueHandlers(handlers: MutableList<HandlerMethodReturnValueHandler>) {
handlers.add(ToXmlReturnValueHandler(MappingJackson2HttpMessageConverter()))
}
}
※ 결과
위에서 정의한 ReturnValueHandler가 정상적으로 동작하는지 보자.
- request
curl --location --request GET 'http://localhost:8080/user'
- response
"<name>유저명</name><id>1</id>"
'Spring' 카테고리의 다른 글
[Spring] MissingKotlinParameterException을 ExceptionHandler로 처리 하는 방법 (0) | 2023.01.15 |
---|---|
[Spring] "has been compiled by a more recent version" 에러 처리 방법 (0) | 2022.12.19 |
[Spring] ArgumentResolver 추가 방법 (kotlin ver.) (0) | 2022.12.18 |
[Spring] 코틀린 springdoc-openapi 클래스명 중복 처리 방법 (0) | 2022.09.09 |
[Spring] 코틀린 springdoc-openapi, swagger - enum 소문자, 원하는 값으로 보여주기 (0) | 2022.09.09 |
댓글