[스프링부트] 커스텀 어노테이션으로 Session 유저 받아오기(HandlerMethodArgumentResolver 사용 예제)

안녕하세요. 오늘은 커스텀 어노테이션을 활용하여 컨트롤러에서 메소드에 선언된 파라미터에서 세션 유저를 받아올 수 있도록 하는 HandlerMethodArgumentResolver 예제를 공유하고자 합니다.

 

우선 이전에 포스팅했었던 커스텀 어노테이션을 만드는 상세한 내용은 아래의 포스팅을 참조해주시기 바랍니다!!

2020/01/13 - [프로그래밍 언어/[Java] Study 내용 ] - [Java] Custom Annotation 커스텀 어노테이션 만들기(reflection 리플렉션)

 

[Java] Custom Annotation 커스텀 어노테이션 만들기(reflection 리플렉션)

자바에서 커스텀 어노테이션을 만드는 방법은 다음과 같다. public @interface MyAnnotation { } 사실상 인터페이스 키워드에 @를 붙이면 되는 것인데. 이것을 곧바로 클래스, 필드, 메서드 같은 곳에 붙이면 어노..

sas-study.tistory.com

 

우선 기존 코드는 다음과 같습니다.

@RequiredArgsConstructor
@Controller
public class IndexController {
	
    private final PostsService postsService;
    private final HttpSession httpSession;
    
    @GetMapping("/")
    public String index(Model model, @LoginUser SessionUser user) {
        model.addAttribute("posts", postsService.findAllDesc());
		
        SessionUser user = (SessionUser) httpSession.getAttribute("user");
        
        if(user != null) {
            model.addAttribute("userName", user.getName());
        }

        return "index";
    }

}

- 세션 유저를 가져와서 처리해야하는 경우가 발생한다면 다분히 Session 에서 getAttribute로 꺼내와야하는 코드가 뻔하게 들어가야하는 상황이었습니다.

 

 

커스텀 어노테이션 예제

일단 로그인 유저를 지칭하는 @LoginUser 어노테이션을 만들어 보겠습니다.

@LoginUser 어노테이션
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}

우선 거의 대부분의 커스텀 어노테이션을 특별한 속성을 두지 않는 한 굉장히 단순한 모습을 띄게 됩니다.

@Target 어노테이션의 값을 ElementType.PARAMETER로 두었기 때문에 메소드의 파라미터로 선언된 타입에서만 쓸 수 있습니다.

 

 

SessionUser 클래스
@Getter
public class SessionUser implements Serializable {
    private String name;
    private String email;
    private String picture;

    public SessionUser(User user) {
        this.name = user.getName();
        this.email = user.getEmail();
        this.picture = user.getPicture();
    }
}

- 평범한 유저 클래스의 일종입니다. 세션에 저장하기 위해 직렬화 처리를 하였습니다.

 

 

LoginUserArgumentResolver 클래스
@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

    private final HttpSession httpSession;

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
    	//@LoginUser 어노테이션이 들어있으면
        boolean isLoginUserAnnotation = methodParameter.getParameterAnnotation(LoginUser.class) != null; 
        //SessionUser 클래스 타입의 파라미터에 @LoginUser 어노테이션이 사용되었는가?
        boolean isUserClass = SessionUser.class.equals(methodParameter.getParameterType()); 
        
        return isLoginUserAnnotation && isUserClass;
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest,
                                  WebDataBinderFactory webDataBinderFactory) throws Exception {

        return httpSession.getAttribute("user");
    }
}

- LoginUserArgumentResolver 클래스는 HandlerMethodArgumentResolver 인터페이스를 구현하는 구현체입니다. 각각의 오버라이딩 메소드인 supportsParameter와 resolveArgument를 구현해줍니다.

 

supportsParameter : 우선 메소드의 파라미터들이 하나씩 methodParameter 에 매핑될 것입니다. 그때 @LoginUser 어노테이션이 발견이 된다면 1단계 통과입니다. 그후 @LoginUser 어노테이션이 붙어있는 메소드 파라미터가 SessionUser 클래스 타입이라면 리턴값으로 true를 던져주게 될 것입니다.

 

resolveArgument : 만일 supportsParameter의 리턴값이 false 로 떨어진다면 해당 메소드는 실행되지 않습니다. true 인경우에만 실행되므로 httpSession에서 "user" 로 저장된 객체를 꺼내오게 됩니다.

 

 

WebConfig 클래스
@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final LoginUserArgumentResolver loginUserArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        // HandlerMethodArgumentResolver 구현체를 여기에서 등록해줘야한다.
        argumentResolvers.add(loginUserArgumentResolver);
    }

}

- HandlerMethodArgumentResolver 구현체를 등록해주는 설정 클래스가 필요합니다. WebMvc 설정을 담당하는 WebMvcConfigurer 인터페이스를 구현하고 그중 argumentResolver를 등록하는 addargumentResolvers() 메소드의 argumentResolver 리스트에 우리가 구현한 LoginUserArgumentResolver 객체를 추가해주는 설정을 해줍니다.

 

 

설정 완료 후 사용하는 모습

@RequiredArgsConstructor
@Controller
public class IndexController {

    private final PostsService postsService;

    @GetMapping("/")
    public String index(Model model, @LoginUser SessionUser user) {
        model.addAttribute("posts", postsService.findAllDesc());

        if(user != null) {
            model.addAttribute("userName", user.getName());
        }

        return "index";
    }
}

컨트롤러가 다음과 같이 좀더 깔끔하게 보일 수 있다. parameter 갯수가 늘어나겠지만 개인적으로 코드는 키가 큰것보다 뚱뚱한게 좋다고 생각한다. 쓸데없는 불필요한 코드에 메소드의 길이가 길어지는 것보단 간단한 설정으로 프로젝트 개발의 효율성을 올려보자!

댓글

Designed by JB FACTORY