본문 바로가기
Spring

[Spring] ReturnValueHandler 추가 방법 (kotlin ver.)

by 노력남자 2022. 12. 18.
반응형

이전 포스팅에선 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>"
반응형

댓글