일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 프로그래머스
- java
- MySQL
- 카카오코테
- 기술면접
- doitandroid
- groupby
- 정처기
- join
- select
- 혼공단
- 안드로이드
- SQL
- 혼공파
- Android
- 오블완
- 안드로이드스튜디오
- 자료구조
- 티스토리챌린지
- 인프런
- 스터디
- Kotlin
- 알고리즘
- 코테
- 정보처리기사
- Til
- 코틀린
- CS
- 혼공챌린지
- 자바
- Today
- Total
Welcome! Everything is fine.
[Spring] Spring에서 세션을 다루는 3가지 방법, 뭐가 제일 좋을까? 본문
튜터님의 해설 강의를 듣고 일정 관리 앱 과제를 리뷰하다가 알게 된 내용을 간단하게 포스팅 해본다. 이번 일정 관리 앱 과제에서 튜터님의 코드와 비교했을때, 가장 큰 차이 중 하나는 세션을 사용하는 방식이었다. 나는 HttpSession을 직접 가져와 사용하는 방식을 사용했다. 반면 튜터님은 @SessionAttribute 어노테이션을 사용하여 세션 속성을 자동 주입하는 방식을 사용하셨고, 그 방법이 더 쉽게 세션을 사용할 수 있는 것 같다.
📌 세션을 사용하는 방식
HttpSession 사용하기
내가 작성한 댓글 생성 기능 부분이다. HttpServletRequest에서 HttpSession을 가져와 세션키를 사용하고 있다. 이 방법은 Servlet API에서 제공하는 기본적인 세션 관리 방식으로, 직접 HttpSession 객체를 사용하여 값을 저장하고 가져온다.
@PostMapping("/schedules/{scheduleId}/comments")
public ResponseEntity<CommentResponseDto> save(
@PathVariable Long scheduleId,
@Valid @RequestBody CommentRequestDto dto,
HttpServletRequest request
) {
HttpSession session = request.getSession(false);
String sessionKey = (String) session.getAttribute("sessionKey");
CommentResponseDto commentResponseDto = commentService.save(scheduleId, sessionKey, dto);
return new ResponseEntity<>(commentResponseDto, HttpStatus.CREATED);
}
이 경우 한 눈에 보기에도 코드가 길어 간결해보이지 않는다는 단점이 있고, 만약 세션이 없을 경우 NullpointerException이 발생할 가능성이 있다. 또한 session.getAttribute()를 호출할 때 형 변환이 필요하다.
실제로 코드를 짜면서 아래 코드가 굉장히 많이 반복되어서 찝찝했는데 시간이 없어서 그냥 제출했다. 리뷰를 하면서 해당 코드가 있는 부분을 고치는데 너무 많아서 힘들었다.
HttpSession session = request.getSession(false);
String sessionKey = (String) session.getAttribute("sessionKey");
@SessionAttribute 사용하기
위 코드는 @SesstionAttribute 어노테이션을 사용해 더 간결하게 바꿀 수 있다. @SessionAttribute는 특정 값을 컨트롤러 메서드에서 세션 속성으로 직접 매핑한다. 자동으로 세션값을 주입해 주기 때문에 불필요한 코드가 사라진다.
@PostMapping("/schedules/{scheduleId}/comments")
public ResponseEntity<CommentResponseDto> save(
@SessionAttribute(name = "LOGIN_USER") Long userId,
@PathVariable Long scheduleId,
@RequestBody CommentSaveRequestDto dto
) {
return ResponseEntity.ok(commentService.save(userId, scheduleId, dto));
}
이 경우에는 첫 번째 방법보다 코드도 짧고 유지보수하기 쉽다. 세션을 가져오고, 속성을 읽은 후 형 변환까지 해야하는 과정 없이 바로 세션값을 사용할 수 있다.
HandlerMethodArgumentResolver 사용하기
세션을 사용하는 방법이 더 있는지 찾아보다가 발견한 방식이다. HandlerMethodArgumentResolver를 구현하여, 특정 세션 값을 자동으로 컨트롤러 매개변수로 주입하는 방식이다. 컨트롤러에서 매번 @SessionAttribute나 HttpSession을 호출할 필요 없이, 자동으로 특정 객체를 주입받을 수 있도록 설정할 수 있다. 세션을 가져오는 로직을 전역적으로 한 곳에서 관리할 수 있기 때문에 더 깔끔하고 유지보수가 쉽다. 사용하는 방법은 다음과 같다.
1) @LoginUser 어노테이션 생성
먼저 LoginUser라는 커스텀 어노테이션을 만든다.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
- @Target(ElementType.PARAMETER) : 메서드의 파라미터에만 사용할 수 있도록 제한
- @Retention(RetentionPolicy.RUNTIME) : 런타임에도 어노테이션이 유지되어 ArgumentResolver에서 인식할 수 있도록 설정
- RetentionPolicy.SOURCE: 소스 코드에서만 유지
- RetentionPolicy.CLASS: 클래스 파일에만 유지
- RetentionPolicy.RUNTIME: 런타임 시에도 유지
2) HandlerMethodArgumentResolver 구현
@LoginUser를 사용하면 자동으로 세션에서 값을 가져오도록 HandlerMethodArgumentResolver를 구현한다.
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
// @LoginUser 애노테이션이 있고, Long 타입일 때만 적용
return parameter.getParameterAnnotation(LoginUser.class) != null
&& parameter.getParameterType().equals(Long.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
// 요청 객체 및 세션 가져오기
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
HttpSession session = request.getSession(false);
if (session == null) {
throw new RuntimeException("세션이 존재하지 않습니다.");
}
return userId;
}
}
3) WebConfig에 등록
addArgumentResolvers()에서 위에서 만든 LoginUserArgumentResolver를 SPring의 ArgumentResolver 목록에 추가한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final LoginUserArgumentResolver loginUserArgumentResolver;
public WebConfig(LoginUserArgumentResolver loginUserArgumentResolver) {
this.loginUserArgumentResolver = loginUserArgumentResolver;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginUserArgumentResolver);
}
}
이렇게 하면 컨트롤러에서 @LoginUser 어노테이션을 사용할 수 있다.
4) 컨트롤러에서 @LoginUser 사용
이제 다음과 같이 @LoginUser만 붙이면 세션값을 가져올 수 있다. 아마 제일 간결한 방식이지 않을까 싶다.
@PostMapping("/schedules/{scheduleId}/comments")
public ResponseEntity<CommentResponseDto> save(
@LoginUser Long userId, // 세션에서 자동으로 userId를 주입
@PathVariable Long scheduleId,
@RequestBody CommentSaveRequestDto dto
) {
return ResponseEntity.ok(commentService.save(userId, scheduleId, dto));
}
그 외...
- Spring Security : 세션 대신 SecurityContext에 사용자 정보 저장 가능
- Spring Session : 서버 메모리가 아닌 Redis, JDBC, Hazelcast 같은 저장소에 세션 저장 가능
- 실무에서는 Spring Session + Redis를 많이 사용한다고 한다.(GPT가 한 말이라 100% 신뢰할 순 없지만...)
실무에서 세션을 사용하는 방식은 나중에 더 많은 것을 배우고 나서 다시 공부해보면 좋을 것 같다.
'TIL' 카테고리의 다른 글
[Spring] JWT 시크릿 키 설정 및 문제 해결 / -hex 64 vs -base64 32 차이 (0) | 2025.02.27 |
---|---|
[Spring] @EntityGraph로 N+1문제 해결하기 (0) | 2025.02.26 |
[Spring] Converter와 Fomatter 사용하기 (0) | 2025.02.21 |
[Git] 로컬 브랜치에서 pull 할 때 에러 발생 - stash로 임시 커밋하기 (0) | 2025.02.18 |
[Spring] 왜 엔티티에 Setter를 사용하면 안 될까? (0) | 2025.02.13 |