다이나믹 프록시

이연중·2021년 1월 24일
1

JAVA

목록 보기
6/20
post-thumbnail

런타임 시점에 특정 인터페이스들을 구현하는 클래스, 인스턴스를 만드는 것

스프링 데이터 JPA의 동작

  • Spring AOP를 기반으로 동작하여 RepositoryFactorySupport에서 프록시를 생성

프록시 패턴

  • 프록시와 리얼 서브젝트가 공유하는 인터페이스가 있고, 클라이언트는 해당 인터페이스 타입으로 프록시를 사용한다
  • 클라이언트는 프록시를 거쳐 리얼 서브젝트를 사용하기에 프록시는 리얼 서브젝트에 대한 접근을 관리하거나 부가기능을 제공하거나 리턴값을 변경할 수 있다
  • 리얼 서브젝트는 자신이 해야할 일만 하면서 SRP 프록시를 사용해 부가적인 기능(접근 제한, 로깅, 트랜잭션 등)을 제공할 때 이런 패턴을 주로 사용한다

프록시 인스턴스 만들기

  • 프록시 패턴의 문제점:
  1. 부가적인 기능 추가때마다 별도의 프록시를 만들어야 함
  2. 프록시로 프록시를 감싸는 일 발생
  3. 모든 구현체에서 원래 타겟으로 위임하는 코드가 중복해서 발생
  4. 다른 메소드의 부가 기능이 같은 것일 경우 부가 기능의 중복이 발생
    => 이러한 문제를 해결하기 위해 즉, 프록시에 해당하는 클래스를 매번 만드는 것이 아닌 동적으로 런타임에 생성해내는 "다이나믹 프록시" 등장
  • Object Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)

  • BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[] {Bookservice.class}, //반드시 인터페이스 타입이어야 함. 클래스 타입은 안됨
    new InvocationHandler(){
    	BookService bookService = new DefaultBookService();
    	@Overrid
    	public Object invoke(Object proxy, Method method, Obhect[] args) throws Throwable{
    		if(method.getName().equals("rent")){
    			System.out.println("aaa");
    
    			Object invoke = method.invoke(bookService, args);
    
    			System.out.println("bbb");
    
    			return invoke;
    		}
    		return method.invoke(bookService, args);
    	}
    });

클래스의 프록시가 필요하다면?

위 코드의 주석에서 언급한 바와 같이, 클래스 배열에서 클래스를 넘겨줄때 반드시 전달 클래스는 인터페이스 타입이어야만 한다고 했다. (자바에서 제공하는 다이나믹 프록시의 제약점)

이번에는 인터페이스가 없는 경우의 프록시 생성 방법에 대해 알아보고자 한다

서브 클래스를 만들 수 있는 라이브러리를 사용하여 프록시를 만들 수 있음

CGlib

  • https://github.com/cglib/cglib/wiki

  • 스프링, 하이버네이트가 사용하는 라이브러리

  • 버전 호환성이 좋지 않아 서로 다른 라이브러리 내부에 내장된 형태로 제공되기도 함

dependency 추가

 <dependency>
 	<groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
 </dependency>

프록시 생성

MethodInterceptor handler = new MethodInterceptor() {
    BookService bookService = new BookService();
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return method.invoke(bookService, objects);
    }
};

BookService bookService = (BookService) Enhancer.create(BookService.class, handler);

ByteBuddy

  • https://bytebuddy.net/#/
  • 바이트 코드 조작뿐 아니라 다이나믹 프록시를 만들 때도 사용할 수 있음

프록시 생성

Class<? extends BookService> proxyCLass = new ByteBuddy().subClass(BookService.class).method(named("rent")).intercept(InvocationHandlerAdapter.of(new InvocationHandler(){
	BookService bookService = new BookService();
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable
    	{
        	System.out.println("aaa");
        	Object invoke = method.invoke(bookService, args);
        	System.out.println("bbb");
        	return invoke;
    	}
	}))
    .make().load(BookServie,class.getClassLoader()).getLoaded();
	proxyClass.getConstructor(null).newInstance();

서브 클래스를 만드는 방법의 단점

  • 상속을 사용하지 못하는 경우 프록시를 만들 수 없다
    • Private 생성자만 있는 경우
    • Final 클래스인 경우
  • 인터페이스가 있을 때는 인터페이스의 프록시를 만들어 사용해야 함

다이나믹 프록시 정리

다이나믹 프록시

  • 런타임에 인터페이스 또는 클래스의 프록시 인스턴스 또는 클래스를 만들어 사용하는 프로그래밍 기법

다이나믹 프록시 사용처

  • 스프링 데이터 JPA
  • 스프링 AOP
  • Mockito
  • 하이버네이트 lazy initialization

참고

https://www.inflearn.com/course/the-java-code-manipulation

profile
Always's Archives

0개의 댓글