[boostcourse] 5.7. Argument Resolver - BE

1. Argument Resolver란?

- 사용자가 임의의 값을 컨트롤러의 메소드 인자로 전달할 때 사용됨

- 예를 들어, 세션에 저장되어 있는 값 중 특정 이름의 값을 메소드 인자로 전달하는 경우

 

즉, 어떠한 요청이 컨트롤러에 들어왔을 때, 요청에 들어온 값으로부터 원하는 객체를 만들어내는 일을 Argument Resolver가 간접적으로 해줄 수 있음

예를 들어, 사용자가 로그인되어 있을 때, 올바른 사용자인지 확인해야 함. 사용자가 가진 토큰이 유효한 토큰인지 검증을 거친 후, 토큰에 저장된 id를 꺼내 LoginMember라는 객체로 만들어내는 과정이 필요 -> 이때 ArgurmentResolver를 사용할 수 있음

 

Argument Resolver와 Interceptor의 차이점

- Argument Resolver는 Interceptor 이후에 동작하며, 어떠한 요청이 컨트롤러에 들어왔을 때, 요청에 들어온 값으로부터 원하는 객체를 반환함

- Interceptor는 실제 컨트롤러가 실행되기 전에 요청을 가로채며, 특정 객체를 반환할 수 없음. 오직 boolean 혹은 void의 리턴 타입만 존재함

 

https://hudi.blog/spring-handler-interceptor/

 

Spring HandlerInterceptor를 활용하여 컨트롤러 중복 코드 제거하기

우아한테크코스 레벨2 마지막 미션인 장바구니 미션에서 인증과 인가를 구현하기 위해, Spring Interceptor 를 사용해야했다. 이를 위해 학습한 내용을 정리해보았다. 컨트롤러에서 발생한 중복 코드

hudi.blog

https://hudi.blog/spring-argument-resolver/

 

스프링에서 Argument Resolver 사용하기

컨트롤러에서 쿼리 스트링을 변수에 바인딩하려면 @RequestParam 을, 가변적인 경로를 변수에 바인딩하려면 @PathVariable 을, HTTP Body를 변수에 바인딩하려면 @RequestBody 를 사용해야한다. 하지만 HTTP Head

hudi.blog

 

Argument Resolver 작성 방법

- org.springframework.web.method.support.HandlerMethodArgumentResolver를 구현한 클래스를 작성함

- supportsParameter 메소드를 오버라이딩한 후, 원하는 타입의 인자가 있는지 검사한 후 있을 경우 true를 리턴하도록 함

- resolveArgument 메소드를 오버라이딩한 후, 메소드의 인자로 전달할 값을 리턴함

 

이렇게 작성한 후,

JavaConfig에 설정한다면, WebMvcConfigurerAdapter를 상속받은 Java Config 파일에서 addArgumentResolvers 메소드를 오버라이딩한 후 원하는 Argument Resolver 클래스 객체를 등록함

xml 파일에 설정한다면,

<mvc:annotation-driven>

  <mvc:argument-resolvers>

    <bean class="ArgumentResolver클래스명"></bean>

  </mvc:argument-resolvers>

</mvc:annotation-driven>

 

2. Argument Resolver 적용하기

웹 애플리케이션에 Argument Resolver를 적용해 HTTP 요청 헤더 정보를 저장하고 있는 HeaderInfo 인자 타입이 메소드에 있는 경우, 자동으로 넘겨주기

- Map이나 Map을 상속받는 객체는 Spring에서 내부적으로 사용하고 있는 Argument Resolver가 선처리하기에 직접 사용할 수 없음

- 그러므로 Map을 필드로 가지고 있는 HeaderInfor라는 클래스를 작성하도록 함

 

#HeaderInfo.java

package com.example.guestbook.argumentresolver;

import java.util.HashMap;
import java.util.Map;

public class HeaderInfo {

    private Map<String, String> map;

    public HeaderInfo() {
        map = new HashMap<>();
    }

    public void put(String name, String value) {
        map.put(name, value);
    }

    public String get(String name) {
        return map.get(name);
    }
}

 

 # HeaderMapArgumentResolver.java

- HandlerMethodArguementResolver를 상속받는 클래스

package com.example.guestbook.argumentresolver;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.util.Iterator;

public class HeaderMapArgumentResolver implements HandlerMethodArgumentResolver {

    // 인자의 정보를 parameter로 전달함. 해당 파라미터 정보에 원하는 정보가 있다면 true 리턴
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType() == HeaderInfo.class;
    }

    // supportsParameter()가 true를 리턴할 때만, resolveArgument()가 호출됨
    // 리턴한 값은 컨트롤러의 인자로 전달됨
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        HeaderInfo headerInfo = new HeaderInfo();

        Iterator<String> headerNames = webRequest.getHeaderNames();
        while(headerNames.hasNext()) {
            String headerName = headerNames.next();
            String headerValue = webRequest.getHeader(headerName);
            System.out.println(headerName + ", " + headerValue);
            headerInfo.put(headerName, headerValue);
        }

        return headerInfo;
    }
}

 

# WebMvcContextConfiguration.java

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        System.out.println("argument resolver 등록");
        argumentResolvers.add(new HeaderMapArgumentResolver());
    }

 

ArgumentResolver를 생성하고 등록함

그럼 이제 Controller에서, ArgumentResolver는 해당 인자가 넘어왔을 때 사용됨. 그러므로 파라미터에 HeaderInfo를 추가함

 

# GuestbookController.java

    @GetMapping("/list")
    public String list(@RequestParam(name = "start", required = false, defaultValue = "0") int start,
                       ModelMap model, @CookieValue(value="count", defaultValue = "0", required = true) String value,
                       HttpServletResponse response, HeaderInfo headerInfo) {

        System.out.println("----------------------------");
        System.out.println(headerInfo.get("user-agent"));
        System.out.println("----------------------------");
        
        // 이하 기존 코드
        
    }

ArgumentResolver를 사용해서 header 정보를 잘 출력하고 있음