blog

자주 묻는 질문 - JDK 및 CGLIB 동적 프록시

JDK 동적 프록시는 인터페이스를 구현하는 클래스만 프록시할 수 있지만, CGLIB는 인터페이스를 구현하지 않는 클래스도 프록시할 수 있습니다. 또한 CGLIB 동적 프록시는 프록...

Oct 10, 2025 · 5 min. read
シェア

JDK 동적 프록시 대 CGLIB 동적 프록시

  1. JDK 동적 프록시는 인터페이스를 구현하는 클래스만 프록시할 수 있지만, CGLIB는 인터페이스를 구현하지 않는 클래스도 프록시할 수 있습니다. 또한 CGLIB 동적 프록시는 프록시된 클래스의 서브클래스를 생성하여 프록시된 클래스의 메서드 호출을 가로채므로 최종으로 선언된 클래스 및 메서드는 프록시할 수 없습니다.
  2. 둘 사이의 효율성 측면에서는 대부분의 경우 JDK 동적 에이전트가 더 우수하며, JDK 버전이 업그레이드될수록 이러한 이점은 더욱 분명해집니다.
  3. JDK 동적 프록시는 인터셉터, 리플렉션 메커니즘을 사용하여 프록시 인터페이스 익명 클래스를 생성한 후, 처리할 특정 메서드를 호출하기 전에 InvokeHandler를 호출합니다; CGLIB 동적 프록시는 ASM 프레임워크를 사용하고, 프록시 객체 클래스의 클래스 파일은 바이트코드 수정을 통해 로드되어 처리할 하위 클래스를 생성합니다.

JDK 동적 프록시 기본 원칙:

JDK 동적 프록시에서 생성된 프록시 클래스 $Proxy1은 Proxy에서 상속되어 HelloService 인터페이스를 구현하고, 프록시 클래스의 메서드를 호출할 때 인터셉터 MyInvocationHandler의 호출 메서드를 입력하게 되며, 다음은 프록시 클래스 생성 코드입니다:

// 프록시 객체 생성
HelloService helloService = (HelloService) Proxy.newProxyInstance(MyInvocationHandler.class.getClassLoader(), new Class[]{HelloService.class}, new MyInvocationHandler());
helloService.say();

위의 코드를 통해 얻은 헬로서비스 오브젝트는 실제로 JDK 동적 프록시 오브젝트이며, 다음과 같이 VM 옵션을 추가하여 동적 프록시 오브젝트를 저장하고 VM 옵션을 추가할 수 있습니다:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

그 후 다음과 같이 동적 프록시 객체가 생성되는데, say()는 실제로 개선이 필요한 HelloService에 정의된 메서드이므로, helloService.say()를 호출하면 실제로는 $Proxy1.say() 메서드가 호출되고, 여기서 h는 직접 정의한 MyInvocationHandler 인터셉터이며, 이후 인터셉터의 invoke 메서드로 이동하게 됩니다. MyInvocationHandler 인터셉터로 이동한 다음 인터셉터의 호출 메서드로 이동합니다.

import com.example.nettystudy.JdkProxyTest.HelloService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy1 extends Proxy implements HelloService {
 private static Method m1;
 private static Method m2;
 private static Method m3;
 private static Method m0;
 ...
 
 public final void say() throws {
 try {
 super.h.invoke(this, m3, (Object[])null);
 } catch (RuntimeException | Error var2) {
 throw var2;
 } catch (Throwable var3) {
 throw new UndeclaredThrowableException(var3);
 }
 }
 ...
}

다음으로 인터셉터의 호출 메서드를 살펴보겠습니다. 이 메서드에는 세 개의 매개 변수가 있는데, 첫 번째 매개 변수인 proxy는 위의 프록시 클래스 객체이고, method는 인터페이스의 say 메서드이므로 인터셉터가 추가하는 향상 작업을 수행하게 됩니다.

