[Spring] Proxy(2/2)

dnjstjt12·2025년 3월 13일

Proxy Factory

동적 프록시는 JDK 동적 프록시와 CGLIB 프록시 두 가지 방식으로 생성됩니다.

JDK 동적 프록시는 인터페이스 기반으로 동작합니다.
CGLIB 프록시는 클래스 상속 기반으로 동작합니다.
그런데, 인터페이스 기반과 상속 기반을 혼용해서 사용해야 할 때는 어떻게 해야 할까요?
이럴 때마다 JDK와 CGLIB 프록시를 각각 따로 구현해야 할까요?
그렇지 않습니다.

스프링은 이런 상황을 위해 JDK와 CGLIB을 자동으로 선택해서 프록시를 만들어주는 ProxyFactory를 제공합니다.

Proxy Factory 코드

Subject: JDK 동적 프록시를 위한 인터페이스

public interface Subject {
    void call();
    void sayHello();
}

RealSubject: Subject 인터페이스를 구현한 클래스 (JDK 프록시 대상)

@Slf4j
public class RealSubject implements Subject {

    @Override
    public void call() {
        log.info("서버 시작");
        log.info("서버 끝");
    }
    
    @Override
    public void sayHello() {
        log.info("Hello!");
    }
}

ConcreteSubject: 인터페이스 없이 동작하는 클래스 (CGLIB 프록시 대상)

@Slf4j
public class ConcreteSubject{

    public void call() {
        log.info("서버 시작");
        log.info("서버 끝");
    }
    
    public void sayHello() {
        log.info("Hello!");
    }
}
  • 이제 위의 Subject 인터페이스 + RealSubject, 그리고 ConcreteSubject를 이용해 ProxyFactory로 각각 JDK와 CGLIB 프록시를 자동 생성할 수 있습니다.

AdviceImpl: ProxyFactory에 적용할 Advice

@Slf4j
public class AdviceImpl implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        log.info("proxy 실행");
        Object result = invocation.proceed();
        log.info("proxy 종료");
        return result;
    }
}
  • 여기서 Advice란 무엇일까요?
    방금 작성한 AdviceImpl 클래스는 MethodInterceptor를 구현하고 있죠.
    이러한 클래스는 스프링 AOP에서 'Advice' 라고 불립니다.

  • Advice란?
    Advice는 공통 기능(예: 로깅, 트랜잭션, 보안 검사 등)을 핵심 로직과 분리해서 적용할 수 있게 해주는 코드 조각입니다.
    즉, 핵심 기능을 감싸서 실행 전/후에 부가 로직을 추가할 수 있는 것이죠.

  • 또한 ProxyFactory에서 프록시를 만들 때 Advice를 사용하여 공통기능을 추가할 수 있습니다.

이제 ProxyFactory를 만들고 Advice를 적용시킨 테스트 코드를 살펴보겠습니다.

테스트 코드

public class ProxyFactoryTest {

    @Test
    public void proxyFactoryTest() {
        // -------------------------------
        // 1. JDK 동적 프록시 예제
        // -------------------------------

        // 프록시 대상: 인터페이스 기반 객체
        Subject subject = new RealSubject();

        // ProxyFactory 생성 (JDK 프록시 방식이 자동 선택됨)
        ProxyFactory proxyFactory = new ProxyFactory(subject);

        // 공통 기능(Advice) 추가
        proxyFactory.addAdvice(new AdviceImpl());

        // 프록시 생성
        Subject proxy = (Subject) proxyFactory.getProxy();

        // 프록시 메서드 호출 (call과 sayHello 모두 Advice 적용됨)
        proxy.call();       // → Advice + 실제 메서드 실행
        proxy.sayHello();   // → Advice + 실제 메서드 실행

        // -------------------------------
        // 2. CGLIB 프록시 예제
        // -------------------------------

        // 프록시 대상: 클래스 기반 객체 (인터페이스 없음)
        ConcreteSubject concreteSubject = new ConcreteSubject();

        // ProxyFactory 생성 (CGLIB 방식이 자동 선택됨)
        ProxyFactory proxyFactory2 = new ProxyFactory(concreteSubject);

        // 공통 기능(Advice) 추가
        proxyFactory2.addAdvice(new AdviceImpl());

        // 프록시 생성
        ConcreteSubject proxy2 = (ConcreteSubject) proxyFactory2.getProxy();

        // 프록시 메서드 호출 (call과 sayHello 모두 Advice 적용됨)
        proxy2.call();       // → Advice + 실제 메서드 실행
        proxy2.sayHello();   // → Advice + 실제 메서드 실행
    }
}

