π νλ‘μλ 무μμΈκ°?
νλ‘μ(Proxy)λ ν΄λΌμ΄μΈνΈκ° μλ²μ μ§μ  μμ²νμ§ μκ³ , μ€κ°μ λ리 κ°μ²΄λ₯Ό ν΅ν΄ κ°μ μ μΌλ‘ μμ²μ μννλ ꡬ쑰μ΄λ€. μ΄λ μ€κ°μ λ리 κ°μ²΄κ° λ°λ‘ 'νλ‘μ'μ΄λ€. νλ‘μλ ν΄λΌμ΄μΈνΈμ μλ² μ¬μ΄μ μμΉνμ¬ μμ²μ κ°λ‘μ±κ±°λ, λΆκ°μ μΈ μμ μ μννκ±°λ, μμ²μ μ μ΄ν μ μλ€.
μΌλ°μ μΌλ‘ ν΄λΌμ΄μΈνΈλ μμ μ΄ μμ²νλ λμμ΄ μ€μ  μλ²μΈμ§, νλ‘μμΈμ§λ₯Ό μμ§ λͺ»νλ€. λ°λΌμ νλ‘μλ μλ²μ λμΌν μΈν°νμ΄μ€λ₯Ό ꡬνν΄μΌ νλ€. μ΄λ‘ μΈν΄ ν΄λΌμ΄μΈνΈλ νλ‘μλ μλ²λ λμΌν λ°©μμΌλ‘ νΈμΆν μ μμΌλ©°, κ΅¬μ‘°κ° λ³κ²½λμ΄λ ν΄λΌμ΄μΈνΈ μ½λλ μ ν μν₯μ λ°μ§ μλλ€.

ποΈ νλ‘μ ν¨ν΄μ ꡬ쑰
νλ‘μ ν¨ν΄μ κΈ°λ³Έ ꡬ쑰λ λ€μκ³Ό κ°λ€.
- Client: μμ²μ 보λ΄λ 주체μ΄λ€.
- Proxy: μλ² λμ μμ²μ λ°μ μ²λ¦¬νκ±°λ, μλ²μκ² μ λ¬νλ μ€κ° κ°μ²΄μ΄λ€.
- RealSubject (Server): μ€μ  λΉμ¦λμ€ λ‘μ§μ μ²λ¦¬νλ μλ² κ°μ²΄μ΄λ€.

μ΄ μΈ κ°μ²΄λ λ€μκ³Ό κ°μ μΈν°νμ΄μ€ ꡬ쑰λ₯Ό κ°λλ€.
public interface Subject {
    String operation();
}
public class RealSubject implements Subject {
    public String operation() {
        return "μ€μ  κ°μ²΄ νΈμΆ κ²°κ³Ό";
    }
}
public class Proxy implements Subject {
    private RealSubject realSubject;
    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }
    public String operation() {
        // λΆκ° κΈ°λ₯ μν
        System.out.println("νλ‘μμμ μΆκ° μμ
 μν");
        return realSubject.operation();
    }
}
  μ΄λ κ² κ΅¬μ±λλ©΄ ν΄λΌμ΄μΈνΈλ μλμ²λΌ μ¬μ©ν μ μλ€.
public class Client {
    private Subject subject;
    public Client(Subject subject) {
        this.subject = subject;
    }
    public void execute() {
        String result = subject.operation();
        System.out.println("κ²°κ³Ό: " + result);
    }
}
  μ΄ κ΅¬μ‘°μμ ν΄λΌμ΄μΈνΈλ Proxyλ₯Ό μ£Όμ
λ°μλ, RealSubjectλ₯Ό μ£Όμ
λ°μλ μ ν μ μ μμΌλ©°, μ½λμ μμ  μμ΄ λμΌνκ² λμνλ€.
νλ‘μ ν¨ν΄κ³Ό λ°μ½λ μ΄ν° ν¨ν΄
GOF λμμΈ ν¨ν΄μμλ νλ‘μ ν¨ν΄κ³Ό λ°μ½λ μ΄ν° ν¨ν΄μ ꡬλΆνλ€. λ λ€ κ΅¬μ‘°μ μΌλ‘λ μ μ¬νμ§λ§, μλ(intent)κ° λ€λ₯΄λ€.

