본문 바로가기

개발/Java & Spring

[Spring boot] HandlerMethodArgumentResolver 로 메소드 파라미터에 대한 공통기능 한번에 수행하기

HandlerMethodArgumentResolver

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/method/support/HandlerMethodArgumentResolver.html

Strategy interface for resolving method parameters into argument values in the context of a given request.

컨트롤러에 들어오는 파라미터를 커스터마이징할 때 사용할 수 있는 인터페이스로, 공통적으로 수행해야 하는 작업을 수행한 후 해당 Object를 반환함으로써 코드의 중복을 줄일 수 있다. 클라이언트의 요청이 담긴 파라미터를 컨트롤러보다 먼저 받아서 작업을 수행한다.

Instance Method Description
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,WebDataBinderFactory binderFactory) 공통작업 수행 후 리턴
boolean supportsParameter(MethodParameter parameter) parameter가 해당 resolver에 의해 수행될 수 있는 타입인지 true/false로 리턴. 이 메소드가 먼저 수행되고 true일 시 resolveArgument를 수행한다.

예제

  1. User 타입의 파라미터를 통해 사용자가 접속한다.

  2. User 아이디는 a, b, c 셋 중 하나로 시작한다.

  3. a 로 시작하면 관리자, b로 시작하면 vip회원, c로 시작하면 일반회원이다.

  4. 각 사용자별로 페이지에 접속할 수 있는 권한이 차등지급된다.

  5. 본인이 접속할 수 없는 페이지면 상태코드 403(FORBIDDEN)을 리턴한다.

 

1) User 객체생성

package com.edu.tistory.model;

import lombok.Getter;
import lombok.Setter;

@Getter
public class User {
    private String userId;
    private String userPassword;
    @Setter
    private UserType userType;

    public enum UserType {
        Manager, VIPMember, Member
    }
}

 

2) LoginUser 어노테이션 생성

package com.edu.tistory.custom;
​
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginUser {
​
}
​

 

3) HandlerMethodArgumentResolver 구현

package com.edu.tistory.custom;
​
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 com.edu.tistory.model.User;
import com.edu.tistory.model.User.UserType;
​
public class LoginUserResolver implements HandlerMethodArgumentResolver{
​
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
		//parameter가 User Type인지 체크 
        return parameter.getParameterType().isAssignableFrom(User.class)
        && parameter.hasParameterAnnotation(LoginUser.class);
    }
​
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        User user = new User();

		//User의 id가 시작하는 값에 따라 UserType(Manager, VIPMemeber, Member)차등부여하고 리턴
        String userId= webRequest.getParameter("userId");
        if(userId.charAt(0) == 'a') user.setUserType(UserType.Manager);
        else if(userId.charAt(0)=='b') user.setUserType(UserType.VIPMember);
        else user.setUserType(UserType.Member);

        return user;
    }
​
}
​

4) Controller 구현

package com.edu.tistory.controller;
​
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
​
import com.edu.tistory.custom.LoginUser;
import com.edu.tistory.model.User;
import com.edu.tistory.model.User.UserType;
​
@RestController
public class LoginController {
​

    @GetMapping("/login/manager")
    public ResponseEntity<String> pageForManager(@LoginUser User user) {
        // Page for manager
        return getResponseEntity(user, UserType.Manager);
    }

    @GetMapping("/login/vip")
    public ResponseEntity<String> pageForVIPMember(@LoginUser User user) {
        // Page for vip
        return getResponseEntity(user, UserType.VIPMember);
    }

    @GetMapping("/login/member")
    public ResponseEntity<String> pageForMember(@LoginUser User user) {
        // Page for member
        return getResponseEntity(user,UserType.Member);
    }

    public ResponseEntity<String> getResponseEntity(User user, UserType userType) {
        if(user.getUserType()!=userType)
            return new ResponseEntity<>(HttpStatus.FORBIDDEN);
        return new ResponseEntity<String>(HttpStatus.OK);
    }

}
​

 

5) Config 작성

package com.edu.tistory.config;
​
import java.util.List;
​
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
​
import com.edu.tistory.custom.LoginUserResolver;
​
@Configuration
public class WebConfig implements WebMvcConfigurer{

    @Bean
    public LoginUserResolver loginUserResolver() {
        return new LoginUserResolver();
    }
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(loginUserResolver());
        WebMvcConfigurer.super.addArgumentResolvers(resolvers);
    }
}
​

위와 같이 작성 후 userId 에 차이를 주고 url에 접근하면 적절히 resolver가 수행되는 것을 알 수 있다.

소스코드: https://github.com/csbsjy/spring-study-example/tree/master/spring-handler-method-argument-resolver

 

 

헷갈리는 아래 개념 정리는 다음 ㅇ ㅣ 시간에 ,,

Interceptor vs HandlerMethodArgument vs Model

 

  • 내멋으로 2019.12.25 11:24 댓글주소 수정/삭제 댓글쓰기

    좋은 포스트 감사합니다

    한가지 궁금하여 여쭙습니다
    @LoginUser Annotaion을 생성하고 사용하는 것은 알겠습니다만
    어떻게 적용이되는지 궁금합니다

    감사합니다

    • 좋은댓글 감사합니다 덕분데 놓쳤던부분 수정합니다:)
      @Override public boolean supportsParameter(MethodParameter parameter) { //parameter가 User Type인지 체크 return parameter.getParameterType().isAssignableFrom(User.class); }​

      User Class 체크 부분에 parameter.hasParameterAnnotation(LoginUser.class) 를 추가하여 어노테이션을 체크하는 방법이 있습니다:)

      해당 부분은 수정하도록 하겠습니다 감사합니다~