[JAVA] Spring AOP

이권민·2025년 12월 20일

목차

  1. AOP
  2. Proxy Pattern
  3. Dynamic Proxy
  4. CGLIB
  5. private method와 AOP
  6. self invocation 우회방법

AOP

💡 여러 곳에 흩어지는 공통 관심사(cross-cutting concern)를 핵심 비즈니스 로직과 분리해서 관리하는 프로그래밍 방식. Aspect-Oriented Programming

  • 로깅, 트랜잭션, 인증/권한 체크, 성능 측정, 예외 처리 공통으로 처리

  • 유지 보수성 ↑

  • Client -> Proxy (AOP 적용) -> Target 식으로 적용

    • 프록시는 공통 로직 실행, 실제 메서드 호출, 후처리 - AOP 핵심 용어
      용어의미
      Aspect공통 관심사 모음 (클래스)
      Advice언제 무엇을 할지 (메서드)
      Join PointAOP가 끼어들 수 있는 지점 (메서드 실행 등)
      Pointcut어떤 Join Point에 적용할지 조건
      Target실제 비즈니스 객체
      ProxyTarget을 감싸는 객체
// @Around는 가장 강력한 Advice
// 메서드 실행 전 / 후 / 예외 발생 시점까지 모두 제어 가능
@Around("pointcut()")
// ProceedingJoinPoint: 실제 타겟 메서드를 대리 실행하는 객체
// proceed()를 호출해야만 진짜 비즈니스 로직이 실행됨
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    // before
    Object result = pjp.proceed(); // 실제 메서드 실행
    // after
    return result;
}

@Aspect // 공통관심사 선언, 프록시 생성 대상으로 인식
@Component // spring Bean으로 등록
public class LogAspect {

// com.example.service 패키지 이하의 모든 메서드 실행을 가로챔
    @Around("execution(* com.example.service..*(..))")
    // joinPoint = 지금 실행될 메서드 정보
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
	// 실행시간 측정 및 실제 메서드 실행
        Object result = joinPoint.proceed(); 

        long end = System.currentTimeMillis();
        // getSignature() → 메서드 시그니처 정보
        System.out.println(joinPoint.getSignature() + " 실행 시간: " + (end - start));

        return result;
    }
}

// @Service → Spring Bean. com.example.service 패키지 → Pointcut 조건 만족
@Service
public class OrderService {
    public void order() {
        // 비즈니스 로직
    }
}

// pointCut 문법
// execution(접근제어자 반환타입 패키지.클래스.메서드(파라미터))
// execution(* com.example..*Service.*(..))
// * 아무거나, .. 하위 패키지 전체
  • 프록시 기반으로 Spring Bean만 AOP 적용 가능(new로 직접 만든 객체 x)

  • 메서드 내부 호출은 적용 x(프록시 안 거치기 때문)

  • 인터페이스, 클래스 기준. 인터페이스 있으면 JDK Dynamic Proxy, 없으면 CGLIB

구분AOPInterceptorFilter
레벨메서드컨트롤러서블릿
대상BeanHTTP 요청요청/응답
용도로직 공통화인증/로깅인코딩

Proxy Pattern

💡 실제 객체(Target) 앞에 대리 객체(Proxy)를 두고, 호출을 가로채서 부가 기능을 수행하는 구조

  • Spring AOP = Proxy 기반 AOP

    • Spring AOP는 바이트코드에 직접 끼어드는 게 아니라, Bean을 감싼 Proxy 객체를 대신 주입
    @Autowired
    OrderService orderService;
    // 실제로는 OrderServiceProxy 가 주입
      
    interface Service {
      void run();
    }
    
    class RealService implements Service {
        public void run() {
            System.out.println("비즈니스 로직 실행");
        }
    }
    
    class ServiceProxy implements Service {
        private final Service target;
    
      public ServiceProxy(Service target) {
          this.target = target;
      }
    
      @Override
      public void run() {
          System.out.println("[AOP] before");
          target.run();
          System.out.println("[AOP] after");
      }
      }
    
    public class Main {
      public static void main(String[] args) {
          Service service = new ServiceProxy(new RealService());
          service.run();
      }
    }

Dynamic Proxy

  • 대상 클래스가 인터페이스 구현한 경우 사용

  • Java 표준(java.lang.reflect.Proxy)

  • 인터페이스 기반

    • 인터페이스 메서드만 프록시 가능, 가볍고 안정적
  • 실제 구현체 상속 x

public interface OrderService {
    void order();
}

public class OrderServiceImpl implements OrderService { }

CGLIB

Code Generation Library

  • Spring이 인터페이스가 없는 클래스에 쓰는 방식.

    • 클래스 기반 프록시
  • 메서드 오버라이딩 방식

  • final / private 메서드 프록시 불가

  • 대상 클래스를 상속받아 프록시를 만듦

    • OrderService$$EnhancerByCGLIB -> OrderService (class)
  • Spring은 내부적으로 CGLIB 사용

private method와 AOP

private 메서드가 AOP 에 걸리지 않음

  • 프록시는 오버라이딩으로 가로채는데, private 메서드는 상속,오버라이딩이 불가능하기 때문
class Service {
    public void a() {
        b(); // private
    }

    private void b() {}
}
  • CGLIB은 Service를 상속해서 프록시 생성

  • private 메서드는 자식 클래스에서 접근 불가 → 프록시가 끼어들 수 없음

  • JDK Dynamic Proxy도 마찬가지

    • 인터페이스 메서드만 대상

    • private은 애초에 대상 아님

Spring AOP가 적용되지 않는 경우

상황이유
private 메서드프록시가 오버라이딩 불가
final 메서드CGLIB 오버라이딩 불가
static 메서드객체 메서드 호출이 아님
self-invocation프록시를 거치지 않음
new로 생성한 객체Spring Bean 아님

self invocation 우회방법

  • Spring AOP는 프록시 기반, 같은 클래스 내부에서 메서드를 호출(self-invocation)하면 프록시를 거치지 않아 AOP가 적용되지 않는다.

  • Client -> Proxy -> target.outer() vs target.outer() -> this.inner()

    • 프록시 우회
@Service
public class MyService {

    @Transactional
    public void outer() {
        inner(); 
    }

    @Transactional
    public void inner() {
        // 트랜잭션 대상
    }
}

해결방법_메서드 분리

@Service
class InnerService {
    @Transactional
    public void inner() {}
}

@Service
class OuterService {
    private final InnerService innerService;

    public void outer() {
        innerService.inner(); 
    }
}
  • 자기 자신 프록시 주입, AopContext.currentProxy() 등은 순환 참조 위험, 코드 가독성 저하, 테스트/리팩토링 어려움으로 비권장
profile
이것저것이것 개발자

0개의 댓글