스프링 MVC 강의를 듣던 중 @SessionAttribute에 대해 알게 됐다. 김영한 강사님은 이 기능을 소개하며 한 가지 특징을 명확히 짚어주셨다. "이 어노테이션은 세션을 새로 생성하지 않는다."
단순히 편의성만 생각했다면 생성 기능까지 있어도 좋았을 텐데, 왜 스프링은 이 기능을 굳이 빼놓았을까? 강의를 들으며 바로 꽂혔던 이 '제약 사항' 뒤에 숨겨진 이유와 설계 의도를 정리해 보려 한다.
@SessionAttribute는 세션이 존재하지 않으면 null을 반환하거나 예외를 던질 뿐, 결코 세션을 새로 생성하지 않는다.
보통 request.getSession()은 세션이 없으면 새로 만들어주는 옵션(true)이 기본이지만, @SessionAttribute는 내부적으로 request.getSession(false)를 고집한다. 즉, 이 어노테이션을 사용하는 순간 스프링은 "이미 있는 세션에서 가져오되, 없으면 새로 만들지 마"라는 스탠스를 취한다.
처음엔 "생성 기능까지 있으면 더 편하지 않나?"라는 생각이 들 수도 있지만, 조금 더 고민해보니 이건 스프링의 철저한 리소스 관리 전략이었다.
세션을 직접 만들지 못한다는 기술적 제약은 자연스럽게 이 어노테이션을 '조회 전용(Read-Only)' 도구로 정의한다.
이미 생성된 데이터에 접근할 때만 사용 가능하기 때문에, 우리는 이 어노테이션을 보는 것만으로도 "이 메서드는 세션 상태를 변경하지 않고 정보만 참고하겠구나"라는 확신을 가질 수 있다. 기술적 제약이 오히려 코드의 의도를 명확하게 만들어주는 셈이다.
결국 수정이나 삭제 같은 강력한 권한은 HttpSession을 직접 다룰 때만 명시적으로 허용함으로써, 개발자의 실수로 세션 상태가 꼬이는 것을 방지한다.
스프링이 파라미터 한 줄로 이 모든 걸 처리해주는 비결은 ArgumentResolver에 있다.
내부를 들여다보면 SessionAttributeMethodArguementResolver가 우리가 수동으로 하던 getAttribute()와 null 체크, 그리고 타입 캐스팅까지 대신 수행한다. 우리가 반복 작업을 줄이기 위해 커스텀 어노테이션을 만들어 쓰듯, 스프링도 이 리졸버를 통해 "안전하게 세션 값만 꺼내오는 로직"을 표준화해서 제공하고 있다.
강의에서 배운 "세션을 생성하지 않는다"는 짧은 한 문장이 사실은 가독성 그 이상의 안전과 최적화를 담고 있다는 걸 알 수 있었다.
직접 어노테이션을 만들어 쓸 때도 마찬가지인 것 같다. 기능을 무조건 꽉꽉 채워 넣는 것보다, 때로는 정확한 제약을 걸어주는 것이 나중에 더 읽기 좋고 안전한 코드를 만드는 길이라는 것을 배웠다.