Java 아키텍처·Spring 용어 사전
Spring 코어JDK 동적 프록시 · CGLIB · 횡단 관심사

AOP 프록시

빈을 감싸는 대리 객체로 부가 기능을 끼우는 방식. @Transactional·@Cacheable이 전부 이 위에서 동작한다.

빈을 감싸는 대리 객체(프록시) 를 만들어 앞뒤에 부가 기능을 끼우는 방식. Spring 애노테이션 대부분의 동작 원리다.

무엇이 프록시로 도나

  • @Transactional · @Cacheable · @Async · @Retryable · @PreAuthorize ...
  • 전부 프록시가 가로채서 부가 동작을 수행한 뒤 원본을 호출한다
호출자 → [프록시] → 원본 빈
           │
           ├ 트랜잭션 시작
           ├ 원본 메서드 호출
           └ 커밋 또는 롤백

두 가지 프록시

  • JDK 동적 프록시 — 인터페이스 기반. 같은 인터페이스를 구현한 객체를 만든다

    • 인터페이스에 없는 메서드는 프록시할 수 없다
  • CGLIB — 클래스 상속 기반. 대상 클래스를 상속한 자식을 만든다

    • final 클래스·final 메서드·private 메서드는 오버라이드 불가
    • Spring Boot 는 기본이 CGLIB 다 (인터페이스가 없어도 되게)
// 프록시인지 확인해 보기
System.out.println(orderService.getClass());
// class com.example.OrderService$$SpringCGLIB$$0    ← 프록시다

프록시가 못 끼는 자리

  • private 메서드 — 오버라이드가 불가능하다 → 애노테이션이 무시된다
  • final 메서드 — 같은 이유
  • final 클래스 — CGLIB 상속 자체가 안 된다
  • 자기 호출 — 프록시를 거치지 않는다 (가장 유명한 함정)
  • 생성자 안 — 프록시가 아직 완성되지 않았다

애노테이션을 붙였는데 아무 일도 안 일어나면 이 목록부터 확인한다.

용어

  • 조인 포인트(Join Point) — 부가 기능을 끼울 수 있는 지점 (Spring 은 메서드 실행만)
  • 포인트컷(Pointcut) — 어디에 끼울지 고르는 표현식
  • 어드바이스(Advice) — 무엇을 끼울지 (Before · After · Around)
  • 애스펙트(Aspect) — 포인트컷 + 어드바이스 묶음
@Aspect @Component
class TimingAspect {
    @Around("@annotation(Timed)")
    Object measure(ProceedingJoinPoint pjp) throws Throwable {
        long t = System.nanoTime();
        try { return pjp.proceed(); }                 // 원본 호출
        finally { log.info("{} took {}ns", pjp.getSignature(), System.nanoTime()-t); }
    }
}

면접 함정

  • "AOP는 컴파일 시점에 코드를 짜 넣는다" → AspectJ는 그렇지만 Spring AOP는 런타임 프록시다.
  • "Spring AOP는 모든 지점에 끼울 수 있다" → 메서드 실행 지점만 가능하다. 필드 접근·생성자는 안 된다.

프록시 방식을 바꾸기

spring:
  aop:
    proxy-target-class: true    # true=CGLIB(기본) · false=인터페이스가 있으면 JDK 프록시
// 지금 무엇이 적용됐는지 확인
AopUtils.isAopProxy(bean);
AopUtils.isCglibProxy(bean);
AopUtils.isJdkDynamicProxy(bean);
AopProxyUtils.ultimateTargetClass(bean);   // 원본 클래스

적용 순서를 정한다

@Aspect
@Order(1)                       // 숫자가 작을수록 바깥이다
class LoggingAspect { }

@Aspect @Order(2)
class TimingAspect { }

트랜잭션과 커스텀 애스펙트의 순서가 중요할 때가 있다

  • 예: 트랜잭션 커밋 후 로그를 남기고 싶다면
    • 로깅 애스펙트가 트랜잭션 '바깥' 에 있어야 한다

@Transactional 의 기본 순서는 Ordered.LOWEST_PRECEDENCE (가장 안쪽)

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — Spring 핵심 — IoC·DI·AOP