1. νλ‘μ ν¨ν΄ - μ κ·Ό μ μ΄ λͺ©μ 
νλ‘μ ν¨ν΄μ λ³΄ν΅ μ κ·Όμ μ μ΄νκΈ° μν΄ μ¬μ©λλ€.
- κΆνμ λ°λ₯Έ μ κ·Ό μ°¨λ¨
- κ²°κ³Όλ₯Ό μΊμ±ν΄μ μ±λ₯ ν₯μ
- μ§μ° λ‘λ©
μμ: μΊμ νλ‘μ
public class CacheProxy implements Subject {
    private Subject target;
    private String cache;
    public CacheProxy(Subject target) {
        this.target = target;
    }
    public String operation() {
        if (cache == null) {
            cache = target.operation();
        }
        return cache;
    }
}
  ν λ²λ§ μλ²μ μ κ·Όνκ³ , μ΄νμλ μΊμλ κ²°κ³Όλ₯Ό 리ν΄νλ€.
2. λ°μ½λ μ΄ν° ν¨ν΄ - κΈ°λ₯ νμ₯ λͺ©μ 
λ°μ½λ μ΄ν° ν¨ν΄μ κΈ°μ‘΄ κΈ°λ₯μ μλ‘μ΄ κΈ°λ₯μ μΆκ°νλ λ° μ¬μ©λλ€. ꡬ쑰λ νλ‘μμ κ±°μ λμΌνμ§λ§, λͺ©μ μ΄ λ€λ₯΄λ€.
μμ: λ©μμ§ λ³κ²½ λ°μ½λ μ΄ν°
public class MessageDecorator implements Subject {
    private Subject target;
    public MessageDecorator(Subject target) {
        this.target = target;
    }
    public String operation() {
        String result = target.operation();
        return "[λ°μ½λ μ΄μ
] " + result;
    }
}
  νλ‘μλ₯Ό μ°μμ μΌλ‘ μ°κ²°νλ μ²΄μΈ ννλ‘λ κ°λ₯νλ€.
Subject realSubject = new RealSubject();
Subject decorator1 = new MessageDecorator(realSubject);
Subject decorator2 = new TimeDecorator(decorator1);
Client client = new Client(decorator2);
client.execute();
  μ΄μ²λΌ λ°μ½λ μ΄ν°λ 체μΈμ ꡬμ±ν΄μ μ μ  κΈ°λ₯μ μΆκ°ν΄ λκ° μ μλ€. νλ‘μ μμ λ§μ°¬κ°μ§λ‘ νλ‘μ 체μΈμ ꡬμ±ν μ μλ€.
νλ‘μ ν¨ν΄μ ν΅μ¬ μ 리
- νλ‘μλ μλ²μ λμΌν μΈν°νμ΄μ€λ₯Ό ꡬνν΄μΌ νλ€.
- νλ‘μλ₯Ό λμ ν΄λ ν΄λΌμ΄μΈνΈ μ½λλ λ³κ²½ μμ΄ λμν΄μΌ νλ€.
- νλ‘μλ μ²΄μΈ ννλ‘ μ¬λ¬ κ° μ°κ²°ν μ μλ€.
- μ κ·Ό μ μ΄λ λΆκ° κΈ°λ₯ μΆκ° λ±μ μν μ μνν μ μλ€.
- νλ‘μλ μ€νλ§ AOPλ νλ‘μ κΈ°λ° DI ꡬνμμ λ§€μ° μ€μνλ€.
μ€νλ§μμμ μ€μ  νλ‘μ ꡬν μμ
μλλ μ€νλ§μμ μΈν°νμ΄μ€ κΈ°λ°μΌλ‘ νλ‘μλ₯Ό ꡬνν μ½λμ΄λ€.
@RequiredArgsConstructor
public class OrderServiceInterfaceProxy implements OrderServiceV1 {
    private final OrderServiceV1 target;
    private final LogTrace logTrace;
    public void orderItem(String itemId) {
        TraceStatus status = null;
        try {
            status = logTrace.begin("OrderService.orderItem()");
            target.orderItem(itemId);
            logTrace.end(status);
        } catch (Exception e) {
            logTrace.exception(status, e);
            throw e;
        }
    }
}
  μ΄λ° λ°©μμΌλ‘ OrderController, OrderRepositoryλ νλ‘μ ꡬν체λ₯Ό λ§λ€μ΄, κΈ°μ‘΄ ꡬν체λ₯Ό κ°μΈλ λ°©μμΌλ‘ κΈ°λ₯μ νμ₯ν  μ μλ€.
