영주머니의 개발주머니

ArgumentResolver 종류 및 사용 예시 본문

Spring

ArgumentResolver 종류 및 사용 예시

영주머니 2025. 5. 7. 20:27

@RequestParam

  • 1개의 HTTP 요청 파라미터를 받기 위해 사용한다.

@ModelAttribute

  • 클라이언트가 전송하는 폼(Form) 형태의 HTTP Body와 요청 파라미터들을 생성자나 Setter로 바인딩하기 위해 사용된다.
  • @ModelAttribute에는 매핑시키는 파라미터의 타입이 객체의 타입과 일치하는지 등을 포함한 다양한 검증(Validation) 작업이 추가적으로 진행된다.

@RequestBody

  • 클라이언트가 전송하는 Json 형태의 HTTP Body를 Java 객체로 변환해주는 역할을 한다.
  • @RequestBody로 받는 데이터는 Spring에서 관리하는 MessageConverter들 중 하나인 MappingJackson2HttpMessageConverter를 통해 Java 객체로 변환되는데, 이는 ObjectMapper라는 클래스를 사용한다.

사용 예시

  • 테스트를 위해 아래와 같은 User 객체가 있다고 하자.
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Enumerated(EnumType.STRING)
    private UserRole role;


    public void setId(Long id) {
        this.id = id;
    }
    public Long getId() {
        return id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setRole(UserRole role) {
        this.role = role;
    }
    public UserRole getRole() {
        return role;
    }
}

@RequestParam 사용 예시

  • Controller
@PostMapping("/signup")
public String signup(@RequestParam("name") String name,
                     @RequestParam("role") UserRole role) {

    User user = new User();
    user.setName(name);
    user.setRole(role);

    User savedUser = userService.signUp(user);
    session.setAttribute(SessionConstant.CURRENT_USER, savedUser);
    return new CouponResponse<>("회원가입이 완료되었습니다.");
}
  • 요청 형태(1) : URL 쿼리 파라미터
POST /signup?name=홍길동&role=USER
  • 요청 형태(2): application/x-www-form-urlencoded
Content-Type: application/x-www-form-urlencoded

name=홍길동&role=USER
  • 요청 처리 과정
    • @RequestParam을 보고 요청 파라미터 중 name, role 값을 찾아서 해당 메서드 파라미터에 바인딩한다.

@ModelAttribute 사용 예시

  • Controller
    @PostMapping("/signup")
    public CouponResponse<Void> signup(@ModelAttribute User user, HttpSession session) {
        User savedUser = userService.signUp(user);
        session.setAttribute(SessionConstant.CURRENT_USER, savedUser);
        return new CouponResponse<>("회원가입이 완료되었습니다.");
    }
  • 요청 형태(1) : HTML form
<form action="/signup" method="post">
  <input type="text" name="name" />
  <select name="role">
    <option value="USER">USER</option>
    <option value="ADMIN">ADMIN</option>
  </select>
  <button type="submit">가입</button>
</form>
  • 요청 형태(2): application/x-www-form-urlencoded
Content-Type: application/x-www-form-urlencoded

name=홍길동&role=USER

@RequestBody 사용 예시

  • Controller
@PostMapping("/signup")
public CouponResponse<Void> signup(@RequestBody User user, HttpSession session) {
    User savedUser = userService.signUp(user);
    session.setAttribute(SessionConstant.CURRENT_USER, savedUser);
    return new CouponResponse<>("회원가입이 완료되었습니다.");
}
  • 요청 형태 : JSON Body
POST /signup
Content-Type: application/json

{
  "name": "홍길동",
  "role": "USER"
}
  • 요청 처리 과정
    • 클라이언트가 JSON 전송
    • @RequestBody는 클라이언트가 보낸 JSON 데이터를 읽어서 내부적으로 Jackson 라이브러리가 JSON 데이터를 User라는 자바 객체로 역직렬화(deserialize) 해준다.
    • 컨트롤러에서는 이걸 User 자바 객체로 받아서 사용한다.
    • 따라서, user.getName(), user.getRole() 등 자바 코드로 접근이 가능하다.

