Spring

Spring - ArgumentResolver 활용

녁이 2023. 9. 26. 17:31
728x90
반응형

우리가 파라미터로 넘기거나 받는 Argument들을 스프링이 어떻게 알아채고 처리하지? 라는 고민을 한 사람들이 있을 수 있다.

이를 해결해주는 해결사가 바로 스프링의 "ArgumentResolver"이다.

이 argumentResolver는 스프링의 뒤편에서 많은 일들을 해준다.

이의 기능을 활용해서 기존에 작성했던 HomeController를 수정해보도록 하자.


기존 HomeController

@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";
}

세션이 있는지,없는지 확인하는 LoginService단에서 확인을 하고, 있으면 해당 세션을 반환, 없으면 null을 반환하기 때문에, loginMember의 유무로 LoginHome으로 보낼지, Home으로 보낼지 결정한다.

여기서는, @SessionAttribute()를 사용하였다.

[SessionConst.LOGIN_MEMBER 는 loginMember로 자주 사용할 상수라 따로 생성해주었다.]

 


ArgumentResolver를 활용한 HomeController

    @GetMapping("/")
    public String homeLoginV3ArgumentResolver(@Login Member loginMember, Model model) {

        //세션에 회원 데이터가 없으면 home
        if (loginMember == null) {
            return "home";
        }

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

여기서 위와의 차이는 어노테이션의 차이가 있다.

@SessionAttribute → @Login 으로 변경

@Login이라는 어노테이션이 있나? 라고 생각할 수 있지만, 이것은 내가 ArgumentResolver를 활용해 만든 어노테이션이다.

이를 사용하려면 @Login 뒤에 꼭 Member 클래스 타입이 와야 한다.

코드를 보면 이해가 갈 것이다.


@Login

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

어노테이션으로 Login을 만들어준 뒤,

 

@Target(ElementType.PARAMETER) 로 추가해줘야 한다.

→ 우리는 파라미터로 @Login을 사용할 것이기 때문이다.

또, @Retention(RetentionPolicy.RUNTIME) 을 추가해준다.

  리플렉션 등을 활용할 수 있도록 런타임까지 애노테이션 정보가 남아있게 된다.

 

이를 사용하는 위치가 적절한지, 또 @SessionAttribute가 해줬던, 세션이 있으면 해당 세션을 반환하고, 없으면 null을 반환할 수 있도록 하는 기능을 작성해줘야 한다.

이를 LoginMemberArgumentResolver를 만들어서 작성해보자.

LoginMemberArgumentResolver

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

        log.info("supportsParameter 실행");

        //파라미터로 @Login이 쓰였는지
        boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);

        //파라미터값이 Member타입인지
        boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());

        return hasLoginAnnotation && hasMemberType; //둘다 만족하면 true로 resolveArgument가 실행됨.
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, 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);
    }
}

우선, HandlerMethodArgumentResolver를 implements해야 한다.

이를 가져오면, 무조건 오버라이딩해야하는 함수들이 있는데, 그게 supportsParameter, resolveArgument이다.

메서드의 이름을 보면 알 수 있듯이, supportsParameter는 해당 파라미터가 지원을 하는지, 사용을 해도 적절한지 확인을 하는 메서드이다. 여기서 리턴이 false가 나오면 @Login은 무용지물이 된다.

resolveArgument 메서드는 컨트롤러 호출 직전에 호출 되어서 필요한 파라미터 정보를 생성해준다.

앞서 말한 대로, 세션이 있으면 해당 세션을 가져와 리턴해주고, 세션이 없으면 null을 리턴해주는 로직을 처리한다.

 


WebConfig

앞선 글에 나온 Filter나 스프링 인터셉터를 만들면 해당 필터나 인터셉터를 Config 파일에 넣어주는 과정을 본 적 있을 것이다.

위의 @Login을 사용하기 위해서도 마찬가지이다.

Web에 관련된 설정 파일들을 넣어놓은 WebConfig에다가 위에서 만든 argumentResolver를 추가해보자.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new LoginMemberArgumentResolver());
    }

addArgumentResolvers를 오버라이딩하여 추가해주었다.

@SessionAttribute를 사용할 때와 결과는 똑같지만, 좀 더 접근하기 편리해졌다.

이렇게 ArgumentResolver 를 활용하면 공통 작업이 필요할 때 컨트롤러를 더욱 편리하게 사용할 수 있다.

 

728x90
반응형