# Proxy / JDK Dynamic Proxy / Code Generator Library(CGLIB) # 배경 - 토비의 스프링 책으로 AOP를 학습하다가 막혔는데 알고보니 프록시 대해서 잘 몰라서 막힌다는 걸 알게 되어 프록시에 관한 내용들을 글로 정리하면서 학습하고자 한다. # 다루는 내용 - Proxy - 프록시 패턴 - 동적 프록시 - JDK Dynamic Proxy - Code Generator Library(CGLIB) - Proxy Factory Bean ## Proxy **특정 객체에 대한 접근을 제어하거나 기능을 추가할 수 있는 객체를 의미한다.** - Client로부터 Target을 대신해서 요청을 받는 **대리인** - 실제 오브젝트인 Target은 Proxy를 통해 최종적으로 요청받아 처리한다. - 그 결과, Target은 자신의 기능에만 집중하고 **부가적인 기능**은 Proxy**에게 위임**한다. > Client → Proxy → Target > ### Proxy 사용 목적 두 가지로 구분할 수 있다. - 1. 클라이언트가 Target에 **접근**하는 방법을 **제어**하기 위함 ex) JPA Hibernate 지연로딩 - 지연로딩은 어떻게 동작하나? 실제로 필요하기 전까지, 연관 관계로 가지고 있는 엔티티 필드에 null 값을 넣어둘 순 없으니 프록시 객체를 주입해서 실제 객체가 들어있는 것처럼 동작하도록 한다. - 프록시 객체는 **실제 객체를 상속한 타입**을 가지고 있기 떄문에 문제없이 동작한다. - 2.Target에 **부가적인 기능**을 주기 위함 ex) `@Transactional` - 어떻게? Reflection API, 바이트 코드 조작 등의 방법으로 프록시를 구현해 부가적인 기능을 준다. ### Proxy를 직접 구현한다면? - Proxy 패턴을 통해서 Proxy를 직접 구현할 수 있다. ### Proxy 패턴 - 특정 객체에 대한 **접근을 제어**하거나 **부가기능**을 구현하는데 사용하는 패턴 ![출처: https://en.wikipedia.org/wiki/Proxy_pattern](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/25aa8da3-48ed-486b-906b-7dd45ec4368f/Untitled.png) 출처: https://en.wikipedia.org/wiki/Proxy_pattern ### Proxy 패턴을 사용함으로써 얻는 장점 **기능 추가, 접근 제어 등 다양하게 활용할 수 있다.** - OCP(Open Closed Principle) 원칙을 지킨다. - 기존 Target 코드를 변경하지 않고 새로운 **기능을 추가**할 수 있다. - SRP(Single Responsibility Principle) 원칙을 지킨다. - Validation 처리와 같은 부가적인 기능은 분리시킴으로써 기존 Target 코드가 해야하는 역할만 **유지**할 수 있다. ### Proxy 패턴을 사용함으로써 얻는 단점 - 코드의 복잡도 증가 - 프록시패턴을 구현하려면 인터페이스를 구현해야하고 또한 프록시 객체를 생성해서 구현해줘야 한다. - 중복 코드 발생 - 부가기능을 구현할 때 모든 메서드에 적용해줘야 하기 때문에 중복 코드가 발생한다. > **프록시 패턴의 문제점을 동적 프록시를 통해 해결할 수 있다.** > --- ## 동적 프록시(Dynamic Proxy) 동적 프록시는 말그대로 동적인 시점(런타임 시점)에 프록시를 자동으로 만들어서 적용해주는 기술이다. ### 동적 프록시 기술이 만들어진 배경 - 프록시 패턴의 문제점으로 동적 프록시 기술이 만들어졌다. - 프록시를 적용해야 할 클래스가 수십 개에서 수백 개가 되면? - 클래스를 그 개수만큼 반복해서 만들어주어야 하는데 이런 불편함을 해소하기 위해 나온 기술 ### 스프링에서 동적 프록시는 어떻게 동작할까? - 클라이언트가 메소드를 요청하면 Proxy Factory Bean 이라는 곳에서 인터페이스 유무를 확인하여 - 인터페이스가 있으면 JDK Dynamic Proxy 방식으로 프록시를 생성하고, - 인터페이스가 없으면 CGLIB의 방식으로 프록시를 생성한다. ## JDK Dynamic Proxy 동적 프록시의 한 종류이다. - 프록시 클래스를 직접 구현하지 않아도 된다. - 코드 복잡도 해소 - **`Invocatio Handler`**를 ******사용해서 중복 코드 제거를 제거한다. - `Invocation Handler`는 **Reflection API**를 사용한다. ### **Invocation Handler** - 실제 객체인 Target의 필드를 반드시 가지고 있어야 한다. → **Target에 의존적이다.** - 왜 가지고 있어야할까? 부가기능을 수행하기 위해서 - Target A, Target B, Target C … 에 맞는 빈을 매번 생성해줘야 한다. ### **Reflection API** - 구체적인 클래스 타입을 몰라도 런타임에 클래스의 정보에 접근할 수 있게 해주는 자바 API ### Reflection API 단점 - JVM 최적화가 동작하지 않아 성능상 **느리다.** - JVM 최적화란? JVM이 실행하는 프로그램의 성능을 개선하기 위해 시스템 자원을 효율적으로 사용하도록 설정하는 것 - 최적화 방법 - 코드 변경: 불필요한 변수 할당을 제거하거나 반복문 내 임시 변수 사용을 없애면서 개발자가 작성한 코드를 직접 수정한다. - JVM 설정 파일 변경: 메모리 제한, GC 동작, JIT컴파일러 동작 등을 조절할 수 있다. - 메모리 최적화: 힙 메모리 크기 조절, GC 동작 설정, 클래스 로딩 최적화 등을 통해 메모리를 최적화할 수 있다. - 코드 컴파일링: 어떤 메소드가 자주 호출되는지, 어떤 메소드가 오랜 시간을 차지하는지 등을 판별하면서 성능 저하가 발생하는 원인을 파악하고 최적화 작업 수행 ![출처: https://docs.oracle.com/javase/tutorial/reflect/](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/25921f0e-535d-44b3-87b6-4e6c7cf3d573/Untitled.png) 출처: https://docs.oracle.com/javase/tutorial/reflect/ ### JDK Dynamic Proxy 사용 흐름 > Client → JDK Dynamic Proxy → Invocation Handler → Target > - 클라이언트가 메소드를 요청하면 JDK Dynamic Proxy는 메소드 처리를 `Invocation Handler`에게 위임한다. - `Invocation Handler`는 부가기능을 수행하고, 이후 Target의 로직을 처리하기 위해 Target에게 위임한다. ### JDK Dynamic Proxy 특징 정리 - JDK에서 지원하는 프록시 생성 방법 - Reflection API를 사용한다. → 외부 라이브러리에 의존하지 않지만 JVM 최적화가 동작하지 않아 느리다. - **인터페이스**가 반드시 있어야 한다. - `Invocation Handler`를 재정의한 **invoke**를 구현해줘야 **부가기능이 추가**된다. ### 스프링의 프록시 구현 - ProxyFactoryBean - 스프링은 일관된 방법으로 프록시를 만들 수 있게 도와주는 추상 레이어를 제공 - JDK Dynamic Proxy를 **추상화**해서 적절한 방식의 프록시를 생성하도록 도와주는 팩토리 클래스다. - 프록시 오브젝트를 생성해주는 기술을 추상화한 팩토리 빈을 제공 - ProxyFactoryBean : 프록시를 생성해서 빈 오브젝트로 등록하게 해주는 팩토리 빈 - 순수하게 프록시 생성을 위한 작업만을 담당하고, 부가 기능은 별도의 빈에 둘 수 있음 ### ProxyFactoryBean 특징 - target의 인터페이스 정보가 필요없다. - 프록시 빈을 생성해준다. - 부가기능을 `MethodInterceptor`로 구현 - `MethodInterceptor` 를 재정의한 invoke를 구현해줘야한다. - CGLIB의 MethodInterceptor 와는 다르다. ### ProxyFactoryBean의 프록시 생성 메커니즘 > ProxyFactoryBean → Proxy → MethodInterceptor → Target > - ProxyFactoryBean이 프록시를 생성하면 부가기능을 MethodInerceptor가 수행한다. - InvoationHandler와는 달리, Target에 대한 정보는 Proxy가 가지고 있다. ### MethodInerceptor는 왜 Target을 가지지 않을까? - 부가기능을 **독립적**으로 유지하기 위해서이다. - 부가기능을 싱글톤으로 공유하여 사용 가능하다. --- ## Code Generator Library(CGLIB) 동적 프록시의 한 종류이다. - 상속을 통한 프록시 구현 - 메소드가 처음 호출됐을 때 동적으로 Target 클래스의 **바이트코드를 조작해서 프록시를 구현한다.** - 이후 호출 시엔 조작된 바이트 코드를 **재사용**한다 - 바이트 코드 조작은 어떻게 하나? - `Enhancer` 인스턴스를 생성하여 런타임 시 해당 결과값을 선정한다. - 메서드 시그니처에 따라서 intercept - BeanGenerator의 GGLIB 사용 - `MethodInterceptor`를 재정의한 **intercept**를 구현해야 **부가기능**이 추가된다. ### CGLIB 사용 흐름 > client → CGLIB → MethodInterceptor → Target > - 클라이언트가 메소드를 요청하면 CGLIB은 메소드 처리를 `MethodInterceptor` 에게 위임한다. - `MethodInterceptor` 는 부가기능을 수행하고, 이후 Target의 로직을 처리하기 위해 Target에게 위임한다. ### CGLIB 특징 정리 - 인터페이스에도 강제로 적용할 수 있다. 이때는 클래스에도 프록시를 적용시켜야 한다. - 메서드에 `final` 을 붙이면 오버라이딩이 불가능하다. - `~~net.sf.cglib.proxy.Enhancer` 의존성을 추가해야 한다.~~ - 3.2 version Spring Core 패키지에 포함 - ~~Defult 생성자 필요~~ - 4.0 version Objeansis 라이브러리 - ~~Target의 생성자 두 번 호출~~ - 4.0 version Objeansis 라이브러리 ### CGLIB 단점 - 상속에 따른 제약이 존재한다. ## JDK Dynamic Proxy와 CGLIB 비교 - JDK Dynamic Proxy - 리플렉션을 사용해서 느리다. - 인터페이스가 있어야 동작한다. - CGLIB - 바이크코드를 조작해서 빠르다. - 클래스만 있어도 동작한다. - 상속을 이용해서 프록시를 만든다. - 메서드에 final을 붙이면 안된다. → **Spring 4.3 & spring boot 1.4 이후 CGLIB proxy가 기본 설정이다.** ### 스프링과 스프링부트에서 CGLIB을 기본 설정으로 한 이유는? ![Phil Webb is a [Spring Framework](http://projects.spring.io/spring-framework/) developer and co-creator of the [Spring Boot](http://projects.spring.io/spring-boot/) project.](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c5c98b67-497f-4815-95c8-cd87b0a09c31/Untitled.png) Phil Webb is a [Spring Framework](http://projects.spring.io/spring-framework/) developer and co-creator of the [Spring Boot](http://projects.spring.io/spring-boot/) project. - 인터페이스 기반 프록시는 ClassCast Exceptions를 추적하기 어렵게 하기 때문이라고 한다. # 마치면서 - 프록시에 대해서 전체적으로 정리했다. 쉽지는 않다. 다음에는 Spring AOP와`@Transactional` 이 어떻게 동작하는지 학습하면서 다시 복습을 해봐야겠다.# Proxy / JDK Dynamic Proxy / Code Generator Library(CGLIB) # 배경 - 토비의 스프링 책으로 AOP를 학습하다가 막혔는데 알고보니 프록시 대해서 잘 몰라서 막힌다는 걸 알게 되어 프록시에 관한 내용들을 글로 정리하면서 학습하고자 한다. # 다루는 내용 - Proxy - 프록시 패턴 - 동적 프록시 - JDK Dynamic Proxy - Code Generator Library(CGLIB) - Proxy Factory Bean ## Proxy **특정 객체에 대한 접근을 제어하거나 기능을 추가할 수 있는 객체를 의미한다.** - Client로부터 Target을 대신해서 요청을 받는 **대리인** - 실제 오브젝트인 Target은 Proxy를 통해 최종적으로 요청받아 처리한다. - 그 결과, Target은 자신의 기능에만 집중하고 **부가적인 기능**은 Proxy**에게 위임**한다. > Client → Proxy → Target > ### Proxy 사용 목적 두 가지로 구분할 수 있다. - 1. 클라이언트가 Target에 **접근**하는 방법을 **제어**하기 위함 ex) JPA Hibernate 지연로딩 - 지연로딩은 어떻게 동작하나? 실제로 필요하기 전까지, 연관 관계로 가지고 있는 엔티티 필드에 null 값을 넣어둘 순 없으니 프록시 객체를 주입해서 실제 객체가 들어있는 것처럼 동작하도록 한다. - 프록시 객체는 **실제 객체를 상속한 타입**을 가지고 있기 떄문에 문제없이 동작한다. - 2.Target에 **부가적인 기능**을 주기 위함 ex) `@Transactional` - 어떻게? Reflection API, 바이트 코드 조작 등의 방법으로 프록시를 구현해 부가적인 기능을 준다. ### Proxy를 직접 구현한다면? - Proxy 패턴을 통해서 Proxy를 직접 구현할 수 있다. ### Proxy 패턴 - 특정 객체에 대한 **접근을 제어**하거나 **부가기능**을 구현하는데 사용하는 패턴 ![출처: https://en.wikipedia.org/wiki/Proxy_pattern](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/25aa8da3-48ed-486b-906b-7dd45ec4368f/Untitled.png) 출처: https://en.wikipedia.org/wiki/Proxy_pattern ### Proxy 패턴을 사용함으로써 얻는 장점 **기능 추가, 접근 제어 등 다양하게 활용할 수 있다.** - OCP(Open Closed Principle) 원칙을 지킨다. - 기존 Target 코드를 변경하지 않고 새로운 **기능을 추가**할 수 있다. - SRP(Single Responsibility Principle) 원칙을 지킨다. - Validation 처리와 같은 부가적인 기능은 분리시킴으로써 기존 Target 코드가 해야하는 역할만 **유지**할 수 있다. ### Proxy 패턴을 사용함으로써 얻는 단점 - 코드의 복잡도 증가 - 프록시패턴을 구현하려면 인터페이스를 구현해야하고 또한 프록시 객체를 생성해서 구현해줘야 한다. - 중복 코드 발생 - 부가기능을 구현할 때 모든 메서드에 적용해줘야 하기 때문에 중복 코드가 발생한다. > **프록시 패턴의 문제점을 동적 프록시를 통해 해결할 수 있다.** > --- ## 동적 프록시(Dynamic Proxy) 동적 프록시는 말그대로 동적인 시점(런타임 시점)에 프록시를 자동으로 만들어서 적용해주는 기술이다. ### 동적 프록시 기술이 만들어진 배경 - 프록시 패턴의 문제점으로 동적 프록시 기술이 만들어졌다. - 프록시를 적용해야 할 클래스가 수십 개에서 수백 개가 되면? - 클래스를 그 개수만큼 반복해서 만들어주어야 하는데 이런 불편함을 해소하기 위해 나온 기술 ### 스프링에서 동적 프록시는 어떻게 동작할까? - 클라이언트가 메소드를 요청하면 Proxy Factory Bean 이라는 곳에서 인터페이스 유무를 확인하여 - 인터페이스가 있으면 JDK Dynamic Proxy 방식으로 프록시를 생성하고, - 인터페이스가 없으면 CGLIB의 방식으로 프록시를 생성한다. ## JDK Dynamic Proxy 동적 프록시의 한 종류이다. - 프록시 클래스를 직접 구현하지 않아도 된다. - 코드 복잡도 해소 - **`Invocatio Handler`**를 ******사용해서 중복 코드 제거를 제거한다. - `Invocation Handler`는 **Reflection API**를 사용한다. ### **Invocation Handler** - 실제 객체인 Target의 필드를 반드시 가지고 있어야 한다. → **Target에 의존적이다.** - 왜 가지고 있어야할까? 부가기능을 수행하기 위해서 - Target A, Target B, Target C … 에 맞는 빈을 매번 생성해줘야 한다. ### **Reflection API** - 구체적인 클래스 타입을 몰라도 런타임에 클래스의 정보에 접근할 수 있게 해주는 자바 API ### Reflection API 단점 - JVM 최적화가 동작하지 않아 성능상 **느리다.** - JVM 최적화란? JVM이 실행하는 프로그램의 성능을 개선하기 위해 시스템 자원을 효율적으로 사용하도록 설정하는 것 - 최적화 방법 - 코드 변경: 불필요한 변수 할당을 제거하거나 반복문 내 임시 변수 사용을 없애면서 개발자가 작성한 코드를 직접 수정한다. - JVM 설정 파일 변경: 메모리 제한, GC 동작, JIT컴파일러 동작 등을 조절할 수 있다. - 메모리 최적화: 힙 메모리 크기 조절, GC 동작 설정, 클래스 로딩 최적화 등을 통해 메모리를 최적화할 수 있다. - 코드 컴파일링: 어떤 메소드가 자주 호출되는지, 어떤 메소드가 오랜 시간을 차지하는지 등을 판별하면서 성능 저하가 발생하는 원인을 파악하고 최적화 작업 수행 ![출처: https://docs.oracle.com/javase/tutorial/reflect/](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/25921f0e-535d-44b3-87b6-4e6c7cf3d573/Untitled.png) 출처: https://docs.oracle.com/javase/tutorial/reflect/ ### JDK Dynamic Proxy 사용 흐름 > Client → JDK Dynamic Proxy → Invocation Handler → Target > - 클라이언트가 메소드를 요청하면 JDK Dynamic Proxy는 메소드 처리를 `Invocation Handler`에게 위임한다. - `Invocation Handler`는 부가기능을 수행하고, 이후 Target의 로직을 처리하기 위해 Target에게 위임한다. ### JDK Dynamic Proxy 특징 정리 - JDK에서 지원하는 프록시 생성 방법 - Reflection API를 사용한다. → 외부 라이브러리에 의존하지 않지만 JVM 최적화가 동작하지 않아 느리다. - **인터페이스**가 반드시 있어야 한다. - `Invocation Handler`를 재정의한 **invoke**를 구현해줘야 **부가기능이 추가**된다. --- ## Code Generator Library(CGLIB) 동적 프록시의 한 종류이다. - 상속을 통한 프록시 구현 - 메소드가 처음 호출됐을 때 동적으로 Target 클래스의 **바이트코드를 조작해서 프록시를 구현한다.** - 이후 호출 시엔 조작된 바이트 코드를 **재사용**한다 - 바이트 코드 조작은 어떻게 하나? - `Enhancer` 인스턴스를 생성하여 런타임 시 해당 결과값을 선정한다. - 메서드 시그니처에 따라서 intercept - BeanGenerator의 GGLIB 사용 - `MethodInterceptor`를 재정의한 **intercept**를 구현해야 **부가기능**이 추가된다. ### CGLIB 사용 흐름 > client → CGLIB → MethodInterceptor → Target > - 클라이언트가 메소드를 요청하면 CGLIB은 메소드 처리를 `MethodInterceptor` 에게 위임한다. - `MethodInterceptor` 는 부가기능을 수행하고, 이후 Target의 로직을 처리하기 위해 Target에게 위임한다. ### CGLIB 특징 정리 - 인터페이스에도 강제로 적용할 수 있다. 이때는 클래스에도 프록시를 적용시켜야 한다. - 메서드에 `final` 을 붙이면 오버라이딩이 불가능하다. - `~~net.sf.cglib.proxy.Enhancer` 의존성을 추가해야 한다.~~ - 3.2 version Spring Core 패키지에 포함 - ~~Defult 생성자 필요~~ - 4.0 version Objeansis 라이브러리 - ~~Target의 생성자 두 번 호출~~ - 4.0 version Objeansis 라이브러리 ### CGLIB 단점 - 상속에 따른 제약이 존재한다. ## JDK Dynamic Proxy와 CGLIB 비교 - JDK Dynamic Proxy - 리플렉션을 사용해서 느리다. - 인터페이스가 있어야 동작한다. - CGLIB - 바이크코드를 조작해서 빠르다. - 클래스만 있어도 동작한다. - 상속을 이용해서 프록시를 만든다. - 메서드에 final을 붙이면 안된다. → **Spring 4.3 & spring boot 1.4 이후 CGLIB proxy가 기본 설정이다.** ### 스프링과 스프링부트에서 CGLIB을 기본 설정으로 한 이유는? ![Phil Webb is a [Spring Framework](http://projects.spring.io/spring-framework/) developer and co-creator of the [Spring Boot](http://projects.spring.io/spring-boot/) project.](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/c5c98b67-497f-4815-95c8-cd87b0a09c31/Untitled.png) Phil Webb is a [Spring Framework](http://projects.spring.io/spring-framework/) developer and co-creator of the [Spring Boot](http://projects.spring.io/spring-boot/) project. - 인터페이스 기반 프록시는 ClassCast Exceptions를 추적하기 어렵게 하기 때문이라고 한다. ### 스프링의 프록시 구현 - ProxyFactoryBean - 스프링은 일관된 방법으로 프록시를 만들 수 있게 도와주는 추상 레이어를 제공 - JDK Dynamic Proxy를 **추상화**해서 적절한 방식의 프록시를 생성하도록 도와주는 팩토리 클래스다. - 프록시 오브젝트를 생성해주는 기술을 추상화한 팩토리 빈을 제공 - ProxyFactoryBean : 프록시를 생성해서 빈 오브젝트로 등록하게 해주는 팩토리 빈 - 순수하게 프록시 생성을 위한 작업만을 담당하고, 부가 기능은 별도의 빈에 둘 수 있음 ### ProxyFactoryBean 특징 - target의 인터페이스 정보가 필요없다. - 프록시 빈을 생성해준다. - 부가기능을 `MethodInterceptor`로 구현 - `MethodInterceptor` 를 재정의한 invoke를 구현해줘야한다. - CGLIB의 MethodInterceptor 와는 다르다. ### ProxyFactoryBean의 프록시 생성 메커니즘 > ProxyFactoryBean → Proxy → MethodInterceptor → Target > - ProxyFactoryBean이 프록시를 생성하면 부가기능을 MethodInerceptor가 수행한다. - InvoationHandler와는 달리, Target에 대한 정보는 Proxy가 가지고 있다. ### MethodInerceptor는 왜 Target을 가지지 않을까? - 부가기능을 **독립적**으로 유지하기 위해서이다. - 부가기능을 싱글톤으로 공유하여 사용 가능하다. # 마치면서 - 프록시에 대해서 전체적으로 정리했다. 쉽지는 않다. 다음에는 Spring AOP와`@Transactional` 이 어떻게 동작하는지 학습하면서 다시 복습을 해봐야겠다.