다이나믹 프록시 ( dynamic Proxy )
- 런타임시에 동적으로 만들어지는 오브젝트
- java의 reflection을 이용해서 proxy 객체 생성(java.lang.reflect)
- 타겟 인터페이스와 동일한 형태로 생성
- 프록시 대상의 객체가 최소 하나 이상의 인터페이스를 구현했다면 JDK 동적 프록시를 이용하면된다.
- FactoryBean(팩토리빈)을 통해 생성
- reflection이란 객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다.
- 스프링의 빈은 기본적으로 클래스 이름과 Property로 정의한다.
- 스프링은 지정된 클래스 이름을 가지고 reflection을 이용해 해당 클래스의 객체(object)를 생성한다.
JDK Dynamic Proxy
JDK Dynamic Proxy는 Proxy Factory에 의해 런타임시 동적으로 만들어지는 오브젝트이다.
JDK Dynamic Proxy는 반드시 인터페이스가 정의되어있고, 인터페이스에 대한 명세를 기준으로 Proxy를 생성한다.
즉, 인터페이스 선언에 대한 강제성이 있다는 단점이 있다.
내부적으로 JDK Dynamic Proxy에서는 InvationHandler라는 인터페이스를 구현해 만들어지는데,
invoke 함수를 오버라이딩하여 Proxy의 위임 기능을 수행한다.
이 과정에서 객체에 대한 Reflection 기능을 사용해 구현하기 때문에 퍼포먼스 하락의 원인이 되기도 한다.
구현과정
다이나믹 프록시 객체는 클래스 파일 자체가 존재하지 않으며, 빈 객체로 등록이 불가하다.
public interface ServiceTest {
void print();
}
@Service
public class ServiceTestImpl implements ServiceTest {
@Async
public void print() {
}
}
@RestController
public class HelloController {
private final ServiceTest service;
public HelloController(ServiceTest service) {
this.service = service;
System.out.println(service.getClass());
}
}
class com.sun.proxy.$Proxy60
위의 코드를 살펴보면 인터페이스(ServiceTest)를 구현한 객체(ServiceTestImpl)이 있다.
print문의 결과를 보면 JDK 동적 프록시가 들어간 것을 확인할 수 있다.
스프링 AOP를 이용하여 프록시 객체를 생성해볼 것이다.
스프링 내부에서 제공하는 FactoryBean 클래스를 사용하여 생성할 것이다.
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setInterfaces(ServiceTest.class);
proxyFactory.setTarget(new ServiceTestImpl());
proxyFactory.addAdvice(new ServiceAdvice());
final ServiceTest proxy = (ServiceTest) proxyFactory.getProxy();
System.out.println(proxy.getClass());
class com.sun.proxy.$Proxy60
인터페이스가 있어서 JDK 동적 프록시가 생성된 것을 볼 수 있다.
스프링데이터 JPA는 어떻게 동작할까?
스프링 데이터 JPA에서 DB와 데이터를 주고받는 Repository 인터페이스가 있습니다.
여기서 신기한점은 이 리포지토리는 따로 애노테이션을 통해 Dependency Injection이 되는것도 아닌 것 같고, 구현체 정의를 한적도 없는데
JpaRepository를 상속받기만하면 해당 타입의 객체도 만들어지고 해당 타입의 빈(bean)도 등록이 됩니다.
과연 스프링데이터JPA에서 인터페이스 타입의 인스턴스는 누가 만들어주는 것일까?
JpaRepository 역시 인터페이스이고 사용하게 되는 기능(메서드)들도 정의만 되어있는데
실제로 동작을 하는걸 볼 수 있습니다.
핵심은 리플렉션 패키지 내부에 있는 프록시(proxy)클래스 입니다.
스프링데이터JPA에서는 스프링 AOP를 쓰고있고,
스프링AOP가 프록시의 코드를 조금 더 추상화 한 (ProxyFactory)를 제공하는데,
이 프록시 팩토리가 자바의 다이나믹프록시를 좀 더 추상화 해 놓은 스프링 AOP의 핵심 클래스입니다.
스프링 데이터 JPA는 RepositoryFactorySupport에서 사용합니다.
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
RepositoryFactorySupport 내부의 코드로 프록시 객체를 만들어주는 부분입니다.
여기서 만들어주는 프록시 객체가 빈으로 등록이 되어
프로젝트 내부에 @Autowired 등으로 의존성 주입 선언된 곳에 주입됩니다.
프록시 패턴
프록시는 직역하자면 대리인이라고 볼 수 있는데,
프로그래밍에서는 리얼 서브젝트(실제 객체)와 프록시가 동일한 인터페이스(서브젝트)를 구현하고 있으며,
프록시는 리얼 서브젝트를 참조합니다. 그리고 서브젝트는 프록시를 사용합니다.
이럴 시퀀스 다이어그램으로 그리면 위와 같습니다.
예를들면 회사내에서 프록시를 비서 정도로 볼 수 있습니다.
클라이언트들의 요청을 비서(프록시)가 받아서 회장님(리얼서브젝트)에게 연결할 수도 있고, 안 할수도 있습니다.
어째서 프록시 패턴을 사용하는 것일까요.
개발을 할 때 단일 책임(SRP)를 지키며 개발을 하기위해선 부가기능, 접근제한 AOP등등 리얼 서브젝트에 들어가선 안됩니다.
하지만, 이런 모든 기능들을 모두 리얼 서브젝트(도메인)에 집어넣게 되면 코드는 복잡해지고 객체지향적이지도 않게 됩니다.
특징
- 프록시가 리얼 서브젝트가 공유하는 인터페이스가 있고, 클라이언트는 해당 인터페이스 타입으로 프록시를 사용한다.
- 클라이언트는 프록시를 거쳐 리얼 서브젝트를 사용하기 때문에 프록시는 리얼 서브젝트에 대한 접근을 관리하거나 부가기능을 제공하거나, 반환값을 변경할 수도 있습니다.
- 리얼 서브젝트는 자신이 해야 할 일만 하면서(SRP) 프록시를 사용해서 부가적인 기능(접근 제한, 로깅, 트랜잭션, 등)을 제공할 때 이런 패턴을 주로 사용합니다.
참고사이트
'개발중 > Spring' 카테고리의 다른 글
[스프링] TDD(Test-Driven Development) 필요한 이유와 방법론에 대해 (0) | 2023.03.18 |
---|---|
Spring 프로젝트에서 A DB 와 B DB 커넥션 맺어서 사용하려고 하는데 패키지명과 패키지 구조를 어떻게 잡아야할지 고민이야. (0) | 2023.03.17 |
[Spring] OAuth2.0 개념 및 작동방식 (0) | 2023.02.25 |
[Spring] CORS 가 뭐야 ? (1) | 2023.02.11 |
Sql Mapper (mybatis) 의 단점 요약 ! (0) | 2023.02.04 |