Dynamic Proxy의 CTW, LTW, RTW은 각각 어떤 시점에 개입하는 것일까요

kkambbak1·2024년 1월 3일

https://www.youtube.com/watch?v=MFckVKrJLRQ

https://velog.io/@suhongkim98/AOP

Proxy

클라이언트로부터 타겟에 대신해서 요청을 받는 대리인

타겟은 프록시를 통해 최종적으로 요청받아 처리

타겟은 자신의 기능에만 집중 (프록시가 나머지 처리)

프록시의 사용목적

  1. 클라이언트가 타깃에 접근하는 방법을 제어
  2. 타깃에 부가적인 기능을 부여
public class Client{
	public static void main(String[] args) {
		Hello hello = new Hello();
		hello.sayHello("깜빡");
	}
}

public class Hello {
	public String sayHello(String name){
		return "Hello" + name;
 }

	public String sayThankyou(String name){
		return "Thank you" + name;
	}
}

client와 hello클래스 코드를 크게 손대지 않고 대문자로 출력하고 싶다.

어떻게 해야할까?

먼저 Hello를 인터페이스로 변경한다.

Dynamic Proxy

인터페이스를 필드로 가지고 있어야 한다

jdk프록시는 Method라는 reflection API를 사용한다.

리플렉션은 동적일 때 해결되는 타입을 포함하므로 JVM Optimization이 동작하지 않아 느리다

https://velog.io/@joosing/toby-spring-6-aop-history

public interface Hello{
	String sayHello(String name);
	String sayThankyou(String name);
}

public class HelloProxy implements Hello{
	private Hello hello;
	
	public HelloProxy(Hello hello){
		this.hello = hello;
	}

	@Override
	public String sayHello(String name){
		return hello.sayHello(name).toUpperCase();
	}

	@Override
	public String sayThankyou(String name){
		return hello.sayThankyou(name).toUpperCase();
	}
}
import java.lang.reflect.Method;

public class UpperCaseHandler implements InvocationHandler{
	private final Object target;

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

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
		if(method.getName().startsWith("say")){
			return ((String) method.invoke(target, args)).toUpperCase();
		}
		return method.invoke(target, args);
	}
}
@Test
public void dynamicProxy() {
		HelloTarget helloTarget = new HelloTarget();
    Hello hello = (Hello) Proxy.newProxyInstance(
            getClass().getClassLoader(),
            new Class[] { Hello.class }, // 프록시가 구현할 타겟 인터페이스 
            new UpperCaseHandler(helloTarget, "say") // 부가기능 구현
    );
    Assertions.assertEquals("HELLO TOBY", hello.sayHello("Toby"));
    Assertions.assertEquals("THANK YOU TOBY", hello.sayThankYou("Toby"));
}
  • 다이내믹 프록시가 만들어질 때 추가된 메서드가 자동으로 포함되고 UppercaseHandler.invoke() 메서드로의 연결을 수행하는 코드가 자동으로 만들어 질 것이고,

  • 부가기능은 Invoke() 메서드에서 처리 될 것이다.

  • 이제 부가기능을 담당하는 코드는 UppercaseHandler로 분리되었고, 다이나믹 프록시의 각 메서드에서 공유해서 사용된다.

JDK Dynamic Proxy의 특징

  1. JDK에서 지원하는 프록시 생성 방법을 사용하므로 외부에 의존적이지 않다.
  2. 리플렉션 API를 사용한다.
  3. 인터페이스가 반드시 있어야 한다.
  4. Invocation Handler를 재정의한 invoke()를 구현해야 한다.

CGLIB

바이트코드를 조작하여 프록시 객체를 생성해주는 코드 생성(Code gen)라이브러리

CGLIB의 특징

  1. 상속을 통한 프록시 구현
  2. 바이트 코드 조작으로 프록시 생성
  3. MethodInterceptor를 재정의한 intercept 구현해야 한다.

Weaving

모듈화한 부가 기능을 타겟에 적용해 핵심 기능과 연결하는 과정

RTW (Run-Time)

런타임 시 Proxy를 생성하여 기능을 추가하기 때문에 바이트 코드와 소스 코드 사이에 차이가 거의 없다.
메소드 호출에 대해서만 어드바이스를 적용 할 수 있다는 단점

CTW (Compile-Time)

특수한 컴파일러를 활용해 컴파일 과정에서 바이트 코드 조작을 통해 Advisor 코드를 직접 삽입

LTW (Load-Time)

java 파일을 컴파일한 결과물 자바 클래스가 JVM에 로드될 때 바이트 코드 조작

PCW

컴파일 후에 생성된 class파일을 바이트코드 조작을 통해 위빙

Spring AOP, AspectJ, Weaving

Spring AOP

스프링 컨테이너 안의 Bean에만 적용가능하다.

Spring AOP는 Dynamic Proxy 기반으로 기본적으로 RTW를 사용한다.

AspectJ에 비해 가볍지만, 대부분의 기능을 구현할 수 있다.

Spring AOP가 성능과 기능은 매우 부족하지만, Spring Bean에 자동으로 적용되고 설정하기 매우 편리하다.

AspectJ

AspectJ는 RTW를 사용할 수 없다. 대신 CTW, PCW를 지원한다.

LTW가 설정되지 않을 경우 AspectJ 컴파일러가 필요하다.

profile
윤성

0개의 댓글