지난 포스팅에서 ArgumentResolver의 개념을 다루어보았다.
Controller는 비즈니스 로직을 처리하는 클래스로, 필요한 데이터를 파라미터로 요구한다. Controller(Handler)를 실행하는 주체는 HandlerAdapter인데, HandlerAdapter는 파라미터에 맞는 데이터를 넘겨야 한다. 그러므로 다양한 파라미터에 대응할 수 있는 모듈이 필요한데, 그것이 ArgumentResolver이다.
핸들러 어댑터는 컨트롤러 메소드의 파라미터 객체를 생성할 수 있는 ArgumentResolver 구현체를 탐색한다. @ModelAttribute, @RequestBody, HttpEntity.. 등등 다양한 파라미터에 대응할 수 있는 ArgumentResolver 구현체가 이미 Spring에 구현되어 있다.
이번 포스팅에서는 ArgumentResolver 구현체를 직접 만들어, Spring이 지원하지 않는 파라미터에 객체를 넘기어 보겠다.
ArgumentResolver 활용하기
@Login
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}
Controller
@GetMapping("/")
public String login(@Login Member loginMember, Model model){
// [ 비즈니스 로직 ]
}
@Login 어노테이션을 새로 만들었다. 그리고 Controller의 login 메소드 파라미터에 @Login 어노테이션을 선언하였고 파라미터로 Member 클래스를 정의하였다. @Login 어노테이션은 새로 만든 어노테이션이므로, Spring이 제공하는 ArgumentResolver 구현체로는 Member 객체를 생성하여 파라미터로 넘기지 못한다.
그러므로 @Login 어노테이션이 선언되어 있을때 Member 객체를 넘겨 줄 수 있는 ArgumentResolver 구현체를 직접 만들어야 한다.
LoginMemberArgumentResolver
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 1) @Login 어노테이션이 선언되어 있는가?
boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
// 2) 파라미터가 Member 클래스인가?
boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());
// 1)과 2)가 TRUE이면 LoginMemberArgumentResolver 동작
return hasLoginAnnotation && hasMemberType;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// HttpServletRequest 객체로 형변환
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
// 세션 조회하기
HttpSession session = request.getSession(false);
// 세션이 없는 경우
if(session == null){
return null;
}
// 세션에서 회원정보 추출하여 반환하기 ( Member 객체 )
return session.getAttribute(SessionConst.LOGIN_MEMBER);
}
}
LoginMemberArgumentResolver를 직접 만들어 보았다.
LoginMemberArgumentResolver는 HandlerMethodArgumentResolver 인터페이스의 구현체이다. HandlerMethodArgumentResolver는 @RequestMapping이 선언된 컨트롤러를 지원하는 ArgumentResolver이다. ArgumentResolver는 두 가지 메소드를 구현하면 된다.
1) supportsParameter 메소드
2) resolveArgument 메소드
핸들러 어댑터는 파라미터에 객체를 전달할 수 있는 ArgumentResolver를 탐색하는데, ArgumentResolver의 supportsParameter 메소드를 호출하여 적합 여부를 판단한다.
그럼 위 코드를 보자.
- supportsParameter 메소드
파라미터가 @Login 어노테이션을 가지고 있고(hasParameterAnnotation)
파라미터에 Member 클래스가 할당될 수 있다면(isAssignableFrom)
메소드는 true를 반환한다. 핸들러 어댑터에게 자신이 파라미터에 적합한 객체를 생성하여 반환할 수 있음을 알리는 것이다. 그럼 핸들러 어댑터는 resolveAgument 메소드를 호출한다.
- resolveArgument 메소드
resolveArgument 메소드는 실제 파라미터에 넘길 객체를 생성하는 메소드이다. 메소드의 파라미터를 보자.
MethodParameter parameter
ModelAndViewContainer mavContainer
NativeWebRequest webRequest
WebDataBinderFactory binderFactory
파라미터 데이터부터 웹 관련 데이터까지, 웹 환경에서 데이터를 추출하여 컨트롤러 파라미터에 넘길 객체를 만들 수 있도록 환경을 제공하고 있다. 우리는 그럼 HttpServletRequest 객체에서 세션 객체를 가져와보자. 세션객체에 key값을 넣어 회원정보(Member)를 추출한다. 그렇게 추출된 Member 객체를 핸들러 어댑터에 반환하면 핸들러 어댑터는 컨트롤러 메소드의 파라미터로 전달한다.
Controller
@GetMapping("/")
public String login(@Login Member loginMember, Model model){
// [ 비즈니스 로직 ]
}
그럼 Controller의 loginMember 객체에는 로그인 요청을 보내온 회원의 정보가 들어 있다. 정보를 토대로 로그인 비즈니스 로직을 처리하면 된다.
그럼 직접 만든 ArgumentResolver를 Spring이 사용할 수 있도록 등록해보자.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginMemberArgumentResolver()); // 등록!
}
}
WebMvcConfigurer 인터페이스는 Spring이 제공하는 각종 인터페이스에 커스터 마이징된 구현체들을 쉽게 등록할 수 있도록 기능을 제공하고 있다. WebMvcConfigurer 인터페이스의 addArgumentResolvers 메소드는 ViewReselover를 등록하는 기능을 제공하는 메소드이다. 위 코드처럼 간단히 등록할 수 있다.
ArgumentResolver 등록을 완료하면, 다음과 같은 자동화가 이루어진다.
컨트롤러 메소드에 @Login과 Member 클래스로 구성된 파라미터가 있으면 Spring이 자동으로 세션에서 회원정보를 추출하여 파라미터로 넘겨준다. 그럼 다음부터 개발할때는 간단히 @Login으로 회원정보를 요청만하면 된다. 이렇게 ArgumentResolver 구현체를 직접 만들어 등록하면 Controller 메소드를 단순화시킬 수 있다.
참고자료
'SPRING > Spring MVC' 카테고리의 다른 글
[SpringMVC] 예외처리 ( 필터, 인터셉터 ) (0) | 2023.09.01 |
---|---|
[SpringMVC] 오류페이지 띄우기 (0) | 2023.09.01 |
[SpringMVC] 스프링 인터셉터(Interceptor)란? (0) | 2023.08.31 |
[SpringMVC] 서블릿 필터(Filter)란? (0) | 2023.08.31 |
[SpringMVC] 서블릿 세션(Servlet Session) 이용하기 (0) | 2023.08.30 |