Updated:

1. 어노테이션 클래스 생성

컨트롤러 각 url 마다 세션값을 필요로 한다면, 세션을 불러오는 코드가 계속 중복해서 발생할 수 밖에 없다.

SessionUser user = (SessionUser) httpSession.getAttribute("user");

이를 해결할 수 있는 방법은 어노테이션 기반으로 세션처리를 하면 된다!
우선 어노테이션 클래스를 만들어보자

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

@Target(ElementType.PARAMETER)

  • 사용자가 만든 어노테이션을 부착할 수 있는 위치를 지정한다
  • ElementType.PARAMETER 는 메소드 파라미터 위치를 말한다
  • 그 외에도 다양한 타입이 존재 (ElementType.PACKAGE, TYPE, CONSTRUCTOR 등등)

@Retention(RetentionPolicy.RUNTIME)

  • 어느 시점까지 어노테이션의 메모리를 가져갈 지 설정한다
  • RetentionPolicy.RUNTIME 은 말그대로 런타임 종료까지 메모리에 살아있게 한다
  • 그 외에도 RetentionPolicy.SOURCE, CLASS 타입이 존재
  • SOURCE 는 주석처럼 사용하고 컴파일할 때 메모리에서 버린다고 하며, CLASS는 컴파일까지는 유지되나 런타임 시 버려진다
  • CLASS의 필요성이 모호하나, .class 파일로 관리되는 라이브러리에 메타정보를 기록하고 싶을 때 사용하면 좋다

2. HandlerMethodArgumentResolver 인터페이스 구현체 생성

HandlerMethodArgumentResolver 인터페이스 구현체를 만든다.

import com.fourtwod.config.auth.dto.SessionUser;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpSession;

@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

    private final HttpSession httpSession;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
        boolean isUserClass = SessionUser.class.equals(parameter.getParameterType());

        return isLoginUserAnnotation && isUserClass;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return httpSession.getAttribute("user");
    }
}

supportsParameter()

  • 메서드의 특정 파라미터 지원 여부를 체크한다
  • 어노테이션 클래스가 파라미터로 사용되면 사용자가 만든 어노테이션 클래스, 파라미터 타입인지 검사하도록 했다

resolveArgument()

  • 가져올 객체를 리턴한다
  • 세션 값을 리턴하도록 했다

3. HandlerMethodArgumentResolver 구현체, WebMvcConfigurer 등록

만들어 놓은 HandlerMethodArgumentResolver 구현체를 WebMvcConfigurer 에 등록하여 스프링에서 인식되도록 한다.

import com.fourtwod.config.auth.LoginUserArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final LoginUserArgumentResolver loginUserArgumentResolver;

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

4. 적용 예제

이제 컨트롤러의 메소드 파라미터에 어노테이션을 추가하여, 자동으로 원하는 객체를 불러오도록 하자

@GetMapping("")
@ApiOperation(value = "유저 정보 조회", response = ApiResult.class)
public ApiResult selectUserInfo(@ApiIgnore @LoginUser SessionUser user) throws Exception {
  if (user != null) {
    ApiResult apiResult = userService.selectUserInfo(user);
    return apiResult;
  } else {
    throw new Exception();
  }
}

5. 테스트 코드 작성

마지막으로
이렇게 작성된 코드를 테스트할때는 어떻게 하면 좋을지도 고민해봐야 한다.
런타임시에는 로그인 후 세션에 값이 있으므로 자동으로 불러오겠지만
테스트 코드에서 MockMvc 로 api를 호출할 때는 세션값이 없어서 오류가 발생하게 된다.

이럴때는 MockHttpSession 을 사용하여 세션을 주입한 후 호출하면된다.

@Test
@Transactional
public void user_유저정보조회() throws Exception {
	// HttpSession 세팅
	MockHttpSession session = new MockHttpSession();
	session.setAttribute("user", SessionUser.builder()
			.email(email)
			.registrationId(registrationId)
			.build());

	mvc.perform(get("/api/user")
			.session(session))
			.andExpect(status().isOk());
}

Leave a comment