본문 바로가기
Spring(boot)

ArgumentResolver 활용

by 글발 2024. 9. 10.
728x90
반응형

@Login 어노테이션과 ArgumentResolver 구현하기

이번 글에서는 @Login 어노테이션을 만들어 그것을 활용하여 세션에 있는 로그인 회원 정보를 자동으로 찾아주는 기능을 구현하는 방법을 살펴보겠습니다. 이를 통해 컨트롤러 코드에서 세션을 직접 확인하지 않고도 로그인 정보를 사용할 수 있습니다.

@Login 어노테이션이 있으면 직접 만든 ArgumentResolver가 동작해서 자동으로 세션에 있는 로그인 회원을 찾아주고,

만약 세션에 없다면 null을 반환하도록 개발해봅시다.

 

@Login의 쓰임은 아래와 같습니다.

@GetMapping("/")
public String homeLoginV3ArgumentResolver(@Login Member loginMember, Model model) {
	 //세션에 회원 데이터가 없으면 home
     if (loginMember == null) {
        return "home";
     }
 
     //세션이 유지되면 로그인으로 이동
     model.addAttribute("member", loginMember);
     return "loginHome";
}

참고로 위 코드는 아래 코드를 변경한 것입니다.(@Login을 만들고 사용을 위해)

@GetMapping("/")
public String homeLoginV3Spring( @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) {
    //세션에 회원 데이터가 없으면 home
    if (loginMember == null) {
        return "home";
    }

    //세션이 유지되면 로그인으로 이동
    model.addAttribute("member", loginMember);
    return "loginHome";
    }

비교하면 @Login만 적어주면 되니까 간단해졌습니다.

@Login 어노테이션 정의

이제 ArgumentResolver를 직접 만들어 활용하여 @Login 어노테이션을 사용해보겠습니다.

@Login 어노테이션을 정의하는 방법은 아래와 같습니다.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
	
}

 

위 코드는 @Login이라는 어노테이션을 만드는 코드인데 조금만 설명을 하자면

1. @Target(ElementType.PARAMETER):

 - 어노테이션이 적용될 수 있는 위치를 지정합니다.
 - ElementType.PARAMETER는 이 어노테이션이 메서드의 매개변수에만 적용될 수 있음을 의미합니다.

2. @Retention(RetentionPolicy.RUNTIME):
 - 어노테이션이 얼마나 오래 유지될지를 지정합니다.
 - RetentionPolicy.RUNTIME은 이 애너테이션이 런타임 동안 유지되며,

 - 리플렉션을 통해 접근할 수 있음을 의미합니다.
3. public @interface Login:
 - 새로운 애너테이션 타입을 정의합니다.
 - Login이라는 이름의 애너테이션을 정의합니다.

 

이렇게 @Login 어노테이션을 정의했다면 이제 HandlerMethodArgumentResolver을 구현해봅시다.

LoginMemberArgumentResolver 구현

이제 HandlerMethodArgumentResolver 인터페이스를 구현하여 @Login 어노테이션이 붙은 파라미터를 처리하는 LoginMemberArgumentResolver를 만들어보겠습니다.

@Slf4j
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
	
	@Override
	public boolean supportsParameter(MethodParameter parameter) {

		log.info("supportsParameter 실행");

		boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
		boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());

		return hasLoginAnnotation && hasMemberType; // true면 resolveArgument 실행
	}
	
	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		log.info("resolveArgument 실행");
		
		HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
		HttpSession session = request.getSession(false);
		if (session == null) {
			return null;
		}

		return session.getAttribute(SessionConst.LOGIN_MEMBER);
	}
}

 

  • supportsParameter(): 파라미터에 @Login 어노테이션이 적용되었고, 파라미터 타입이 Member라면 true를 반환하여 resolveArgument() 메서드를 실행.
  • resolveArgument(): 세션에 있는 로그인 정보를 찾아서 반환. 세션이 없거나 회원 정보가 없으면 null 반환.

 

***제가 처음에 헷갈렸던 부분부터 말하면 

boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());

이 부분인데

처음에는 parameter.getParameterType()이 Member를 어떻게 가져올까? 의문이었습니다.

왜냐면 컨트롤러의 파라미터 갯수는 여러개이고

@Login 뒤에 붙은 파라미터가 Member가 아닐수도 있고,

Member는 뒤에 또 다른 파라미터 일 수도 있겠다고 생각했습니다. 그러면 true가 나오면 안되는데 말입니다.

제가 간과한 부분은 HandlerMethodArgumentResolver의 실행 횟수입니다.

결론부터 말하면 HandlerMethodArgumentResolver는 컨트롤러 파라미터 갯수 만큼 호출됩니다.

HandlerMethodArgumentResolver는 컨트롤러의 파라미터, 어노테이션 정보를 기반으로 전달 데이터를 생성하는데

한번에 생성하는 것이 아니라 파라미터 하나씩 생성하는 것입니다.

따라서 위 코드는 @Login Member member만을 위한 HandlerMethodArgumentResolver인 것입니다.

 

다시 돌아와서

ArgumentResolver가 호출되면 supportsParameter()를 호출해서 해당 파라미터를 지원하는지 체크하고,

지원하면 resolveArgument()를 호출해서 실제 파라미터 객체를 생성합니다.

이후 스프링MVC는 컨트롤러의 메서드를 호출하면서 여기에서 반환된 member 객체를 파라미터에 전달해줍니다.

 

이제 HandlerMethodArgumentResolver를 구현해줬으면 사용될 수 있게 등록을 해주어야 합니다.

ArgumentResolver 등록

구현한 LoginMemberArgumentResolver를 Spring MVC에서 사용할 수 있도록 설정 파일에 등록해줍니다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
		resolvers.add(new LoginMemberArgumentResolver());
	}
}

@Configuration 어노테이션을 사용해 WebConfig 클래스를 설정 파일로 등록하고,

WebMvcConfigurer 인터페이스의 addArgumentResolvers() 메서드를 오버라이드하여 직접 구현한 LoginMemberArgumentResolver를 추가합니다.

 

결론

이렇게 하면 @Login 어노테이션만 사용해도 세션에서 로그인 회원 정보를 쉽게 가져올 수 있으며, 컨트롤러 코드가 훨씬 간단해집니다. ArgumentResolver를 통해 컨트롤러의 파라미터를 자동으로 처리하고, 필요한 경우 커스텀 ArgumentResolver를 직접 구현하여 확장할 수도 있습니다.

 

출처: 인프런, 김영한-스프링 MVC 2편