백엔드/Spring

[Spring] 동적 프록시

kwang2134 2024. 11. 5. 16:39
728x90
반응형
728x90

JDK 동적 프록시

  • 런타임 시 인터페이스를 구현하여 프록시 객체를 생성하는 기술 
  • 특정 로직이 같고 호출하는 메서드만 다른 경우 프록시 클래스를 직접 각각 작성할 필요 없이 동적으로 생성

구성 요소

  • InvocationHandler (Interface): 프록시 객체가 호출할 메서드에 대한 처리를 정의 
  • java.lang.reflect.Proxy: 동적 프록시 객체를 생성하는 클래스

사용 방법

  1. InvocationHandler 인터페이스를 상속받아 invoke 메서드를 구현
    • invoke 메서드 내 프록시 객체가 처리할 로직을 구현
    • Method의 invoke를 통해 프록시 객체에서 실제 객체로 요청을 전달
  2. Proxy.newProxyInstance() 동적 프록시 객체 생성
    • 첫 번째 파라미터로 인터페이스를 불러올 클래스로더를 전달
    • 두 번째 파라미터로 구현체가 상속할 인터페이스 배열 전달
    • 세 번째 파라미터로 로직을 정의한 Handler 전달
  3. 생성된 프록시 객체를 통해 메서드 호출
//인터페이스 정의
interface Hello {
    void sayHello();
}

//InvocationHandler 구현
class HelloInvocationHandler implements InvocationHandler {
    private final Hello original;

    public HelloInvocationHandler(Hello original) {
        this.original = original;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("메서드 호출 전");
        Object result = method.invoke(original, args); // 원래 메서드 호출
        System.out.println("메서드 호출 후");
        return result;
    }
}

//실제 구현체
class HelloImpl implements Hello {
    @Override
    public void sayHello() {
        System.out.println("안녕하세요!");
    }
}

public class DynamicProxyExample {
    public static void main(String[] args) {
        Hello original = new HelloImpl();
        Hello proxy = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(),		//클래스 로더
            new Class[]{Hello.class},			//구현체가 상속 받을 인터페이스 배열
            new HelloInvocationHandler(original)	//로직이 정의된 핸들러
        );

        proxy.sayHello(); // 프록시 메서드 호출
    }
}

장점

  • 런타임에 객체를 변경하거나 새로운 기능을 추가할 수 있음
  • 공통적인 처리 로직을 한 곳에서 관리할 수 있음

단점

  • 인터페이스 기반으로 작동하여 클래스 기반 사용 불가

CGLIB

  • 자바에서 동적 프록시를 생성하기 위한 라이브러리로 클래스 기반 동적 프록시 생성을 지원
  • 자바의 리플렉션으로 런타임에 새로운 클래스를 생성하고 기존 클래스의 메서드를 오버라이드하여 프록시를 구현
  • Java 표준 라이브러리가 아닌 외부 라이브러리

특징

  • 인터페이스가 아닌 클래스를 기반으로 동적 프록시를 생성할 수 있음 -> 인터페이스가 없어도 적용 가능
  • 바이트코드를 조작하여 새로운 클래스를 생성, 기존 클래스의 메서드를 동적으로 오버라이드
  • 리플렉션을 사용하는 것보다 더 빠른 성능을 제공

사용 방법

  1. MethodInterceptor 인터페이스를 상속받아 intercept 메서드 구현
    • InvocationHandler의 invoke를 구현하는 것과 동일하게 MethodInterceptor의 interceptor 메서드에 공통 로직을 구현
    • MethodProxy의 invokeSuper를 사용해 원본 객체 메서드를 호출 해줌
    • 프록시 객체에서 원본 객체 메서드를 오버라이드 한 경우 invoke를 통해 프록시 객체에 오버라이드 된 메서드 호출 가능
  2. Enhancer 동적 프록시 생성
    • Enhancer를 통해 동적 프록시 객체를 생성
    • 프록시 객체의 부모 클래스가 될 기반 클래스를 setSuperclass로 지정
    • setCallback()을 통해 로직이 정의된 Interceptor 지정
    • create()로 프록시 객체 생성
//실제 클래스
class HelloService {
    public void sayHello() {
        System.out.println("안녕하세요!");
    }
}

//MethodInterceptor 구현
class HelloInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("메서드 호출 전");
        //Object result = proxy.invokeSuper(obj, args); //원래 메서드 호출
        Object result = proxy.invoke(obj, args);	//원본 객체의 메서드를 오버라이드 한 경우
        System.out.println("메서드 호출 후");
        return result;
    }
}

public class CGLIBExample {
    public static void main(String[] args) {
        //Enhancer를 통해 프록시 생성
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloService.class);
        enhancer.setCallback(new HelloInterceptor());

        //프록시 객체 생성
        HelloService proxy = (HelloService) enhancer.create();
        proxy.sayHello(); //프록시 메서드 호출
    }
}

장점

  • 인터페이스 없이 클래스 기반의 프록시를 만들 수 있음
  • 실제 메서드를 오버라이드하여 추가적인 기능을 쉽게 적용할 수 있음
  • JDK 동적 프록시에 비해 빠름

단점

  • final로 선언된 메서드를 오버라이드할 수 없음 -> 해당 메서드 프록시 객체에서 사용 불가

스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런 강의 내용 참고

728x90

'백엔드 > Spring' 카테고리의 다른 글

[Spring] 빈 후처리기 - BeanPostProcessor  (0) 2024.11.07
[Spring] ProxyFactory  (1) 2024.11.06
[Spring] Proxy & Decorator Pattern  (0) 2024.11.02
[Spring] Strategy & Template Callback  (0) 2024.11.01
[Spring] Template Method  (0) 2024.10.31