@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 |