Spring
프록시를 만드는 방식 (인터페이스 기반 & 클래스 상속 기반)
제리 .
2022. 3. 10. 01:31
spring boot에서 동적으로 프록시를 만드는데 사용되는 jdk 동적 프록시, cglib방식을 정리하기 전 프록시를 만들 수 있는 방법에 대해서 알아보자.
프록시를 만드는 방식은 인터페이스 기반 방식과 클래스 상속 기반 방식이 있다.
인터페이스 기반 프록시 생성
인터페이스 기반 프록시 생성은 실제 클래스와 프록시 클래스가 동일한 인터페이스를 구현한다.
interface Subject {
fun operation(): String
}
실제 클래스와 프록시 클래스는 각각 이 인터페이스를 구현해야 한다.
class ReadSubject : Subject {
private val logger = LoggerFactory.getLogger(this::class.simpleName)
override fun operation(): String {
logger.info("실체 객체 호출")
return "data"
}
}
class ProxySubject(private val subject: Subject) : Subject {
private val operationValue: String by lazy { subject.operation() } // 필드 사용 시점 초기화
override fun operation(): String {
return operationValue
}
}
ProxySubject는 operation()호출시 operationValue를 반환하고, operationValue는 사용시점에 실제 객체의 operation()을 호출한다. 그 뒤에는 값이 캐싱되어 더 이상 실제 객체를 호출하지 않는다.
클라이언트는 이 인터페이스를 기반으로 요청을 보내기 때문에 프록시 클래스는 실제 클래스를 완전히 대체할 수 있다.
클래스 상속 기반 프록시 생성
다음과 같이 실제 클래스가 존재한다고 하자.
open class ReadSubject {
open fun operation(): String {
logger.info("실체 객체 호출")
return "data"
}
}
프록시 클래스는 이 클래스를 상속받아 만들어진다.
class ProxySubject : ReadSubject() {
private val operationValue: String by lazy { super.operation() }
override fun operation(): String {
return operationValue
}
}
ProxySubject는 operation()호출시 operationValue를 반환하고, operationValue는 사용시점에 부모 클래스(실제 객체)의 operation()을 호출한다. 그 뒤에는 값이 캐싱되어 더 이상 실제 객체를 호출하지 않는다.
이 방식을 사용하면 인터페이스가 굳이 필요하지 않는다.
인터페이스 기반 VS 클래스 상속 기반
클래스 기반 프록시 생성 특징
- 프록시 생성을 위해서는 인터페이스가 필요 없다.
- 상속할 수 없는 클래스, 메서드에 대해서 프록시를 적용할 수 없다.
- 자식 클래스가 생성될 때 부모 클래스의 생성자가 호출되기 때문에 실제 클래스의 생성자를 호출하게 된다.
- 클래스 기반은 해당 클래스에 대해서만 프록시를 만들 수 있다. 인터페이스 기반은 인터페이스만 같으면 모든 곳에 적용 가능하다.
인터페이스 기반 프록시 생성 특징
- 프록시 생성을 위해서는 인터페이스가 필요하다. (인터페이스가 논리적으로 필요한 상황인지?)
- 상속을 사용하지 않기 때문에 상속의 제약에 대해서 자유롭다.
- 클래스 상속 기반 프록시는 상속 기반으로 프록시 클래스-> 실제 클래스로 타입 캐스팅이 가능하지만 인터페이스 기반 프록시는 새로운 인터페이스 구현체로 프록시를 만들기 때문에 프록시 클래스 <-> 실제 클래스는 어떤 연관 관계가 없고 타입 캐스팅이 불가하다.