동적 프록시 기술

ParkIsComing·2023년 12월 12일

Spring

목록 보기
17/21

리플렉션

리플렉션이란?

  • 리플렉션 : 구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
  • 런타임 시점에 동적으로 정보를 추출한다.
  • 리플렉션을 통해 얻은 클래스나 메서드의 메타정보를 사용해서 동적으로 호출하는 메서드를 변경할 수 있다.

리플렉션 사용하기

Hello라는 이름의 클래스로부터 정보를 얻어보자.



Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello");

위와 같이 클래스 메타 정보를 획득할 수 있다.

Method methodCallA = classHello.getMethod("callA");

클래스 메타 정보로부터 클래스 내의 메서드의 메타 정보도 획득할 수 있다.

리플렉션 사용해보기


위와 같이 메타정보를 이용하여 호출할 메서드를 동적으로 제공하는 리플렉션을 구현할 수 있다. method는 메서드의 메타정보, target은 실제 실행할 인스턴스 정보이다.

리플렉션의 한계

  • 리플렉션 기술은 런타임에 동작하기 때문에, 컴파일 시점에 오류를 잡을 수 없기 때문에 가급적이면 사용을 지양하자. (컴파일 시점에 오류를 잡는 것이 가장 이상적이다.)

JDK 동적 프록시

개념

  • JDK 동적 프록시는 인터페이스를 기반으로 프록시를 동적으로 만들어준다. (클래스는 x)

  • 다이나믹 프록시 사용처

    • Spring Data JPA
    • Spring AOP
    • Mockito
    • Hibernate Lazy Initialization

사용하기

  • java.lang.reflect.InvocationHandler를 구현하여 동적 프록시를 사용할 수 있다.
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}


Proxy의 newProxyInstance() 메서드를 호출하면 프록시 객체를 생성할 수 있다.이때 OrderControllerV1은 당연히 인터페이스이다.

이때 newProxyInstance() 메서드를 들여다보면 세가지의 매개변수를 받는 것을 알 수 있다.

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h) {
        Objects.requireNonNull(h);

        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);
 }
  • ClassLoader loader
    • 프록시 객체가 구현할 interface에 getClassLoader()로 ClassLoader를 얻어오는 것이 일반적이다.
  • Class<?>[] interfaces
    • 프록시 클래스가 구현하고자 하는 인터페이스 목록을 배열로 받는다
  • InvocationHandler h
    • 프록시 메서드(invoke)가 호출되었을 때 실행할 핸들러 메서드
    • 리플렉션을 이용해 메서드 정보와 메서드에 전달된 인자를 invoke()의 인자로 받게 됨

스프링 빈을 등록할 때 다음과 같은 구조로 프록시를 동적으로 만든다.

CGLIB

앞서 알아본 JDK 동적 프록시는 인터페이스가 있어야 프록시를 만들 수 있다.

인터페이스가 없으면 적용이 불가능하다.

이때 클래스만을 이용해 동적프록시 적용하려면 CGLIB를 사용해야 한다.

CGLIB란?

  • 바이트코드를 조작해서 동적으로 클래스를 생성하는 기술을 제공하는 외부 라이브러리이나 스프링 프레임워크가 스프링 내부 소스 코드에 포함하고 있다.
  • 참고로 CGLIB 깃헙 레포지토리를 확인해보니 최신 JDK, 특히 JDK17+에서는 제대로 작동하지 않는다고 한다.

0개의 댓글