π JDK λμ  νλ‘μ κΈ°μ
νλ‘μ κΈ°μ μ νμ©νμ¬ κΈ°μ‘΄ μ½λλ₯Ό μμ νμ§ μκ³  λΆκ° κΈ°λ₯μ μΆκ°ν  μ μμμ§λ§, λκ° ν΄κ²°ν΄μΌν  λΆλΆμ΄ λ¨μμλ κ² μ²λΌ 보μΈλ€.
κ°μ νΉμ μμ£Ό μ μ¬ν κΈ°λ₯μ μννλ νλ‘μλΌλ νλ‘μλ₯Ό μ μ©νκΈ° μν΄ μ μ© λμμ μ«μ λ§νΌ λ§μ νλ‘μ ν΄λμ€λ₯Ό μμ°ν΄μΌ νλ€λ μ μ΄λ€.
οΉ‘ μ μ© λμμ΄ 1μ΅κ°μ ν΄λμ€λΌλ©΄ νλ‘μ ν΄λμ€λ 1μ΅κ°λ₯Ό μμν΄μΌ νλ€.
λμ  νλ‘μλ₯Ό μ΄ν΄νλ €λ©΄ μλ° λ¦¬νλ μ  κΈ°μ μ λν μ΄ν΄κ° μ νλμ΄μΌ νλ€.
리νλ μ  κΈ°μ μ μ¬μ©νλ©΄ ν΄λμ€λ λ©μλμ λ©νμ 보λ₯Ό λ°νμμ λμ μΌλ‘ νλνκ³ , μ½λλ λμ μΌλ‘ νΈμΆν μ μλ€.
λ°νμμμ ν΄λμ€λ λ©μλμ λ©νμ 보λ₯Ό μ¬μ©ν΄μ λμ μΌλ‘ νΈμΆνλ λ©μλλ₯Ό λ³κ²½ν μ μλ€.
π μλ° λ¦¬νλ μ  μμ  μ½λ
@Test
void reflection1() throws Exception {
    //ν΄λμ€ μ λ³΄
    Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello");
    
    Hello target = new Hello();
    //callA λ©μλ μ λ³΄
    Method methodCallA = classHello.getMethod("callA");
    Object result1 = methodCallA.invoke(target);
    log.info("result1={}", result1);
    
    //callB λ©μλ μ λ³΄
    Method methodCallB = classHello.getMethod("callB");
    Object result2 = methodCallB.invoke(target);
    log.info("result2={}", result2);
}
@Slf4j
static class Hello {
    public String callA() {
    log.info("callA");
    return "A";
    }
    
    public String callB() {
    log.info("callB");
    return "B";
    }
    
}
JDK λμ  νλ‘μ κΈ°μ μ μ΄ λ¦¬νλ μ  κΈ°μ μ μ΄μ©νμ¬ κ°λ°μκ° μ§μ  νλ‘μλ₯Ό λ§λ€μ§ μμλ λλλ‘ ν΄μ£Όλλ°, λ°νμμ νλ‘μ κ°μ²΄λ₯Ό λμ μΌλ‘ λ§λ€μ΄ μ£ΌκΈ° λλ¬Έμ΄λ€.
π μμ λ₯Ό ν΅ν΄ μμ보μ
AInterface
package hello.proxy.jdkdynamic.code;
public interface AInterface {
	String call();
}
AInterface ꡬν체
package hello.proxy.jdkdynamic.code;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AImpl implements AInterface {
    @Override
    public String call() {
        log.info("A νΈμΆ");
        return "a";
    }
}
BInterface
package hello.proxy.jdkdynamic.code;
public interface BInterface {
	String call();
}
BInterface ꡬν체
package hello.proxy.jdkdynamic.code;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BImpl implements BInterface {
    @Override
    public String call() {
        log.info("B νΈμΆ");
        return "b";
    }
}
JDK λμ  νλ‘μκ° μ κ³΅νλ InvocationHandler (InvocationHandlerμ ꡬνν΄μ Proxyμ λ§€κ°λ³μλ‘ λ£μ΄μ€μΌ νλ€.)
μ΄κ² JDK λμ  νλ‘μμ μ μ©ν κ³΅ν΅ λ‘μ§μ΄λ€.
package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
}package hello.proxy.jdkdynamic.code;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@Slf4j
public class TimeInvocationHandler implements InvocationHandler {
    private final Object target;
    
    public TimeInvocationHandler(Object target) {
	    this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("TimeProxy μ€ν");
        long startTime = System.currentTimeMillis();
        
        Object result = method.invoke(target, args);
        
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("TimeProxy μ’
λ£ resultTime={}", resultTime);
        
        return result;
    }
}- `TimeInvocationHandler` μ `InvocationHandler` μΈν°νμ΄μ€λ₯Ό ꡬννλ€. μ΄λ κ²ν΄μ JDK λμ  νλ‘ 
μμ μ μ©ν  κ³΅ν΅ λ‘μ§μ κ°λ°ν  μ μλ€. 
- `Object target` : λμ  νλ‘μκ° νΈμΆν  λμ 
- `method.invoke(target, args)` : 리νλ μ
μ μ¬μ©ν΄μ `target` μΈμ€ν΄μ€μ λ©μλλ₯Ό μ€ννλ€. `args` 
λ λ©μλ νΈμΆμ λ겨쀠μΈμμ΄λ€.
package hello.proxy.jdkdynamic;
import hello.proxy.jdkdynamic.code.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Proxy;
@Slf4j
public class JdkDynamicProxyTest {
    @Test
    void dynamicA() {
        AInterface target = new AImpl();
        TimeInvocationHandler handler = new TimeInvocationHandler(target);
        
        AInterface proxy = (AInterface) Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[] {AInterface.class}, handler);
        proxy.call();
        
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());
    }
    
    @Test
    void dynamicB() {
        BInterface target = new BImpl();
        TimeInvocationHandler handler = new TimeInvocationHandler(target);
        
        BInterface proxy = (BInterface) Proxy.newProxyInstance(BInterface.class.getClassLoader(), new Class[] {BInterface.class}, handler);
        proxy.call();
        
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());
    }
}
μ§μ  νλ‘μμ JDK λμ  νλ‘μ λΉκ΅




