본문 바로가기

JAVA

Proxy 객체

 

더보기

Proxy vs. Proxy Pattern

Proxy Pattern : 실제로 Target에 대한 기능을 확장하거나, 추가하지 않음. 타깃에 접근하는 방식을 변경해주는 역할

Proxy : 실제 Target의 기능을 수행하면서 기능을 확장하거나 추가하는 실제 "객체"를 의미

 

 

 

java에서는dynamic proxy와 CGLib를 제공합니다.

만약 타깃이 하나 이상의 인터페이스를 구현하고 있는 클래스라면 JDK Dynamic Proxy의 방식으로 생성되고

인터페이스를 구현하지 않은 클래스라면 CGLIB의 방식으로 AOP Proxy를 생성해줍니다.

 

 

 

1. dynamic proxy

dynamic proxy의 경우 Proxy클래스를 Java에서 만들어 주기 때문에 Proxy 클래스 내부를 직접 구현 할 수가 없습니다.

따라서, 무슨 일을 할지 알려주기 위한 방법이 필요합니다.

필요한 코드를 InvocationHandler를 implements 하여 InvocationHandlerinvoke() 메소드에

Proxy로 호출되는 모든 메소드에 대해 응답하는 역할을 맡도록 구현 후 Proxy.newProxyInstance의 파라미터로 넘겨줍니다.

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 {
        logger.info("{} executed in {}",method.getName(), target.getClass().getCanonicalName());
        return method.invoke(target,args);
    }
}

 

 

class CalculatorImpl implements Calculator{ //target class

    @Override
    public int add(int a, int b) {
        return a+b;
    }
}

interface Calculator {
    int add(int a, int b);
}

 

 

//실행
Calculator proxyInstance = (Calculator) Proxy.newProxyInstance(
                LoggingInvocationHandler.class.getClassLoader(),
                new Class[]{Calculator.class},
                new LoggingInvocationHandler(calculator));

var result = proxyInstance.add(1, 2);
logger.info("Add -> {}",result);

Proxy 패턴은 접근제어의 목적으로 Proxy를 구성한다는 점도 중요하지만, 사용자 입장에서 기존 target을 이전과 동일하게 사용할 수 있도록 위임 코드를 Proxy 객체에 작성해줘야 합니다.위임코드는 InvocationHandler에서 작성합니다.

이렇게 InvocationHandler를 implement한 객체를 파라미터로 주입하는 방식으로 객체를 생성해주는 과정은 다음과 같습니다.

  1. 타깃의 인터페이스를 자체적인 검증 로직을 통해 ProxyFactory에 의해 타깃의 인터페이스를 상속한 Proxy 객체 생성
  2. Proxy 객체에 InvocationHandler를 포함시켜 하나의 객체로 반환

 

- java reflection

Java reflecton은 구체적인 class type을 몰라도 그 class의 method, type, variables에 접근할 수 있게 해주는 기능입니다.

구체적인 class type을 알지못하지만 동적으로 instance를 생성해야 할 때 사용하는 기능입니다.

 

Java class file들은 binary code로 컴파일되어 static 영역에 존재하기 때문에 class type을 모르더라도 class명을 알면 class를 찾을 수 있습니다.

//임의의 클래스를 가져오는 방법
Class c = "StringTypeTest".getClass();
System.out.println(c);      //class java.lang.String

 

JDK Dynamic Proxy는 Java reflection을 사용해 target class의 method를 호출하는데, 이는 성능 저하를 야기한다.

또한, target class의 모든 메서드에 적용되지 않을지라도, JDK Dynamic Proxy는 일단 호출하고 이후에 Advice되는 method인지 아닌지를 판단한다. 따라서, 더욱 성능이 저하된다.

 

JDK Dynamic Proxy를 정리하면 이렇다.

  1. interface를 구현하는 class들만 target이 될 수 있다.
  2. Java reflection을 사용해 target class의 method를 호출하며, Advise대상이든 아니든 모든 method call마다 reflection을 실시하므로 성능이 떨어진다.

 

 

 

 

2. CGLib(Code Generator Library)

CGLib은 Code Generator Library의 약자로, 클래스의 바이트코드를 조작하여 Proxy 객체를 생성해주는 라이브러리입니다.

Spring은 CGLib을 사용하여  interface의 유무와 상관 없이 타깃의 클래스에 대해서도 Proxy를 생성해줍니다.

CGLib은 Enhancer라는 클래스를 통해 Proxy를 생성합니다.

Enhancer enhancer = new Enhancer();
         enhancer.setSuperclass(CalculatorImpl.class); // 타깃 클래스
         enhancer.setCallback(MethodInterceptor);     // Handler
Object proxy = enhancer.create(); // Proxy 생성

다음과 같이 CGlib은 타깃의 클래스를 상속받아 다음 그림과 같이 Proxy를 생성해줍니다.

이 과정에서 CGLib은 타깃 클래스에 포함된 모든 메소드를 재정의하여 Proxy를 생성해줍니다.

 

CGLIB 프록시는 Target Class를 상속받아 생성되기 때문에 Proxy 생성을 위해 Interface를 만드는 수고를 덜 수 있습니다.

하지만, 상속을 이용하는 만큼 final이나 private와 같이 상속된 객체에 오버라이딩을 지원하지 않는 경우 Proxy에서 해당 메소드에 대한 Aspect를 적용할 수 없습니다.

CGLIB Proxy의 경우 Java reflection이 아닌 bytecode를 사용하여 Dynamic Proxy보다는 퍼포먼스가 상대적으로 빠른 장점이 있습니다. 이러한 방식은 한 번의 method 호출 후에는 생성된 bytecode를 재사용할 수 있어 실행 속도가 향상됩니다.

 

 

 

3. 정리

proxy 객체를 생성하는 방법으로는 Dynamic Proxy와 CGLib 2가지 방식이 있음

 

Dynamic Proxy

- 인터페이스를 구현하는 class들만 target이 될 수 있음

- Java reflection을 실시하므로 성능이 떨어진다.

 

CGLib 

- 인터페이스가 없어도 Proxy 사용 가능

- 클래스를 상속받아 Proxy를 생성

- bytecode 사용해 target class의 method 호출

 

 

 

 

 

 

Reference

https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch08s06

https://gmoon92.github.io/spring/aop/2019/04/20/jdk-dynamic-proxy-and-cglib

https://gyrfalcon.tistory.com/entry/Java-Reflection

https://dreamchaser3.tistory.com/9

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

SpringBoot Controller 매개변수 애너테이션의 종류  (0) 2021.10.05
DAO와 repository의 차이  (0) 2021.09.14
proxy 패턴  (0) 2021.09.07
디자인 패턴  (0) 2021.08.06
Object 클래스 - 메서드  (0) 2021.08.05