Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

[동적프록시] JDK 동적프록시 - 소개 본문

인프런/[인프런] 스프링 핵심 원리 - 고급

[동적프록시] JDK 동적프록시 - 소개

Seung__ 2021. 12. 30. 16:31
  • 지금까지 프록시를 적용하기 위해 적용 대상의 숫자 만큼 많은 프록시 클래스를 만들었다. 
  • 적용 대상이 100개면 프록시 클래스도 100개 만들었다. 
  • 그런데 앞서 살펴본 것과 같이 프록시 클래스의 기본 코드와 흐름은 거의 같고, 
  • 프록시를 어떤 대상에 적용하는가 정도만 차이가 있었다. 쉽게 이야기해서 프록시의 로직은 같은데, 
  • 적용 대상만 차이가 있는 것이다.
  • 이 문제를 해결하는 것이 바로 동적 프록시 기술이다.
  • 동적 프록시 기술을 사용하면 개발자가 직접 프록시 클래스를 만들지 않아도 된다. 
  • 이름 그대로 프록시 객체를 동적으로 런타임에 개발자 대신 만들어준다. 
  • 그리고 동적 프록시에 원하는 실행 로직을 지정할 수 있다.
  • JDK 동적 프록시는 인터페이스를 기반으로 프록시를 동적으로 만들어준다. 
    따라서 인터페이스가 필수이다.

 

1. 예제 - 준비


  • JDK 동적 프록시를 이해하기 위해 아주 단순한 예제 코드를 만들어보자.
  • 간단히 A , B 클래스를 만드는데, JDK 동적 프록시는 인터페이스가 필수이다.
  • 따라서 인터페이스와 구현체로 구분
public interface AInterface {
    String call();
}
@Slf4j
public class AImpl implements AInterface{
    @Override
    public String call() {
        log.info("A 호출");
        return "A";
    }
}
public interface BInterface {
    String call();

}
@Slf4j
public class BImpl implements BInterface{
    @Override
    public String call() {
        log.info("B 호출");
        return "B";
    }
}

 

2. JDK 동적 프록시 InvocationHandler


  • JDK 동적 프록시에 적용할 로직은 InvocationHandler 인터페이스를 구련해서 작성하면된다.
  •  JDK  동적프록시가 제공하는 InvocationHandler
package java.lang.reflect;
public interface InvocationHandler {
  public Object invoke(Object proxy, Method method, Object[] args)
  throws Throwable;
}
  • Object proxy : 프록시 자신
  • Method method : 호출한 메서드
  • Object[] args : 메서드를 호출할 때 전달한 인수

 

3. 적용- InvocationHandler


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@Slf4j
public class TimeInvocationHandler implements InvocationHandler {

    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("TimeProxy 실행");
        
        long startTime = System.currentTimeMillis();
        Object result = method.invoke(target, args);
        long endTime = System.currentTimeMillis();

        long resultTime = endTime - startTime;
        log.info("TimeProxy 종료 resultTime = {}", resultTime);
        
        return result;
    }
}
  • TimeInvocationHandler 은 InvocationHandler 인터페이스를 구현한다. 
  • Object target : 동적 프록시가 호출할 대상
  • method.invoke(target, args) : 리플렉션을 사용해서 target 인스턴스의 메서드를 실행한다. 
    • args는 메서드 호출시 넘겨줄 인수이다.

 

4. 사용예시


@Slf4j
public class JdkDynamicProxyTest {

    @Test
    void dynamicA(){
        AInterface target = new AImpl();

        TimeInvocationHandler handler = new TimeInvocationHandler(target);

        //Object proxy = Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler);
        AInterface proxy = (AInterface) Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler);
        proxy.call();
        log.info("targetClass = {}", target.getClass());
        log.info("targetClass = {}", proxy.getClass());
    }


    @Test
    void dynamicB(){
        BInterface target = new BImpl();

        TimeInvocationHandler handler = new TimeInvocationHandler(target);

        //Object proxy = Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler);
        BInterface proxy = (BInterface) Proxy.newProxyInstance(BInterface.class.getClassLoader(), new Class[]{BInterface.class}, handler);
        proxy.call();
        log.info("targetClass = {}", target.getClass());
        log.info("targetClass = {}", proxy.getClass());
    }


}
  • new TimeInvocationHandler(target) : 동적 프록시에 적용할 핸들러 로직이다.
  • Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler)
    • 동적 프록시는 java.lang.reflect.Proxy 를 통해서 생성할 수 있다.
    • 클래스 로더 정보, 인터페이스, 그리고 핸들러 로직을 넣어주면 된다.
    • 그러면 해당 인터페이스를  기반으로 동적 프록시를 생성하고 그 결과를 반환한다.
  • newProxyInstance(ClassLoader, Instances, Handler)
    • loader – the class loader to define the proxy class ->Proxy의 클래스 정의
    • interfaces – the list of interfaces for the proxy class to implement -> Proxy가 구현하는 Interfaces
  • proxy.call()을 호출하면, InvocationHandler의 invoke()가 호출된다.
  • 이 때 handler에 현재 proxy와 호출된 메서드 call()이 인자로 넘어간다.
  • proxy의 역할은 handler를 호출하면서 메서드 정보, proxy를 넘겨주는 것.

 

실행 결과

16:09:25.331 [main] INFO hello.proxy.jdkdynamic.code.TimeInvocationHandler - TimeProxy 실행
16:09:25.338 [main] INFO hello.proxy.jdkdynamic.code.AImpl - A 호출
16:09:25.338 [main] INFO hello.proxy.jdkdynamic.code.TimeInvocationHandler - TimeProxy 종료 resultTime = 0
16:09:25.341 [main] INFO hello.proxy.jdkdynamic.JdkDynamicProxyTest - targetClass = class hello.proxy.jdkdynamic.code.AImpl
16:09:25.341 [main] INFO hello.proxy.jdkdynamic.JdkDynamicProxyTest - targetClass = class com.sun.proxy.$Proxy9
16:09:25.360 [main] INFO hello.proxy.jdkdynamic.code.TimeInvocationHandler - TimeProxy 실행
16:09:25.360 [main] INFO hello.proxy.jdkdynamic.code.BImpl - B 호출
16:09:25.360 [main] INFO hello.proxy.jdkdynamic.code.TimeInvocationHandler - TimeProxy 종료 resultTime = 0
16:09:25.360 [main] INFO hello.proxy.jdkdynamic.JdkDynamicProxyTest - targetClass = class hello.proxy.jdkdynamic.code.BImpl
16:09:25.360 [main] INFO hello.proxy.jdkdynamic.JdkDynamicProxyTest - targetClass = class com.sun.proxy.$Proxy10

 

 

5. 생성된 JDK 동적 프록시


  • proxyClass=class com.sun.proxy.$Proxy10이 부분이 동적으로 생성된 프록시 클래스 정보이다.
  • 이것은 우리가 만든 클래스가 아니라 JDK 동적 프록시가 이름 그대로 동적으로 만들어준 프록시이다. 
  • 이 프록시는 TimeInvocationHandler 로직을 실행한다.

 

6. 실행 순서


  • 클라이언트는 JDK 동적 프록시의 call() 을 실행한다.
  • JDK 동적 프록시는 InvocationHandler.invoke() 를 호출한다.
    • TimeInvocationHandler 가 구현체로 있으로 TimeInvocationHandler.invoke() 가 호출된다.
  • TimeInvocationHandler 가 내부 로직을 수행하고, method.invoke(target, args) 를 호출해서
    • target 인 실제 객체( AImpl )를 호출한다.
  • AImpl 인스턴스의 call() 이 실행된다.
  • AImpl 인스턴스의 call() 의 실행이 끝나면 TimeInvocationHandler 로 응답이 돌아온다.
    • 시간 로그를 출력하고 결과를 반환한다.

 

 

7. 정리


  • 예제를 보면 AImpl , BImpl 각각 프록시를 만들지 않았다. 
  • 프록시는 JDK 동적 프록시를 사용해서  동적으로 만들고 TimeInvocationHandler 는 공통으로 사용했다.
  • JDK 동적 프록시 기술 덕분에 적용 대상 만큼 프록시 객체를 만들지 않아도 된다. 
  • 그리고 같은 부가 기능  로직을 한번만 개발해서 공통으로 적용할 수 있다. 
  • 만약 적용 대상이 100개여도 동적 프록시를 통해서  생성하고, 
    각각 필요한 InvocationHandler 만 만들어서 넣어주면 된다.
  • 결과적으로 프록시 클래스를 수 없이 만들어야 하는 문제도 해결하고, 
    부가 기능 로직도 하나의 클래스에  모아서 단일 책임 원칙(SRP)도 지킬 수 있게 되었다.
  • JDK 동적 프록시 없이 직접 프록시를 만들어서 사용할 때와 JDK 동적 프록시를 사용할 때의 차이를
    그림으로 비교해보자.
  • 점선은 개발자가 직접 만드는 클래스가 아니다.

 

8. GitHub : 211230 JDK Dynamic Proxy


 

GitHub - bsh6463/Spring_Advanced_2

Contribute to bsh6463/Spring_Advanced_2 development by creating an account on GitHub.

github.com

 

Comments