본문 바로가기
Spring

JDK Dynamic Proxy와 CGLIB

by setung 2021. 12. 7.

저번 글은 AOP에 대해서 작성했었습니다.

이번 글을 Spring에서 제공하는 JDK Dynamic Proxy와 CGLib에 대해서 알아보겠습니다.

 

둘의 공통점은 Proxy를 통해 AOP를 제공한다는 것입니다.

Proxy란 추가적인 기능을 추가해 실제 Target 객체의 기능을 호출하는 객체를 뜻합니다. 

쉽게 말하기가 어렵네요. 

간단히 Proxy 예를 보겠습니다.

 

public interface ITarget {

    void sayHi();
}
public class Target implements ITarget {

    @Override
    public void sayHi() {
        System.out.println("Hi");
    }
}
public class Proxy implements ITarget {

    ITarget target;

    public Proxy(ITarget target) {
        this.target = target;
    }

    @Override
    public void sayHi() {
        System.out.println("I am Proxy");
        target.sayHi();
        System.out.println("Bye Proxy");

    }
}
public class ProxyTest {

    @Test
    public void proxyTest() {
        ITarget target = new Target();
        ITarget proxy = new Proxy(target);

        target.sayHi();
        System.out.println("----------------------------------");
        proxy.sayHi();
    }
}

Proxy 클래스는 Target 객체를 주입받아 Proxy의 sayHi() 메서드에서 Target의 sayHi()를 그대로 호출합니다.

대신 Target.sayHi() 위아래로 출력문을 추가해 주었습니다.

이렇듯 Target의 기능을 대신 호출해주면서 추가적인 기능을 제공해주는 게 Proxy입니다.

결과 Bye...오타..

 

즉 JDK Dynamic Proxy와 CGLib는 실제 실행하고자 하는 로직을 Proxy를 통해 부가 로직을 수행한다고 이해하시면 되겠습니다. 프록시는 Runtime 중에 만들어지며 이를 Runtime Weaving이라고 표현합니다.

 

차이점은 JDK Dynamic Proxy는 Target 클래스가 Interface의 구현체이어야 하며, Reflection을 통해 Proxy를 만듭니다.

CGLib는 Interface 구현에 상관없이 일반 Class도 가능하며, 바이트 코드를 조작을 통한 상속으로 Proxy를 만들게 됩니다. 그래서 CGLib는 Target 클래스가 Final이면 예외가 발생하지만 성능면에선 JDK Dynamic Proxy보다 좋습니다. ( 구체적으로 왜 좋은지는 모르겠습니다. 후에 알게 되면 포스팅하겠습니다.)

 

 

JDK Dynamic Proxy 예시

interface ITargetInterface {
    void sayHi();
}

class TargetClass implements ITargetInterface {

    public void sayHi() {
        System.out.println("hi");
    }
}
class MyInvocationHandler implements InvocationHandler {

    private final Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("dynamic proxy 시작");
        Object result = method.invoke(target);
        System.out.println("dynamic proxy 종료");
        return result;
    }
}
@Test
void dynamicProxy() {
    ITargetInterface target = new TargetClass();
    MyInvocationHandler handler = new MyInvocationHandler(target);

    ITargetInterface proxy =
            (ITargetInterface) Proxy.newProxyInstance(ITargetInterface.class.getClassLoader(), new Class[]{ITargetInterface.class}, handler);

    proxy.sayHi();
    System.out.println("proxy = " + proxy.getClass());
}

Target은 Interface 구현체로 만들며 InvocationHandler를 구현한 클래스에 부가 로직을 작성합니다. 그리고 Proxy.newProxyInstance 메서드를 통해 프록시를 생성합니다.

결과

 

CGLib 예시

class TargetClass {

    public void sayHi() {
        System.out.println("hi");
    }
}
class MyMethodInterceptor implements MethodInterceptor {

    private final Object target;

    public MyMethodInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib proxy 시작");
        Object result = method.invoke(target);
        System.out.println("cglib proxy 종료");
        return result;
    }
}
@Test
void cglib() {
    TargetClass target = new TargetClass();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(TargetClass.class);
    enhancer.setCallback(new MyMethodInterceptor(target));
    TargetClass proxy = (TargetClass) enhancer.create();

    proxy.sayHi();
    System.out.println("proxy = " + proxy.getClass());
}

Target 클래스는 Interface를 구현할 필요 없습니다. MethodInterceptor 인터페이스를 구현한 클래스에 부가 로직을 작성하고 Enhancer 클래스를 통해 Proxy를 생성합니다.

'Spring' 카테고리의 다른 글

Spring Data Jpa의 N+1 문제  (0) 2022.02.07
Spring의 @Transactional  (0) 2021.12.15
Spring AOP  (0) 2021.12.02
Spring Boot에 Redis Cache 적용해보기  (0) 2021.11.25
Spring JPA Specification을 사용해 유연하게 조회 API 만들기  (3) 2021.11.08

댓글