반응형

프록시 패턴

  • 인터페이스를 구현을 통해 진짜 구현체를 대신하여 부가기능을 제공하거나 리턴 값을 변경할 수 있도록 대리자(surrogate)나 자리표시자(placeholder)의 역할을 하는 객체를 제공하는 패턴

각 클래스는 자신이 해야 할 일(SRP: Single Responsibility Principle)만 해야하는데 부가적인 기증을 제공할 때 이런 패턴을 주로 사용함

하지만 부가적인 기능을 추가할 때마다 위임하는 코드가 중복해서 발생할 수 있음

코딩 예시: https://bamdule.tistory.com/154

 

[디자인 패턴] 프록시 패턴 (Proxy Pattern)

1. 프록시 패턴이란? 실제 객체를 바로 이용하는 것 대신 가상 객체에 실제 객체를 선언하여 실제 객체의 기능과 추가적인 기능을 사용함으로 써 기능의 흐름을 제어하는 디자인 패턴입니다. 2.

bamdule.tistory.com

 


매번 클래스를 만드는게 아니라 동적으로 생성하는 방식

-> java reflextion을 활용한 다이내믹 프록시 사용

  • (컴파일 타임이 아닌) 런타임에 인터페이스를 구현하는 클래스/프록시 인스턴스를 만들어 사용하는 프로그래밍 기법

프록시 인스턴스 만들기

  • Object Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)
    • object로 리턴되기 때문에 type casting 필요
//BookService는 반드시 인터페이스여야 함
BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
        new InvocationHandler() {
            BookService bookService = new DefaultBookService();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("rent")) { //rent 라는 method에만 적용하고 싶다면
                    System.out.println("aaaa");
                    Object invoke = method.invoke(bookService, args);
                    System.out.println("bbbb");
                    return invoke;
                }
             //나머지 method는 그대로 리턴
                return method.invoke(bookService, args);
            }
        });

근데 매번 이렇게 구현하는 게 더 복잡, 유연한 구조가 아님.

그리고 클래스 기반의 (자바가 제공하는) proxy를 만들 수 없음.

-> 그래서 Spring AOP(프록시 기반의 AOP)가 등장

 


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

-> subclass를 만들어서 프록시를 만드는 라이브러리를 사용

 

CGlib lib 사용

  • https://github.com/cglib/cglib/wiki
  • 스프링, 하이버네이트가 사용하는 라이브러리
  • 버전 호환성이 좋지 않아서 서로 다른 라이브러리 내부에 내장된 형태로 제공되기도 한다.
MethodInterceptor handler = new MethodInterceptor() {
    BookService bookService = new BookService(); //class
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if(method.getName().equals("rent")){ //method 이름이 rent 일 때만 작업
            log.info("aaa");
            Objet invoke = method.invoke(bookService, args);
            log.info("bb");
            return invoke;
        }
        //나머지 method는 그대로 리턴
        return method.invoke(bookService, args);
    }
};

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

 

ByteBuddy lib 사용

  • https://bytebuddy.net/#
  • 스프링에서 버전 관리해줌
  • 바이트 코드 조작뿐 아니라 런타임(다이내믹) 프록시를 만들 때도 사용할 수 있다.
Class<? extends BookService> proxyClass = new ByteBuddy().subclass(BookService.class)
        .method(named("rent")) //method 이름이 rent면 적용
        .intercept(InvocationHanderAdaptor.of(new InvocationHander(){
            BookService bookService = new BookService();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
                log.info("aaa");
                Object invoke = method.invoke(bookService, args);
                log.info("Bbb");
                return invoke;
            }
        }))
        .make().load(BookService.class.getClassLoader()).getLoaded();
BookService bookService = proxyClass.getConstructor(null).newInstance();

 

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

  • 상속(extends)을 사용하지 못하는 경우 프록시를 만들 수 없음
    • private 생성자(하위 클래스에서 항상 부모의 생성자를 호출한다)
    • final 클래스
  • 인터페이스가 있을 때는 인터페이스의 프록시를 만들어 사용할 것.

 

다이내믹 프록시 기법이 사용되는 곳

  • 스프링 데이터 JPA
  • 스프링 AOP
  • Mockito
  • 하이버네이트 lazy initialization(@OneToMany 등 entity 들고 있을 때 프록시 객체로 처음에 리턴)

 

728x90
반응형

'architecture > sw architecture' 카테고리의 다른 글

람다 아키텍처 vs 카파 아키텍처  (0) 2023.03.22

+ Recent posts