커스텀 ArgumentResolver

  • 공통 파라미터를 컨트롤러에서 반복적으로 사용해야 하는 경우 HandlerMethodArgumentResolver를 구현하여 반복되는 파라미터 처리 로직을 추상화하고 깔끔하게 관리할 수 있다.
  • 예를 들어, 아래 코드와 같이 로그인한 사용자 정보를 매번 세션에서 꺼내야 하는 상황을 생각해보자.
@GetMapping("/myCoupons")
public CouponResponse<List<CouponIssuance>> showMyCoupons(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    User user = (User) session.getAttribute(SessionConstant.CURRENT_USER);

    Long userId = user.getId();
    List<CouponIssuance> myCouponList = couponService.getMyCoupons(userId);
    return new CouponResponse<>("내 쿠폰 목록 조회에 성공했습니다.", myCouponList);
}
  • 반복되는 로직을 모든 컨트롤러에 쓰면 중복이 발생하고 실수가 유발되기 쉽다.
  • 중복되는 세션 접근 로직을 제거하기 위해 @LoginUser 어노테이션과 커스텀 ArgumentResolver를 구현하여, 컨트롤러에서 로그인 유저 객체를 자동 주입받을 수 있도록 구현할 수 있다.

구현 방법

  1. @LoginUser 어노테이션 정의
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginUser {
}
  1. ArgumentResolver 구현
@Component
public class LoginUserArgumentResolver  implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // @LoginUser가 붙고, 타입이 User인 경우만 처리
        return parameter.hasParameterAnnotation(LoginUser.class)
                && User.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpSession session = webRequest.getNativeRequest(HttpServletRequest.class).getSession();
        return session.getAttribute(SessionConstant.CURRENT_USER);
    }
}
  • supportsParameter( … )
    • 이 파라미터를 해당 Resolver가 처리할 대상인지 판단하는 메서드이다.
    • true를 반환하면, 이 파라미터는 해당 Resolver가 처리하게 된다.
  • resolveArgument( … )
    • 파라미터에 실제로 어떤 값을 넣을 결정하는 메서드이다.
    • 위의 예시에서는 세션에서 로그인된 사용자(User 객체)를 꺼내서 반환한다.
  1. WebMvcConfigurer에 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginUserArgumentResolver loginUserArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(loginUserArgumentResolver);
    }
}
  • Spring MVC는 요청이 들어오면 각 파라미터를 처리할 HandlerMethodArgumentResolver를 찾는다.
  • Spring MVC에 우리가 만든 LoginUserArgumentResolver를 등록함으로써, 컨트롤러 메서드에서 @LoginUser 어노테이션 붙은 파라미터가 있을 때 해당 Resolver가 동작하여 세션에서 로그인된 사용자 정보를 자동으로 주입해준다.

Controller 적용 예시

    @GetMapping("/myCoupons")
    public CouponResponse<List<CouponIssuance>> showMyCoupons(@LoginUser User user) {
        Long userId = user.getId();
        List<CouponIssuance> myCouponList = couponService.getMyCoupons(userId);
        return new CouponResponse<>("내 쿠폰 목록 조회에 성공했습니다.", myCouponList);
    }

 

ArgumentResolver 동작 과정

  • RequestMappingHandlerAdapter가 등록된 ArgumentResolver를 순서대로 순회하면서 supportsParameter(methodParameter)를 물어보고 true를 반환하는 최초의 HandlerMethodArgumentResolver를 선택해 resolveArgument를 호출한다.
  • 만약 @RequestBody 같이 JSON을 자바 객체로 변환해야 하는 경우에 resolveArgument 내부에서 HttpMessageConverter를 HTTP 요청 바디(JSON 등)를 자바 객체로 역직렬화하여 반환해준다.
  • 모든 파라미터가 준비되면 컨트롤러를 호출한다.

'Spring' 카테고리의 다른 글

필터(Filter) vs 인터셉터(Interceptor)  (0) 2025.05.04
Comments