전체 목록
SpringMedium#69

Spring AOP(Aspect-Oriented Programming)란 무엇이며 주요 개념을 설명해주세요.

#Spring#AOP#프록시#핵심개념
힌트

횡단 관심사, Aspect, Advice, Pointcut, JoinPoint를 생각해보세요.

정답 및 해설

Spring AOP(Aspect-Oriented Programming)란 무엇이며 주요 개념을 설명해주세요.

AOP(관점 지향 프로그래밍)는 핵심 비즈니스 로직과 횡단 관심사(Cross-Cutting Concerns)—로깅, 트랜잭션, 보안, 캐싱 등—를 분리하는 프로그래밍 패러다임입니다. 여러 클래스에 걸쳐 반복되는 부가 기능을 한 곳에 모아 관리함으로써 코드 중복을 제거하고 유지보수성을 높입니다.

AOP가 필요한 이유

로깅을 예로 들면, AOP 없이는 모든 서비스 메서드에 아래와 같은 코드를 반복 작성해야 합니다.

public void createOrder(OrderRequest request) {
    log.info("createOrder 시작: {}", request);   // 반복되는 로깅
    // 비즈니스 로직
    log.info("createOrder 종료");                 // 반복되는 로깅
}

AOP를 사용하면 이 로깅 코드를 Aspect 하나로 분리하여 적용 대상 메서드에 자동으로 주입할 수 있습니다.

주요 개념

Aspect

횡단 관심사를 모듈화한 단위입니다. @Aspect 어노테이션으로 선언하며, Advice와 Pointcut의 조합으로 구성됩니다.

@Aspect
@Component
public class LoggingAspect {
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);

    // Pointcut + Advice 조합
    @Around("execution(* com.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("메서드 시작: {}", joinPoint.getSignature().getName());
        Object result = joinPoint.proceed();
        log.info("메서드 종료: {}", joinPoint.getSignature().getName());
        return result;
    }
}

Advice

실제 부가 기능 코드가 담긴 메서드입니다. 실행 시점에 따라 5가지로 구분됩니다.

어노테이션실행 시점주요 사용 사례
@Before메서드 실행 전파라미터 검증, 로깅
@After메서드 실행 후 (성공/실패 무관)리소스 정리
@AfterReturning메서드 정상 반환 후반환값 로깅, 캐시 저장
@AfterThrowing예외 발생 후에러 로깅, 알림 발송
@Around메서드 실행 전후 모두트랜잭션, 성능 측정
@Aspect
@Component
public class ExampleAspect {

    // 메서드 실행 전
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        log.info("[Before] 메서드: {}", joinPoint.getSignature().getName());
    }

    // 정상 반환 후 — 반환값 접근 가능
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        log.info("[AfterReturning] 반환값: {}", result);
    }

    // 예외 발생 후 — 예외 객체 접근 가능
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
        log.error("[AfterThrowing] 예외: {}", ex.getMessage());
    }

    // 실행 전후 — proceed()로 원본 메서드 호출
    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long elapsed = System.currentTimeMillis() - start;
        log.info("[Around] {}ms 소요", elapsed);
        return result;
    }
}

Pointcut

Advice가 적용될 위치(JoinPoint)를 정의하는 표현식입니다. @Pointcut으로 재사용 가능한 표현식을 선언할 수 있습니다.

@Aspect
@Component
public class PointcutExamples {

    // 재사용 가능한 Pointcut 정의
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}

    @Pointcut("@annotation(com.example.annotation.Loggable)")
    public void loggableMethod() {}

    // 조합 사용
    @Before("serviceLayer() || loggableMethod()")
    public void combinedAdvice(JoinPoint joinPoint) {
        log.info("결합된 Pointcut 적용: {}", joinPoint.getSignature());
    }
}

주요 Pointcut 표현식:

표현식설명예시
execution메서드 실행 패턴execution(* com.example.service.*.*(..))
@annotation특정 어노테이션이 붙은 메서드@annotation(Transactional)
within특정 타입 내 메서드within(com.example.service.*)
@within특정 어노테이션이 붙은 타입@within(Service)
args특정 파라미터 타입args(String, ..)
bean특정 빈 이름bean(orderService)

JoinPoint

Advice가 적용되는 실제 실행 지점입니다. Spring AOP에서는 메서드 실행만 JoinPoint로 지원합니다.

@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
    // 메서드 시그니처
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    String methodName = signature.getName();

    // 파라미터
    Object[] args = joinPoint.getArgs();

    // 대상 객체
    Object target = joinPoint.getTarget();

    log.info("클래스: {}, 메서드: {}, 파라미터: {}",
        target.getClass().getSimpleName(), methodName, Arrays.toString(args));
}

Spring AOP의 동작 원리: 프록시 기반

Spring AOP는 실제 객체를 감싸는 프록시 객체를 생성해 Advice를 적용합니다.

클라이언트 → 프록시(Advice 실행) → 실제 Bean(핵심 로직)

프록시 생성 방식은 두 가지입니다.

방식적용 조건특징
JDK Dynamic Proxy인터페이스가 있는 경우java.lang.reflect.Proxy 사용
CGLIB인터페이스가 없거나 proxyTargetClass=true바이트코드 조작으로 서브클래스 생성

Spring Boot는 기본적으로 CGLIB을 사용합니다(spring.aop.proxy-target-class=true).

Self-Invocation 문제

같은 클래스 내에서 메서드를 호출하면 프록시를 거치지 않아 AOP가 적용되지 않습니다.

@Service
public class OrderService {

    @Transactional
    public void createOrder() {
        // 내부 호출 — 프록시를 거치지 않아 @Transactional 미적용!
        this.validateOrder();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void validateOrder() {
        // 별도 트랜잭션으로 실행되지 않음
    }
}

해결 방법:

@Service
@RequiredArgsConstructor
public class OrderService {

    // 방법 1: ApplicationContext에서 자기 자신의 프록시를 가져오기
    private final ApplicationContext context;

    public void createOrder() {
        OrderService proxy = context.getBean(OrderService.class);
        proxy.validateOrder(); // 프록시를 통한 호출 → AOP 적용
    }

    // 방법 2: 클래스를 분리하여 외부 호출로 만들기 (권장)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void validateOrder() { }
}

실전 예제: 성능 측정 AOP

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MeasureTime {}
@Aspect
@Component
@Slf4j
public class PerformanceAspect {

    @Around("@annotation(com.example.annotation.MeasureTime)")
    public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Object result = joinPoint.proceed();

        stopWatch.stop();
        log.info("[성능 측정] {}.{} - {}ms",
            joinPoint.getTarget().getClass().getSimpleName(),
            joinPoint.getSignature().getName(),
            stopWatch.getTotalTimeMillis());

        return result;
    }
}
@Service
public class UserService {

    @MeasureTime
    public List<User> findAllUsers() {
        // 이 메서드의 실행 시간이 자동으로 로깅됨
        return userRepository.findAll();
    }
}

정리

개념설명
Aspect횡단 관심사를 모듈화한 클래스
Advice실제 부가 기능 코드 (Before/After/Around 등)
PointcutAdvice 적용 위치를 정의하는 표현식
JoinPointAdvice가 실행되는 실제 시점 (메서드 실행)
WeavingAspect를 타겟 객체에 적용하는 과정