νΉμ  ν¨ν΄μ URlλ§ λμ  νλ‘μλ₯Ό μ μ©νκ³ μΆμ κ²½μ°
package hello.proxy.config.v2_dynamicproxy.handler;
import hello.proxy.trace.TraceStatus;
import hello.proxy.trace.logtrace.LogTrace;
import org.springframework.util.PatternMatchUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LogTraceFilterHandler implements InvocationHandler {
  private final Object target;
  private final LogTrace logTrace;
  private final String[] patterns;
  
  public LogTraceFilterHandler(Object target, LogTrace logTrace, String[] patterns) {
    this.target = target;
    this.logTrace = logTrace;
    this.patterns = patterns;
  }
  
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  
	//λ©μλ μ΄λ¦ νν°
    String methodName = method.getName();
    if (!PatternMatchUtils.simpleMatch(patterns, methodName)) {
      return method.invoke(target, args);
    }
    
    TraceStatus status = null;
    
    try {
      String message = method.getDeclaringClass().getSimpleName() + "."
          + method.getName() + "()";
      status = logTrace.begin(message);
	  //λ‘μ§ νΈμΆ
      Object result = method.invoke(target, args);
      logTrace.end(status);
      return result;
    } catch (Exception e) {
      logTrace.exception(status, e);
      throw e;
    }
  }
}package hello.proxy.config.v2_dynamicproxy;
import hello.proxy.app.v1.*;
import hello.proxy.config.v2_dynamicproxy.handler.LogTraceFilterHandler;
import hello.proxy.trace.logtrace.LogTrace;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Proxy;
@Configuration
public class DynamicProxyFilterConfig {
  private static final String[] PATTERNS = {"request*", "order*", "save*"};
  
  @Bean
  public OrderControllerV1 orderControllerV1(LogTrace logTrace) {
    OrderControllerV1 orderController = new OrderControllerV1Impl(orderServiceV1(logTrace));
    OrderControllerV1 proxy = (OrderControllerV1)
        Proxy.newProxyInstance(OrderControllerV1.class.getClassLoader(),
            new Class[]{OrderControllerV1.class},
            new LogTraceFilterHandler(orderController, logTrace, PATTERNS)
        );
    return proxy;
  }
  
  @Bean
  public OrderServiceV1 orderServiceV1(LogTrace logTrace) {
    OrderServiceV1 orderService = new OrderServiceV1Impl(orderRepositoryV1(logTrace));
    OrderServiceV1 proxy = (OrderServiceV1)
        Proxy.newProxyInstance(OrderServiceV1.class.getClassLoader(),
            new Class[]{OrderServiceV1.class},
            new LogTraceFilterHandler(orderService, logTrace, PATTERNS)
        );
    return proxy;
  }
  
  @Bean
  public OrderRepositoryV1 orderRepositoryV1(LogTrace logTrace) {
    OrderRepositoryV1 orderRepository = new OrderRepositoryV1Impl();
    OrderRepositoryV1 proxy = (OrderRepositoryV1)
        Proxy.newProxyInstance(OrderRepositoryV1.class.getClassLoader(),
            new Class[]{OrderRepositoryV1.class},
            new LogTraceFilterHandler(orderRepository, logTrace, PATTERNS)
        );
    return proxy;
  }
  
}'Design Pattern' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
| μ λ΅ ν¨ν΄(Strategy Pattern) μ 리 - ν νλ¦Ώ λ©μλ ν¨ν΄κ³Όμ μ°¨μ΄μ  (0) | 2025.03.27 | 
|---|---|
| [λμμΈ ν¨ν΄] ν νλ¦Ώ λ©μλ ν¨ν΄ (0) | 2025.03.24 | 
| [λμμΈ ν¨ν΄] Builder ν¨ν΄ (0) | 2025.03.23 | 
 
			
			 
				
			
λκΈ