public class MyInvocationHandler implements InvocationHandler {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 System.out.println("메서드가 실행되기 전에");
 // 여기서 HelloServiceImpl은 프록시 객체이고, 프록시 객체는 메서드를 실행합니다.
 Object result = method.invoke(new HelloServiceImpl(), args);
 System.out.println("메서드가 실행된 후");
 return result;
 }
}

cglib 동적 프록시의 기본 원리

cglib는 기본 바이트코드 기술을 사용하여 클래스의 서브클래스를 생성하고 서브클래스의 메서드를 사용하여 모든 부모 클래스 호출을 가로채고 교차 로직을 짜는 데 사용합니다.

cglib은 다음과 같이 사용됩니다:

// Human.java
public class Human {
 public void info() {
 System.out.println("Human invoke info");
 }
 public void fly() {
 System.out.println("Human invoke fly");
 }
}
// CGLibProxy.java  
class CGLibProxy implements MethodInterceptor {
 
 
	 // CGLib프록시할 대상 객체
 private Object targetObject;
 
 public Object getProxyInstance(Object obj) { 
 
 this.targetObject = obj; 
 //1. 도구 클래스 생성
 Enhancer enhancer = new Enhancer();
 // 2.부모 클래스 - 클래스 또는 인터페이스 설정하기
 enhancer.setSuperclass(obj.getClass()); 
 //3. 콜백 함수 설정
 enhancer.setCallback(this); 
 //4. 서브 클래스 객체, 즉 프록시 객체를 생성합니다.
 Object proxyObj = enhancer.create(); 
 // 프록시 객체를 반환합니다.
 return proxyObj;
 } 
 
 public Object intercept(Object proxy, Method method, Object[] args,
 MethodProxy methodProxy) throws Throwable {
 System.out.println("메서드 실행 전 처리 향상");
 // 대상 객체 메서드 실행
 Object obj = method.invoke(targetObject, args);
 System.out.println("메서드 실행 후 향상된 처리");
 return obj;
 } 
}
// TestCglibProxy.java  
public class TestCglibProxy {
	public static void main(String[] args) {
		// 프록시 객체 생성
		Human man = new Human();
		// 다음 코드를 추가하여 프록시 클래스 소스 파일을 가져옵니다.
		String path = CGLibProxy.class.getResource(".").getPath();
		System.out.println(path);
		System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);
		CGLibProxy cgLibProxy = new CGLibProxy();
		Object obj = cgLibProxy.getProxyInstance(man);
		System.out.println(obj.getClass());
		Human hu = (Human)obj;
		hu.info();
		hu.fly();
	}
}

위 프로그램의 출력은 다음과 같습니다:

생성된 프록시 클래스의 클래스 파일은 빨간색 경로를 따라가면 찾을 수 있습니다.

증강 대상 클래스인 Human 클래스에는 정보() 및 플라이() 메서드 두 개가 정의되어 있고, cglib에서 생성된 서브클래스는 Human 클래스를 상속받아 이 두 메서드를 재정의하게 되며, 생성된 프록시 클래스는 다음과 같습니다:

프록시 클래스에서 인터셉터는 먼저 var00100에 할당되고, 이후에는 직접 정의한 인터셉터의 인터셉트 메서드인 var10000.intercept 메서드가 호출됩니다 CGLibProxy#intercept().

public class Human$$EnhancerByCGLIB$$a1812f09 extends Human implements Factory {
	// ... 나머지 코드는 생략하세요.
 public final void info() {
 MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
 if (var10000 == null) {
 CGLIB$BIND_CALLBACKS(this);
 var10000 = this.CGLIB$CALLBACK_0;
 }
 if (var10000 != null) {
 var10000.intercept(this, CGLIB$info$0$Method, CGLIB$emptyArgs, CGLIB$info$0$Proxy);
 } else {
 super.info();
 }
 }
 // ...
}
Read next

데이터 타입의 1단계 자바

데이터 유형이란 무엇인가요? 데이터 유형은 데이터의 한 종류입니다. Jana에는 기본 데이터 유형과 참조 데이터 유형이라는 두 가지 주요 데이터 유형이 있습니다.

Oct 10, 2025 · 3 min read