🤥개요
우리는 Entity를 조회할 때 연관 관계를 이용하는데, 비즈니스 로직에 의해 사용될 때도 있고, 아닐 때도 있다. 그래서 맺어진 정보들이 필요가 없을 경우가 있는데, 이럴 경우를 대비해서 JPA 내부에선 필요 없는 DB 조회를 줄이면서 성능을 최적화 하는데, 이러한 문제를 해결하기 위해 DB 조회를 지연하는 방법을 제공한다. 이 것을 지연 로딩이라고 한다.
- 지연 로딩 (LAZY) = 가짜 객체를 이용해 DB 조회를 지연시키는 것.
- 즉시 로딩(EAGER) = 연관된 엔티티를 조인해서 다 가지고 오는 것.
이 지연로딩 기능을 사용하려면 DB 조회를 지원할 수 있는 가짜 객체가 필요한데, 이를 프록시 (Proxy) 객체라고 한다. 프록시의 출발은 여기에서 시작한다..
🤔목차
- 프록시란?🧐
- 프록시를 왜 사용할까?? (Proxy Pattern)😮
- JDK Dynamic Proxy
- CGLIB
- JDK Dynamic Proxy vs CGLIB
- 마무리
1). 프록시(Proxy)란? 🧐
Proxy의 사전적 의미로 “대리”라는 뜻을 가지고 있는데, 말 그대로 Proxy는 어떤 행위를 대신 요청 받는 “대리인”이라는 뜻이다.
- 클라이언트로부터 타겟을 대신해서 요청받는 대리인.
- 실제 오브젝트인 타겟은 프록시를 통해 최종적으로 요청받아 처리함
- 따라서 타겟은 자신의 기능에만 집중하고 부가 기능은 프록시에 위임
2). 프록시를 왜 사용할까? 😮
위 개요에서 설명한 것처럼 Proxy는 JPA가 지연 로딩을 할 수 있도록 도와주는데, Proxy의 사용 목적은 다음과 같다.
- 클라이언트가 타깃에 접근하는 방법을 제어하기 위해 사용한다. (지연로딩)
- 또 타깃에 부가적인 기능을 부여해주기 위해 사용한다. (트랜잭션 및 시간 측정)
이때 프록시 패턴을 통해 프록시를 구현할 수 있다.
추가 설명 : https://www.baeldung.com/java-proxy-pattern
Q : 프록시 패턴(Proxy Pattern)이란?
A : 특정 객체에 대한 접근을 제어하거나 부가기능을 구현하는데 사용하는 패턴
이 프록시 패턴을 이용해 우리는 중개자를 만들 수 있다. 구성요소의 기본 복잡성을 숨기면서 다른 리소스에 대한 인터페이스 역할을 한다.
프록시 패턴의 장점
- OCP (개방 폐쇠의 원칙 ) : 기존의 코드를 변경하지 않고 새로운 기능 추가 가능
- SRP ( 단일 책임 원칙 ) : 기존 코드가 해야 하는 일만 유지할 수 있음.
- 기능 추가 및 접근 제어 등 다양하게 응용할 수 있음.
프록시 패턴의 단점
- 코드의 복잡도 증가
- 중복 코드 발생
이러한 단점에서 발생하는 문제를 해결하기 위해선 JDK Dynamic Proxy 사용한다.
3) JDK Dynamic Proxy
추가설명 : https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html
JDK Dynamic Proxy는 프록시 클래스를 직접 구현하지 않아도 된다.
이를 통해 앞서 발생한 문제인 코드의 복잡도를 해소할 수 있으며 Invocation Handler를 통해 중복 코드를 제거할 수 있다.
이 동적 프록시는 인터페이스를 기반으로 프록시를 동적으로 만들어주기에 인터페이스가 필수적이다.
동적 프록시를 설명하는 공식 문서이다. 공식문서를 해석해보면 다음과 같다.
동적 프록시를 사용하면 단일 메서드가 있는 단일 클래스가 임의 개수의 메서드가 있는 임의의 클래스에 대한 여러 메서드 호출을 서비스 할 수 있다.
동적 프록시를 Facade의 한 종류로 생각할 수 있지만, 그러나 모든 인터페이스의 구현인 것처럼 가장할 수 있다. 내부적으로 모든 메서드 호출을 단일 핸들러인 invoke() 메서드로 라우팅한다.
내부 동작 원리는 다음과 같다
이 윗 내부 동작 원리를 설명하면 다음과 같다.
- 클라이언트는 메소드를 요청하고
- 동적 프록시가 요청을 받는다.
- 그럼 이 동적 프록시는 invocation handler에게 메소드 처리를 위임하게 되고,
- 이 핸들러는 부가 기능을 수행한 뒤, 타겟에게 위임하는 순서로 진행된다.
특징
- JDK에서 지원하는 프록시 생성 방법이라 외부의 의존하지 않는다
- reflection api 를 사용한다
- 인터페이스가 반드시 있어야 한다.
- invocation handler를 재정의한 invoke를 구현해줘야 부가기능이 추가된다.
4) CGLIB
동적 프록시의 한 종류인 이 CGLIB은 인터페이스 없이도 사용이 가능하다.
스프링에서는 클라이언트와 메서드를 요청하는 proxy factory bean 이라는 곳이서 인터페이스 유무를 확인하고 있으면 JDK Dynamic Proxy를, 없으면 CGLIB 방식을 이용해 프록시를 생성하는데, 이 CGLIB은 상속을 이용해 프록시를 구현한다.
바이트 코드를 조작해서 프록시를 생성하고
MethodInterceptior를 재정의한 intercept를 구현해야 부가기능이 추가된다.
아까 JDK Dynamic Proxy와 다른점은 Method Interceptor에 메소드 처리를 요구한다는 점이다.
이 CGLIB의 특징
- 인터페이스에도 강제로 적용할 수 있으며, 이렇게 사용하면 클래스에도 프록시를 적용해야한다.
- 메서드에 final을 붙이면 오버 라이딩이 불가능하다
- net.sf.cglib.proxy.Enhancer 의존성을 추가해야 한다.
- Default 생성자가 필요하다
- 타겟의 생성자 두번 호출
5) JDK Dynamic Proxy vs CGLIB
- 성능CGLIB은 메소드가 처음 호출되었을 때 동적으로 타깃의 클래스의 바이트 코드를 조작한다.
- 그 이후 호출시에 조작된 바이트 코드를 재사용한다. 이로 인해 빠르다.
- 우선 앞서 사용한 JDK Dynamic Proxy는 reflaction api를 사용하기에 느리다.
스프링에서는 CGLIB이 기본으로 동작하며 설정을 false로 바꾸면 JDK가 동작한다.
왜 그럼 이 CGLIB을 기본으로 사용하는 것일까
인터페이스 기반 프록시는 때떄로 ClassCastException을 추적하기 어렵게 한다. 이 이유로 CGLIB을 기본으로 사용한다.
6) 마무리
Spring에서는 프록시를 Bean으로 만들어주는 ProxyFactoryBean을 제공하는데, 이 FactoryBean을 이용해 Proxy를 생성할 수 있다.
이 프록시 팩토리 빈은 다음과 같은 특징이 있다.
- 타깃의 인터페이스 정보가 필요없다.
- 프록시 빈을 생성해준다
- 부가기능을 MethodIntercepter로 구현한다.
JDK Dynamic Proxy는 메소드 처리를 Invocation Handler에게 넘기는데, 이 Invocation Handler는 타겟인 실제 객체를 반드시 가지고 있어야 한다. 그래야 부가 기능을 수행할 수 있는데, 이 것이 치명적인 단점으로 작용한다.
그 이유는 이 InvocationHandler가 타겟에 의존적이기 때문이다.
그래서 Spring은 MethodInterceptor를 통해 부가 기능을 구현하는데,
이 ProxyFactoryBean에서는 타겟이 Proxy에 있다. 이러한 이유는 부가기능을 독립적으로 유지하기위해서이다.
이 ProxyFactoryBean의 특징은 다음과 같다
- Spring에서 지원하는 프록시 생성 방법
- MethodInterceptor를 재정의한 invoke를 구현해줘야 부가기능이 추가된다.
- 인터페이스가 반드시 필요하지 않다.
그러나 이 ProxyFactoryBean도 개발자가 매번 생성해주어야 한다는 단점이 존재한다.
'스프링' 카테고리의 다른 글
[#2] Spring Securiy Context Holder (0) | 2023.01.03 |
---|---|
[#1] Spring Security - 시큐리티는 무엇이고, Filter는 무엇인지 (0) | 2022.12.29 |
[10분 테코톡] - @Transactional (0) | 2022.12.21 |
Spring- Lombok의 이해와 @Annotation (계속 추가합니다.) (0) | 2022.12.14 |
스프링 AOP (0) | 2022.12.07 |