질문 역사:
프록시 객체와 Proxy 패턴은 Java에서 실제 객체에 대한 접근을 제어하거나 행동을 변경하거나 보안, 로깅, 메트릭과 같은 교차 기능을 주입할 수 있는 대체 객체를 생성하기 위해 등장하였습니다. Java 1.3부터 java.lang.reflect 패키지에서 표준 동적 프록시에 대한 지원이 나타났고, 이후 CGLIB 및 ByteBuddy와 같은 인기 라이브러리가 등장했습니다.
문제:
기존 클래스의 로직을 직접적으로 변경하는 것이 항상 가능하거나 편리하지 않습니다. 종종 원래 객체의 코드를 변경하지 않고도 행동(예: 로깅, 캐싱, 트랜잭션)을 투명하게 추가해야 할 필요가 있어, 이는 표준 상속 방법으로는 불가능합니다.
해결책:
프록시 객체는 정적으로(수동 서브클래싱을 통해) 및 동적으로(리플렉션 또는 바이트코드 메커니즘을 통해) 구현할 수 있습니다. 동적 프록시는 인터페이스를 구현하고 invoke() 메서드 내에서 실제 객체에 호출을 위임하는 객체를 생성하여 외부 행동을 주입하는 유연성을 제공합니다.
코드 예제:
import java.lang.reflect.*; interface Service { void doWork(); } class RealService implements Service { public void doWork() { System.out.println("Doing work!"); } } class LoggingInvocationHandler implements InvocationHandler { private final Object target; public LoggingInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Calling: " + method.getName()); return method.invoke(target, args); } } Service real = new RealService(); Service proxy = (Service) Proxy.newProxyInstance( real.getClass().getClassLoader(), new Class[] {Service.class}, new LoggingInvocationHandler(real)); proxy.doWork(); // 호출 로그 기록됨
주요 특징:
Java의 표준 동적 프록시가 인터페이스를 구현하지 않는 클래스에서 작동할 수 있는가?
아니요. Java의 표준 프록시는 오직 인터페이스와만 작동합니다. 인터페이스가 없는 클래스(예를 들어 일반 클래스를 프록시하려는 경우)에는 제3자 도구가 필요합니다 (예: CGLIB, ByteBuddy).
프라이빗 메서드를 가진 인터페이스의 프록시를 생성할 수 있는가?
아니요. Java의 인터페이스는 프록시해야 하는 프라이빗 메서드를 포함할 수 없습니다. 동적 프록시는 인터페이스의 공적 메서드만 구현합니다. Java 9부터 private default 메서드가 등장했지만, 이들은 프록시를 통해 접근할 수 없습니다.
InvocationHandler와 MethodInterceptor(CGLIB)의 차이점은 무엇인가?
InvocationHandler는 동적 프록시를 위해 사용되는 JDK의 표준 인터페이스로, 인터페이스 메서드의 호출을 수신합니다. MethodInterceptor는 동적 상속을 통해 모든 클래스의 메서드 호출을 가로채는 CGLIB의 인터페이스입니다. 적용 가능성과 수준의 차이가 있습니다: JDK는 오직 인터페이스와 작업하고, CGLIB는 모든 클래스와 작업합니다.
엔지니어가 모든 서비스의 각 메서드에 로그 코드 반복을 수동으로 작성합니다. 새로운 메서드를 추가할 때마다 로직이 중복되고, 종종 필요한 호출을 추가하는 것을 잊어버립니다.
장점:
단점:
프로젝트에 AOP가 동적 프록시를 통해 도입되었습니다: 로깅과 트랜잭션 관리가 중앙에서 구현된 대체 래퍼를 통해 처리됩니다.
장점:
단점: