스프링/4. 스프링 MVC-2

60. ArgumentResolver 활용

sdafdq 2023. 9. 9. 19:56
public String homeLoginV3Spring(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) {
    if(loginMember == null){
        return "home";
    }
    model.addAttribute("member", loginMember);
    return "loginHome";
}

이런 거 인자가 너무 긴데, 물론 이정도도 편리하긴 하지만,

 

우리가 직접 애노테이션 만들어서 활용할 수는 없을까?

@GetMapping("/")
public String homeLoginV3ArgumentResolver(@Login Member loginMember, Model model) {
    if(loginMember == null){
        return "home";
    }
    model.addAttribute("member", loginMember);
    return "loginHome";
}

이런 식으로. 우리가 직접 커스텀 애노테이션을 만드는 것이다.

 

 

 

 

일단 저 @SessoinAttribute의 애노테이션의 작업은 보면 request에서 세션을 가져와 GetAttribute(SessionConst.LOGIN_MEMBER)로 세션의 저 키에 값이 있다면 가져오는 것이다.

근데 required = false로, 반드시 요구되는 사항이 아니기 때문에 null이 될 수도 있다. 만약 required=true였다면 에러 뿜는다.

 

 

우리가 해야 할 것은 애노테이션을 만들고,

그 애노테이션에 대해 동작해야 할 argumentResolver를 만들어 주는 일이다.

우리가 커스텀 한 것이니 컨트롤러가 알아들을 수 있는 데이터로 만들어 줘야 한다.

 

 

 

 

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

@Target은 애노테이션의 속성? 용도?를 말한다.  필드, 메소드 우리가 지금까지 봐 왔던 것 처럼 여러 종류의 애노테이션을 만들 수 있지만, 지금은 위에 @Login처럼 파라미터 용도로 만들거기 때문에 애노테이션 타입은 파라미터로 했다.

 

@Retention은 직역하면 유지인데, 유지정책은 RUNTIME으로 했다. 다른 것 으로는 CLASS, SOURCE가 있는데 아마 이것은 정적으로 처음 컴파일 해서 올릴 때 까지만 유지하는 그런 건가 보다. 우리는 계속 서비스동안 사용해야 하므로 RUNTIME이다. 

아마 대부분의 애노테이션을 RUNTIME으로 할 것이다.

 

이렇게 @interface 애노테이션명 해서 만들면 애노테이션은 만든거다.

 

이제 저거에 대한 동작을 정의 해야 한다. 또 어떤식으로 이것이 자신의 애노테이션인지, 어댑터 패턴으로 구현해야 한다(ArgumentResolver 인터페이스가 어댑터패턴 형식이다.)

 

 

 

 

public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
        boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());
        return hasLoginAnnotation && hasMemberType;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        HttpSession session = request.getSession(false);
        if(session == null){
            return null;
        }

        Object member = session.getAttribute(SessionConst.LOGIN_MEMBER);
        return member;
    }
}

HandlerMethodArgumentResolver 인터페이스를 상속받는다.

HandlerMethod는 저번에도 말 했듯이 @GetMapping, @PostMapping 등 @RequestMappind에 사용되는 인자? 의 속성? 종류? 인자가 포함하고 있는 하나의 속성? 이다.

 

supportsParmeter는 지원하는지의 여부를 확인하는 것이다.

 

우리가 이제 @Login Member member 이렇게 하면 

@Login이라는 애노테이션에 대해 저 Member라는 것이 받아들일 수 있는것인지 아닌지 판단하는 것이다.

 

일단 먼저, 자신에게 맞는 애노테이션을 가지고 있는지 확인해야 한다.

parameter.hasParameterAnnotation(Login.class)

우리가 정의한 @Login 애노테이션을 사용한 것인지 검사한다.

 

Member.class.isAssignableFrom(parameter.getParameterType())

그 다음 Member.class에 파마리터가 할당할 수 있는 타입인지 검사한다.

@Login Member member, 이 파라미터는 같은 타입이므로 true가 된다.

 

이렇게 검사하는 로직을 짜 봤고, 맞는 파라미터라면 true, 요건을 충족시키지 못한다면 false를 반환하면 된다.

 

 

그 다음 만약 검사를 통과했다면 우리가 해줄 일, 뭐 보통 객체를 반환시켜주는 일 이다.

 

먼저 NativeWebRequest를 HttpServletRequest로 형변환 해준다. 상위타입인가 보다.

 

세션을 꺼내와서(false로), 세션이 없으면 null을 반환하고, 세션이 있다면

세션에서 SessionConst.LOGIN_MEMBER라는 키로 Object에 담아 본다.

만약 세션이 있더라도 SessionConst.LOGIN_MEMBER라는 키에 값을 할당을 안했다면 null이 나올 것이다.

 

이렇게 컨트롤러에서 인자를 받는 작업에 대한 애노테이션을 다 정의 해 주고, 다시 돌아와 보면,

@GetMapping("/")
public String homeLoginV3ArgumentResolver(@Login Member loginMember, Model model) {
    if(loginMember == null){
        return "home";
    }
    model.addAttribute("member", loginMember);
    return "loginHome";
}

loginMember가 null이라면 그냥 바로 home으로 가 버리는 거고, loginMember가 있다면 모델에 넣은 다음에 loginHome 템플릿으로 가는거다.

 

 

 

단 이 전에, 등록을 먼저 해 줘야 한다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

    }
}

addArgumentResolvers 라는 것을 오버라이드 해 주면 된다. WebMvcConfigurer는 스프링 MVC에 대한 여러 설정과 등록을 하도록 도와주는 인터페이스 같다. 빈, 아규먼트리졸버, 등등.. 애노테이션으로 대부분 자동으로 등록되는 게 많지만..

 

저걸 보면 그냥 resolvers에 등록해 주는 거다.

 

그럼 어댑터 패턴으로 다 검사해보고 맞으면 우리가 구현했던 데로 Member 객체를 줄 것이다.

 

 

좀 헷갈릴 수도 있는데, 

@GetMapping("/")
public String homeLoginV3ArgumentResolver(@Login Member loginMember, Model model) {
    if(loginMember == null){
        return "home";
    }
    model.addAttribute("member", loginMember);
    return "loginHome";
}

이 부분의 @Login Member는 세션에 있냐 없냐로 세션에서 가져오는 것이다.