[Spring] AOP 구현
              
          2025. 10. 27. 05:21ㆍ코딩 도구/백엔드 개발 (Backend Development)
반응형
    
    
    
  AOP 구현 정리
이 글은 인프런 김영한님의 "스프링 핵심 원리 - 고급편" 강의를 수강하고 정리한 내용이다.
1. AOP 적용 전 기본 구조
OrderRepository
@Slf4j
@Repository
public class OrderRepository {
    public String save(String itemId) {
        log.info("[orderRepository] 실행");
        if (itemId.equals("ex")) throw new IllegalStateException("예외 발생!");
        return "ok";
    }
}
OrderService
@Slf4j
@Service
public class OrderService {
    private final OrderRepository orderRepository;
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    public void orderItem(String itemId) {
        log.info("[orderService] 실행");
        orderRepository.save(itemId);
    }
}
이 상태에서는 AOP가 적용되지 않았으며, 로그 출력도 단순히 서비스와 리포지토리 내부에서만 수행된다.
2. AOP 도입: @Aspect 적용
AspectV1: AOP 기본 적용
@Slf4j
@Aspect
public class AspectV1 {
    @Around("execution(* hello.aop.order..*(..))")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }
}
- @Around는 가장 강력한 어드바이스로, 메서드 실행 전후 모두 개입 가능하다.
 - 하지만 실수로 proceed()를 누락하면 메서드 자체가 실행되지 않는 문제가 생긴다.
 
3. 포인트컷 분리 및 재사용
AspectV2: 포인트컷 분리
@Pointcut("execution(* hello.aop.order..*(..))")
private void allOrder() {}
@Around("allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
    log.info("[log] {}", joinPoint.getSignature());
    return joinPoint.proceed();
}
포인트컷 시그니처를 분리함으로써 코드 재사용성과 가독성을 확보할 수 있다.
4. 부가기능 추가 - 트랜잭션 시뮬레이션
AspectV3
@Pointcut("execution(* hello.aop.order..*(..))")
public void allOrder() {}
@Pointcut("execution(* *..*Service.*(..))")
private void allService() {}
@Around("allOrder()")
public Object doLog(...) { ... }
@Around("allOrder() && allService()")
public Object doTransaction(...) {
    try {
        log.info("[트랜잭션 시작] {}");
        Object result = joinPoint.proceed();
        log.info("[트랜잭션 커밋] {}");
        return result;
    } catch (Exception e) {
        log.info("[트랜잭션 롤백] {}");
        throw e;
    } finally {
        log.info("[리소스 릴리즈] {}");
    }
}
- 포인트컷 조합 (&&)을 통해 특정 계층(*Service)에만 트랜잭션 적용
 - try-catch-finally를 통해 트랜잭션 흐름을 흉내낸다
 
5. 포인트컷 외부 클래스 분리
Pointcuts 클래스
public class Pointcuts {
    @Pointcut("execution(* hello.aop.order..*(..))")
    public void allOrder() {}
    @Pointcut("execution(* *..*Service.*(..))")
    public void allService() {}
    @Pointcut("allOrder() && allService()")
    public void orderAndService() {}
}
이 방식은 여러 Aspect에서 포인트컷을 재사용할 수 있어 유지보수성이 높아진다.
6. 어드바이스 순서 설정
AspectV5Order
@Aspect
@Order(2)
public static class LogAspect {...}
@Aspect
@Order(1)
public static class TxAspect {...}
- @Order를 사용하여 적용 순서 지정 (숫자가 작을수록 먼저 실행)
 - 트랜잭션 로직이 먼저 실행되고, 그 이후 로그가 출력되도록 구성 가능
 
7. 다양한 어드바이스 종류
@Before // 대상 메서드 실행 전
@AfterReturning // 정상 반환 후
@AfterThrowing // 예외 발생 시
@After // 예외 여부 상관없이 항상 실행
@Around // 전반적인 제어 가능 (proceed)
- @Before, @After 는 단순하고 의도가 명확하다.
 - @Around는 가장 강력하지만 실수로 proceed() 호출을 빠뜨릴 위험이 있다.
 
좋은 설계는 제약이 있는 것이다
좋은 설계란 단순히 유연한 기능을 제공하는 것이 아니라, 명확한 의도와 실수를 줄이기 위한 '제약'을 통해 구조를 안정적으로 만드는 것이다.
@Around 하나만으로도 모든 동작을 구현할 수 있다. 하지만 proceed() 호출을 빼먹는 실수 하나로 치명적인 장애가 발생할 수 있다. 반면 @Before, @AfterReturning 같은 어드바이스는 의도가 명확하며 구조적으로 실수 가능성이 적다.
이처럼 제약이 있으면 오히려 개발자 입장에서 다음과 같은 이점이 있다:
- 실수를 줄이고 방지할 수 있다.
 - 역할이 분리되어 유지보수가 쉬워진다.
 - 다른 개발자도 코드를 빠르게 이해할 수 있다.
 
즉, 제한된 구조 안에서 명확한 책임을 부여하는 것이 바로 좋은 설계이며, 스프링 AOP에서도 이 원칙이 적용된다.
8. 실전 정리 및 마무리
- @Aspect 를 통해 횡단 관심사(Cross-cutting concerns) 를 모듈화할 수 있다.
 - AOP는 OOP가 어려운 관심사 분리에 매우 유용한 보완 기술이다.
 - 스프링 AOP는 프록시 기반으로 런타임 시점에 어드바이스를 적용한다.
 - 포인트컷 재사용, 순서 조절, 다양한 어드바이스 활용으로 유연하고 강력한 구조를 구성할 수 있다.
 
정형화된 AOP가 필요한 상황: 로깅, 트랜잭션, 보안 검사, 성능 측정 등
반응형
    
    
    
  '코딩 도구 > 백엔드 개발 (Backend Development)' 카테고리의 다른 글
| [Spring] AOP 포인트컷 (0) | 2025.11.03 | 
|---|---|
| [Spring] @Aspect AOP (0) | 2025.10.20 | 
| [Spring] 빈 후처리기 (0) | 2025.10.13 | 
| [Spring] 스프링이 지원하는 프록시 (0) | 2025.10.06 | 
| [Spring] 동적 프록시 기술 (0) | 2025.09.29 |