티스토리 뷰
스프링에서 사용자의 요청은 필터를 거쳐 DispatcherServlet으로 넘어가고, 내부의 doDispatch() 메서드를 통해 해당 요청에 대한
인터셉터들의 preHandle()이 처리된 후 컨트롤러로 넘어간다.
나의 경우 인터셉터에서 인증 관련 처리를 하기 위해 Authentication 헤더를 읽어 인증과 인가를 수행한다. 이후 ArgumentResolver 혹은 컨트롤러에서 다시 사용자의 이메일 혹은 아이디를 통한 처리를 진행한다.
이때 인터셉터에서 이미 암호화된 Authentication 헤더의 값을 해석하고 파싱 했음에도 값을 캐싱하지 않는다면 이후의 ArgumentResolver에서 다시 해석과 파싱 작업을 해야 한다.
이런 불편을 없애기 위해 컨트롤러 메서드 앞에서 요청이 처리될 때 값을 캐싱해 넘겨줄 수 있는 방법들을 알아보았다.
1. ThreadLocal
요청에 대한 처리가 하나의 쓰레드가 이루어진다면 ThreadLocal을 충분히 이용할 수 있다.
계정 저장을 위한 객체를 빈으로 등록한 뒤
아래와 같이 인터셉터의 preHandle()에서 ThreadLocal에 저장하고,
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 로그인 처리 후 계정 정보가 담긴 authInfo 객체를 만든다
AuthInfo authInfo = new AuthInfo(email, pw);
. . .
ThreadLocal<AuthInfo> local = new ThreadLocal<>();
local.set(authInfo);
}
ArgumentResolver 혹은 Controller 등 필요한 위치에서 값을 꺼내올 수 있다.
주의할 점은 요청이 처리되고 난 뒤 쓰레드 풀에 쓰레드가 반납되므로, 해당 ThreadLocal에 저장한 값을 제거해야 한다.
사용 시점에 바로 제거하는 편이 간단하다.
public class CustomArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
private ThreadLocal<AuthInfo> local;
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory
) {
local.get();
local.clear();
. . .
}
}
ThreadLocal을 비워줘야 한다는 점이 약간 불편하며,
WebFlux를 사용하여 비동기식으로 요청이 처리되는 상황에서는 동일 쓰레드에서의 처리가 보장되지 않으므로 InheritableThreadLocal을 사용해야 한다.
2. HttpServletRequest에 값 저장하기
HttpServletRequest에 setAttribute()를 통해 값을 바로 저장할 수 있다.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
. . .
request.setAttribute("email", email);
request.setAttribute("password", password);
return true;
}
HttpServletRequest 객체는 요청 별로 하나가 생성되며 attribute를 저장 시 ConcurrentHashMap에 key - value 형태로 저장된다.
사용 위치에서 request를 getAttribute(key_name)로 가져오기만 하면 된다.
ThreadLocal과 같이 사용 후 제거할 필요 없이 요청이 처리되면 스프링에 의해 삭제된다.
3. @RequestScope 사용하기
Spring Core에서 빈이 기본적으로 싱글톤의 생명 주기를 갖게 하는 것과 달리, Spring MVC에선 HttpServletRequest와 마찬가지로 Request가 들어와 처리되는 동안으로 생명주기를 지정할 수도 있다.
@Component
@RequestScope
public class AuthInfo {
. . .
}
위와 같이 요청이 처리될 동안으로 빈의 생명주기를 한정시키고, ArgumentResolver에서 AuthInfo를 주입받아 바로 사용할 수 있다.
마찬가지로 생명주기를 스프링이 알아서 관리하므로 편리하다. 빈으로 등록하고 필요한 위치에서 가져오기만 하면 된다.
추가로, 필터나 인터셉터에서 로깅 등을 위해 아래와 같이 요청을 읽어버리면 요청 객체의 바디 정보가 컨트롤러로 넘어오기 전에 사라진다.
이를 방지하기 위해서는 요청 혹은 응답을 캐싱할 수 있는 ContentCachingRequestWrapper를 사용해야 한다.
BufferedReader br = request.getReader();
logger.log(br.readLine());
// 요청 바디를 읽어버려 내용이 사라진 채로 넘어간다.
'Web > Spring' 카테고리의 다른 글
[Spring] ViewResolver의 동작 과정 (0) | 2023.05.28 |
---|---|
logback-spring.xml을 사용해 로그 커스터마이즈하기 (1) | 2023.05.26 |
[Spring] 핸들러(컨트롤러 메서드)는 어떤 우선순위로 선택되는가? (0) | 2023.05.07 |
[Spring] 왜 Random Port의 SpringBootTest에서는 @Transactional 적용이 되지 않을까? (1) | 2023.04.27 |
[Spring] 필드 주입도 순환 참조를 검사해 준다? (0) | 2023.04.19 |
- Total
- Today
- Yesterday
- Spring
- thenComparing
- JPA
- RandomPort
- 스프링
- Spring 테스트
- MySQL
- 생성자 주입
- Jenkins 예약 배포
- MySQL 이벤트 스케줄
- Spring Boot Monitoring
- springboottest
- logback-spring.xml
- stubbing
- 자바
- 우테코 5기
- GitHub Discussion
- 의존성 주입
- 람다식
- GitHub Discussion 템플릿
- invokedynamic
- java switch case
- Payload 암호화
- 함수형 인터페이스
- GitHub Discussion Template
- 우테코 프리코스
- Java
- Fromtail
- JPA JSON
- 우테코
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |