자바 - 동적 프록시(proxy) 기술( JDK 동적 프록시)

SeungTaek·2021년 11월 1일
0

자바(Java)

목록 보기
8/8
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
잘못된 내용이 있으면 댓글로 알려주세요!


📒 리플렉션 사용 예제

  • 자바 리플랙션은 여기에 잘 정리되어 있다.
  • 프록시에 대한 글은 여기에 잘 정리되어 있다.
  • 본 게시물의 목적은 리플렉션을 이용해 동적 프록시를 사용하는것이다.
    • 간단하게 리플렉션이 어떻게 사용되는지 예제를 통해 알아보자.

🎈 리플렉션 사용 전

void reflection(){
    Hello target = new Hello();

    //공통 로직1 시작
    log.info("start");
    String result1 = target.callA();
    log.info("result={}", result1);
    //공톨 로직1 종료

    //공통 로직2 시작
    log.info("start");
    String result2 = target.callB();
    log.info("result={}", result2);
    //공톨 로직2 종료
}

@Slf4j
static class Hello{
    public String callA(){
        log.info("callA");
        return "A";
    }
    public String callB(){
        log.info("callB");
        return "B";
    }
}
  • reflection에서 두 공통 로직내에서 서로 다른 점은 호출하는 메서드가 다르다는 점이다.
    • target.callA(), target.callB()

그럼 메서드 호출 부분만 동적으로 처리할 수 있다면 반복되는 코드의 양을 줄일 수 있겠구나!


🎈 리플렉션 사용 후

void reflection() throws Exception {
    //클래스 정보
    Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello");

    Hello target= new Hello();

    Method methodCallA = classHello.getMethod("callA");
    dynamicCall(methodCallA, target);

    Method methodCallB = classHello.getMethod("callB");
    dynamicCall(methodCallB, target);
}


private void dynamicCall(Method method, Object target) throws Exception {
    log.info("start");
    Object result = method.invoke(target);
    log.info("result={}", result);
}
  • Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello"): 클래스 메타 정보를 획득한다. 이때 클래스 위치를 적어주고, 내부 클래스는 구분을 위해 $을 사용한다.

  • classHello.getMethod( {메소드명} ): 클래스 메타 정보를 이용해 메소드 메타 정보를 획득한다. 파라미터로 호출하려는 메서드 이름을 적어준다.

  • 호출할 메서드 정보와 인스턴스를 dynamicCall에게 전달하여 공통 로직을 실행한다.

    • method.invoke( {인스턴스} ): 인스턴스를 넘겨주며, 해당 인스턴스의 메서드를 실행한다.

위 예제에서 배운 리플렉션을 이용해 JDK 동적 프록시를 사용해보자




📒 JDK 동적 프록시

  • 동적 프록시 기술을 사용하면 개발자가 직접 프록시 클래스를 만들지 않아도 된다.
  • 단, 인터페이스를 기반으로 프록시를 동적으로 만들어주기 때문에, 인터페이스가 필수이다.

📌 예제

  • 🎈 인터페이스 생성
public interface AInterface {
    String call();
}

  • 🎈 구현체 생성
@Slf4j
public class AImpl implements AInterface {
    @Override
    public String call() {
      log.info("A 호출");
      return "a";
    }
}

  • 🎈 JDK 동적 프록시는 InvocationHandler인터페이스를 구현해 핸들러 객체를 리턴받는다.
    • invoke(Object proxy, Method method, Object[] args)
      • Object proxy: 프록시 자신
      • Method method: 호출할 메서드
      • Object[] args: 메서드를 호출할 때 전달한 인수
@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;
    }
}
  • method.invoke(target, args): 리플렉션을 사용해서 target인스턴스의 메서드를 실행한다. args는 메서드 호출시 넘겨줄 인수이다.


🎈 실행 테스트

  • 동적 프록시는 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)를 통해 만들 수 있다.
    • ClassLoader loader: 클래스 로더 정보
    • Class<?>[] interfaces: 인터페이스
    • InvocationHandler h: 핸들러 로직
void dynamicA(){
    AInterface target = new AImpl();
    TimeInvocationHandler handler = new TimeInvocationHandler(target);

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

//실행결과
TimeInvocationHandler - TimeProxy 실행
AImpl - A 호출
TimeInvocationHandler - TimeProxy 종료 resultTime=0
  • 프록시가 동적으로 만들어지고, 실행된 것을 알 수 있다.
    • dynamicA -> proxy -> AImpl



📌 JDK 동적 프록시의 한계

  • JDK 동적 프록시는 인터페이스가 필수이다.
  • 그럼 인터페이스 없이 클래스만 있는 경우에는 어떻게 동적 프록시를 적용할 수 있을까?
    • 다음 게시물에서 배울 CGLIB라는 바이트코드를 조작하는 라이브러리를 사용해야 한다.

인프런의 '스프링 핵심 원리 고급편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요.

profile
I Think So!

0개의 댓글