다이나믹 프록시

김민지·2022년 10월 21일
0

자바

목록 보기
10/21

프록시? 다이나믹프록시?

  • 프록시와 리얼 서브젝트가 공유하는 인터페이스가 있고, 클라이언트는 일단 프록시를 사용한다. 그리고
    프록시에서 리얼서브젝트라는 필드를 가지고 있어서 그 필드를 사용해서 리얼서브젝트 전후에 어떠한 작업처리를 해주거나 그 값이나 메서드를 변경해줄수있다.
  • 하지만 이런형태는 매번 프록시 객체를 만들어줘야하는 번거로움이 있다.
  • 매번 인터페이스를 만들고 그 인터페이스에 대한 메서드를 구현해야한다.
  • 그리고 구현하지 않아도 되는 메서드가 있다면 쓸데없는 중복들이 발생하게 된다.
    그래서 다이나믹 프록시라는 개념이 등장했다 다이나믹 프록시는 위의 1.매번인터페이스 구현 2.중복발생 의 문제를 해결해주게 된다.
  • 다이나믹 프록시는 런타임에 생성되는 프록시 객체를 의미한다.
  • 다이나믹 프록시는 리플렉션을 사용한다

프록시를 만드는 방법

  1. 클래스에 인터페이스를 구현한 경우 => '다이내믹 프록시' 기술을 사용 (자바의 리플렉션 이용)
  2. 클래스에 인터페이스를 구현 안한 경우 => 'CGLIB' 기술을 사용 (바이트 코드 조작)
  • 리플렉션: 클래스의 구체적인 타입을 몰라도 그 클래스에 접근할수있도록 해주는 기술
    바이트 코드 조작으로 클래스 로딩 직전에도 클래스 파일만 있으면, 정보를 빼오거나 새로운 클래스를 만드는 등 여러가지를 할 수 있음
  • 바이트코드 조작: Reflection을 사용하지 않고, Extends(상속) 방식을 이용해서 Proxy화 할 메서드를 오버라이딩 하는 방식입니다.

리플렉션이 가능한 이유

자바에서는 JVM이 실행되면 사용자가 작성한 자바 코드가 컴파일러를 거쳐 바이트 코드로 변환되어 static 영역에 저장됩니다.
Reflection API는 이 정보를 활용합니다. 그래서 클래스 이름만 알고 있다면 언제든 static 영역을 뒤져서 정보를 가져올 수 있는 것입니다.

다이나믹 프록시

자바에서 다이나믹 프록시를 만드는 방법

 ParentInterface parentInterface = (ParentInterface)Proxy.newProxyInstance(HelloApplication.class.getClassLoader(), new Class[]{ParentInterface.class}, (proxy, method, args) -> {
            System.out.println("메소드 수행전에 할일");
            Object methodResult = method.invoke(new ChildClass(), args);//child라는 class에 args를 모두 넘긴다
            if(method.getName() == "sayHello"){//메서드 이름에 따라.. 처리를 분류할 수 있으니 다 정의하지 않아도됨 모든 메서드에 대해 기본적으로 정의되는거니까 중복도 피할 수 있음
                System.out.println("sayHello!");
                return methodResult;
            }
            System.out.println("sayOne!");
            return methodResult;

        });
        parentInterface.sayHello();
        //클라이언트는 인터페이스타입에 대해 그 메서드를 호출한다 이 인터페이스에 대한 메서드를 호출하면 위에서 정의한대로 프록스 객체를 런타임에 하나 만들어줘서
        //위에서 정의한 대로의 로직을 타고 클라이언트에게 결과를 전송해준다
        parentInterface.sayOne();
  • 하지만 이 코드도 newProxyInstance를 넘겨줄때 부가기능이 너무 커지면 세번째 인자로 넘겨줄때 너무 커질 수 있다.
  • 클래스기반의 프록시를 만들지 못한다. 인터페이스가 없고 클래스만 있다면 프록시를 어떻게 만들것인가?

리플렉션이 어떻게 활용됐다는거야?

  • https://taes-k.github.io/2021/05/15/dynamic-proxy-reflection/
    을참고해보자
  • Proxy 객체는 리플렉션을 통해 넘겨준 인터페이스 정보를 가져와서 클래스 바이트 파일을 만들어주고 있다.
  • 리플렉션에 대한 api를 사용하고 있으니 리플렉션을 활용한다고 볼 수 있다.

CGLIB, BYTEBUDDY

스프링 aop는 자바가 제공하는 다이나믹 프록시만 제공하는게 아니라 스프링빈중에 인터페이스를 상속받지않는
클래스에대해서도 프록시를 적용해야한다하면 cglib을 사용

@Test
    void makeClassProxy() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<? extends Class1> proxyClass = new ByteBuddy().subclass(Class1.class)
                .method(named("sayHello")).intercept(InvocationHandlerAdapter.of(new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("aaa");
                        Object result = method.invoke(new Class1(), args);
                        System.out.println("bbb");
                        return result;
                    }
                }))
                .make().load(Class1.class.getClassLoader()).getLoaded();
        Class1 class1 = proxyClass.getConstructor(null).newInstance();
        class1.sayHello();
    }
  • BYTEBUDDY나 DBLIB이나 모두 클래스를 상속받아서 구현하는거기때문에 상속을 못하는 클래스면 이 방법을 쓸 수 없다.
    근데 이것도 단점이 존재한다 일부 클래스 중에 상속을 허용하지 않는 클래스들이 있다
    클래스에 final같은것을 붙이면 subclass를 만들 수 없으니까 안된다
    그리고 private 한 생성자를 붙이면 상속을 받지못하니 이또한 안되는 클래스들중 하나라고 할 수 있다

다이나믹 프록시를 어디서 사용하는가?

  • 스프링 데이터 JPA
  • 스프링 AOP
  • Mockito : 다이나믹하게 런타임에 인자로 넣어주는 클래스 타입의 객체를 생성해서 넣어준다
  • 하이버네이트 지연초기화 : onetomany같은 설정이 되어있을때 예를들면 팀과 멤버의 일대다 관계가 있을때 팀하나를 불러올때 팀의 필드인 멤버리스트의 값은 불러오지않는다. 그렇다고 null은 아니다. 프록시객체가 들어있다

Spring AOP

AOP는 관점지향 프로그래밍이라는으로 "기능을 핵심 비즈니스 기능과 공통기능으로 '구분’하고,모든 비즈니스 로직에 들어가는 공통기능의 코드를 개발자의 코드 밖에서 필요한 시점에 적용하는 프로그래밍 방식입니다.
Spring 에서 AOP 구현체들을 제공해주고 있는데, JDK Dynamic Proxy 와 CGlib 을 통해 Proxy화 합니다.


출처
https://2step-hyun.tistory.com/117

profile
안녕하세요!

0개의 댓글