실행 결과

실행 결과를 보시면 ProxyFacotry에서 JDK프록시, CGLIB프록시 둘다 상황에 맞게 프록시를 만들어 준것을 알수 있습니다.

Call()메서드를 부르면 "서버 시작","서버 종료" 출력하고, SayHello()메서드를 부르면 "Hello!"를 호출합니다.

여기서 SayHello()메서드를 호출할때는 Advice의 로직을 추가하고 싶지 않다면 어떻게 해야할까요?

Pointcut을 사용하면 됩니다!
Pointcut은 어떤 메서드에 Advice를 적용할지 선택적으로 지정할 수 있는 기능입니다.
즉, call()에는 Advice를 적용하고, sayHello()는 건너뛰게 만들 수 있죠.

Advisor

스프링에서 부가 로직을 적용할때 Pointcut으로 어디에 적용할지 선택하고 Advice로 어떤 로직을 적용할지 선택합니다.

또한 하나의 Pointcut과 하나의 Advice를 합친 것을 Advisor라고 합니다.

Advisor를 적용한 ProxyFactory코드

public class ProxyFactoryWithAdvisorTest {

    @Test
    public void advisorTest() {
        // 대상 객체
        Subject target = new RealSubject();

        // 프록시 팩토리 생성
        ProxyFactory proxyFactory = new ProxyFactory(target);

        // Pointcut: call() 메서드에만 적용
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedName("call");  // call() 메서드만 매칭

        // Advice 설정
        Advice advice = new AdviceImpl();

        // Advisor 생성 및 추가
        Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
        proxyFactory.addAdvisor(advisor);

        // 프록시 생성
        Subject proxy = (Subject) proxyFactory.getProxy();

        // 호출 테스트
        proxy.call();       // Advice 적용됨
        proxy.sayHello();   // Advice 미적용
    }
}

실행 결과

실행 결과를 보시면 call()메서드에만 포인트 컷을 걸어주어, sayHello()메서드가 부가 로직이 들어가지 않은 것을 확인할 수 있습니다.

정리

이번 포스트에서는 스프링에서 어떻게 프록시를 사용하는지,
그리고 JDK 동적 프록시와 CGLIB 프록시의 차이,
그리고 이 둘을 자동으로 선택해주는 ProxyFactory의 사용법까지 알아보았습니다.

또한, 공통 기능을 프록시에 적용하기 위한 Advice,
적용 대상을 지정하기 위한 Pointcut,
이 둘을 결합한 Advisor를 통해
특정 메서드에만 프록시 로직을 적용하는 방법도 함께 실습해보았습니다.

프록시는 단순한 디자인 패턴이지만, 스프링에서는 이를 기반으로
트랜잭션, 보안, 캐싱, 비동기 처리 등 다양한 기능을 모듈화하여 적용하고 있습니다.

예를 들어 @Transactional로 트랜잭션 자동 처리, @Async로 비동기 로직 실행, @PreAuthorize로 보안 인가 처리, @Cacheable로 캐싱, @Validated로 검증 로직 자동화 등이 프록시 기반 AOP로 구현되어 있습니다.

스프링은 프록시를 기반으로 수많은 부가기능을 코드 변경 없이 간단하게 적용할 수 있게 해주며, 이는 관심사의 분리 원칙을 실현하는 기술입니다.


출처:
강의: 인프런의 김영한님의 『스프링 핵심 원리-고급편』을 듣고 작성했습니다.

profile
안녕하세요!

0개의 댓글