AOP(Aspect-Oriented Programming)는 "공통된 관심사"를 핵심 로직에서 분리해서 재사용할 수 있도록 도와주는 관점 지향 프로그래밍이다.
@Before
: 대상 메서드 실행 전에 수행@After
: 대상 메서드 실행 후 수행@Around
: 대상 메서드 실행 전후 모두 제어 가능서비스를 만들다 보면 반복적이고 공통된 로직이 여러 레이어에 흩어져 존재하게 된다. 이런 로직들을 핵심 로직과 분리하지 않으면 코드가 지저분해지고 유지보수가 어렵다. AOP는 이런 문제들을 해결해줄 수 있다. 공통된 로직을 별도의 클래스(Aspect)로 분리해 관심사를 분리시켜줄 수 있고 Controller나 Service는 순수하게 비즈니스 로직에만 집중할 수 있도록 해준다. 그래서 세션 처리, 로깅, 보안, 트랜잭션 관리처럼 여러 계층에서 반복적으로 필요한 기능을 전역적으로 자동화할 수 있다는 점에서 AOP는 매우 유용하다.
@LoginSessionInject
어노테이션
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginSessionInject {}
이 어노테이션이 붙은 메서드는 세션 자동 저장 대상이 된다.
LoginSessionAspect
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LoginSessionAspect {
@Around("@annotation(com.example.fastcoupon.aop.LoginSessionInject)")
public Object injectSession(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attributes.getRequest();
if (result instanceof ResponseEntity<?> responseEntity) {
Object body = responseEntity.getBody();
if (body instanceof UserResponseDto dto) {
request.getSession().setAttribute("userId", dto.getUserId());
request.getSession().setAttribute("email", dto.getEmail());
request.getSession().setAttribute("role", dto.getRole());
log.info("✅ 세션 저장 userId = {}, email = {}, role = {}", dto.getUserId(), dto.getEmail(), dto.getRole());
}
}
return result;
}
}
UserController
@LoginSessionInject
@PostMapping("/login")
public ResponseEntity<UserResponseDto> login(@Valid @RequestBody LoginRequestDto requestDto) {
return ResponseEntity.ok(userService.login(requestDto));
}
로그인 성공 후 반환된 UserResponseDto에서 userId
, email
, role
을 꺼내 세션에 저장한다.
Controller에서 servlet 객체를 직접 사용하는 방식은 의존성이 높아진다. 그래서 의존성을 낮추기 위해 책임분리가 필요했고 AOP를 활용해서 세션 저장 로직을 분리해 관심사를 분리해줬다. 이렇게 되면 Controller는 세션에 전혀 관여하지 않고 순수하게 응답만 리턴할 수 있어서 결과적으로 책임이 나눠지고 관심사 분리도 잘 이루어진 구조가 되기때문이다.
AOP
는 서비스 흐름을 유연하게 제어할 수 있는 확장 포인트인 거 같다. 커스텀 어노테이션을 만들고 Aspect로 받아서 "자동화된 동작"을 만들 수 있다는 걸 처음으로 구현해봤다. 관심사를 분리해 핵심 로직은 더 단순하게 유지하고 공통 로직은 재사용할 수 있어 코드 품질이 확실히 좋아진다는 걸 느꼈다. 앞으로도 AOP
를 활용할 수 있는 부분을 많이 생각하면서 코드를 구현해야겠다.