@Aspect 쓰지 않고 순수 Proxy, Proxy Factory 사용법

@Aspect 를 사용하다 순수 Proxy 를 사용할 일이
생겨 정리해보았습니다.

 

순수 프록시

"프록시를 직접 만든다" 는 건 ProxyFactory 도 없이 클래스를 하나 더 만들어서 진짜 객체를 감싸는 것을 말합니다.

진짜 서비스

public interface Service {
    void doService();
}
public class RealService implements Service {
    @Override
    public void doService() {
        System.out.println("🎯 실제 서비스 실행");
    }
}

 

프록시 클래스

진짜 RealService 를 감싸서 부가기능을 추가하는 프록시를 만듭니다.

public class ServiceProxy implements Service {

    private final Service target; // 진짜 객체를 들고있음

    public ServiceProxy(Service target) {
        this.target = target;
    }

    @Override
    public void doService() {
        System.out.println("🛡️ [프록시] 부가기능 - 시작!");

        // 진짜 서비스 호출
        target.doService();

        System.out.println("🛡️ [프록시] 부가기능 - 끝!");
    }
}

 

실행하는코드

public class ProxyApp {
    public static void main(String[] args) {
        // 진짜 서비스
        Service realService = new RealService();

        // 프록시 생성해서 감싸기
        Service proxy = new ServiceProxy(realService);

        // 클라이언트는 프록시를 사용함
        proxy.doService();
    }
}

 

실행결과

🛡️ [프록시] 부가기능 - 시작!
🎯 실제 서비스 실행
🛡️ [프록시] 부가기능 - 끝!

 

프록시를 직접 만들려면 인터페이스를 그대로 구현하고 진짜 객체를 받아서 부가기능을 추가하고 진짜 메서드를 호출해주고 추가로 또 부가기능을 처리하면 됩니다.

 

하지만 이렇게 직접 프록시를 만들면 클래스가 늘어나게 됩니다.

기능 하나 추가할 때마다 새로운 프록시 클래스를 또 만들어야 하며 코드가 지저분해지고 유지보수가 힘들어지게 됩니다.

 

그래서 스프링에서는 ProxyFactory 같은 걸 써서 알아서 동적으로 프록시를 만들어주게 도와줍니다.

 

프록시 팩토리

프록시 객체를 쉽게 만들어주는 공장이라고 생각하시면 됩니다.

 

프록시를 직접 만들려면 아래처럼 코드를 짜야 합니다.

Service target = new RealService();
Service proxy = new LoggingProxy(target);
proxy.process();

 

근데 프로젝트에 프록시가 100개, 200개가 필요하다면 프록시 하나하나 손수 만들어야 합니다.

그래서 스프링이 ProxyFactory 라는 자동화 도구를 만들어주었습니다.

 

ProxyFactory 하는일

타겟 객체( 진짜 객체 ) 를 넣으면 알아서 프록시 객체를 만들어줍니다.

 

주문 서비스의 메서드 실행 시간 측정 프록시 팩토리

 

진짜 서비스

 public interface OrderService {
    void order();
}
public class OrderServiceImpl implements OrderService {
    public void order() {
        System.out.println("📦 주문 처리 완료!");
    }
}

 

시간 측정 Advice 만들기

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

// 공통 기능 (Advice)
public class TimeAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long startTime = System.currentTimeMillis();

        try {
            // 실제 메서드 실행
            return invocation.proceed();
        } finally {
            long endTime = System.currentTimeMillis();
            System.out.println("⏱️ 실행 시간: " + (endTime - startTime) + "ms");
        }
    }
}

 

ProxyFactory 로 프록시 만들기

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;

public class ProxyFactoryExample {
    public static void main(String[] args) {
        // 1. 진짜 객체 준비
        OrderService target = new OrderServiceImpl();

        // 2. ProxyFactory 생성
        ProxyFactory factory = new ProxyFactory(target);

        // 3. Advisor 등록 (어디에 어떤 공통 기능을 적용할지)
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedName("order"); // 메서드 이름이 order면 적용

        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, new TimeAdvice());
        factory.addAdvisor(advisor);

        // 4. 프록시 객체 얻기
        OrderService proxy = (OrderService) factory.getProxy();

        // 5. 프록시 객체 호출
        proxy.order();
    }
}

 

결과 출력

📦 주문 처리 완료!
⏱️ 실행 시간: 2ms

 

이렇게 프록시 만들기가 엄청 쉬워지며 여러 공통 기능도 추가할 수 있습니다.

"진짜 객체" + "공통 기능(Advice)" + "어디에 적용할지(PointCut)"

 

프록시 체인

프록시 안에 또 프록시가 들어가 있는 형태를 프록시 체인 이라고 합니다.

A 프록시 -> B 프록시 -> 진짜 객체
부가기능을 여러 개 겹쳐서 적용할 수 있습니다.

 

아래는 2가지 부가기능을 적용한 예제 입니다.

1. 메서드 실행 시간 측정

2. 호출 로그 찍기

 

진짜 서비스

public interface Service {
    void doService();
}
public class RealService implements Service {
    public void doService() {
        System.out.println("🎯 실제 서비스 실행");
    }
}

 

시간 측정 Advice

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class TimeAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("⏱️ [TimeAdvice] 시작");
        long startTime = System.currentTimeMillis();

        Object result = invocation.proceed(); // 다음 프록시 또는 타겟 호출

        long endTime = System.currentTimeMillis();
        System.out.println("⏱️ [TimeAdvice] 끝. 실행 시간: " + (endTime - startTime) + "ms");
        return result;
    }
}

 

호출 로그 Advice

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LogAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("📝 [LogAdvice] 메서드 호출: " + invocation.getMethod().getName());
        return invocation.proceed(); // 다음 프록시 또는 타겟 호출
    }
}

 

프록시 체인 만들기

import org.springframework.aop.framework.ProxyFactory;

public class ProxyChainExample {
    public static void main(String[] args) {
        // 진짜 객체
        Service target = new RealService();

        // 프록시 팩토리
        ProxyFactory factory = new ProxyFactory(target);

        // Advice 추가 (순서 중요!!)
        factory.addAdvice(new TimeAdvice()); // 먼저 실행
        factory.addAdvice(new LogAdvice());  // 나중에 실행

        // 프록시 얻기
        Service proxy = (Service) factory.getProxy();

        // 호출
        proxy.doService();
    }
}

 

실행 결과

📝 [LogAdvice] 메서드 호출: doService
⏱️ [TimeAdvice] 시작
🎯 실제 서비스 실행
⏱️ [TimeAdvice] 끝. 실행 시간: 2ms

 

프록시 체인 흐름

[클라이언트]
    ↓
(LogAdvice) 📝
    ↓
(TimeAdvice) ⏱️
    ↓
(RealService) 🎯

 

여기까지가 기본적인 순수 Proxy, ProxyFactory 예제 입니다.

 

'Spring' 카테고리의 다른 글

Spring 에서 CORS 설정  (3) 2025.07.09
@Aspect - AOP  (0) 2025.05.02
다양한 패턴  (0) 2025.04.25
트랜잭션 프록시와 예외  (0) 2025.04.08
@Transactional , Spring Data JPA 을 같이 쓰는 점에 관하여  (0) 2025.04.08