Spring Proxy
- Proxy = 대리자
- Spring 에서 프록시는 매우 많은 곳에서 사용
- AOP 로 로직 감싸서 처리한는 부분.
- Lazy Initialization
- Security
- Transaction Management
- 등등
예를 들어서 이런식임.
class UserService(
private val userRepository: UserRepository
) {
fun save(user: User) {
userRepository.save(user)
}
}
UserRepository 가 interface 라면, UserService 는 Proxy 객체가 매핑. 그리고 이 프록시 객체는 UserRepository 인터페이스를 구현한 클래스와 연결됨. 즉, interface -> proxy -> impl class 객체 이런식으로 매핑시켜주는 녀석이 Proxy 임.
그리고 이런 인터페이스를 기반으로한 프록시 구현방식을 JDK Dynamic Proxy 라고 함. 클래스 기반의 방식인 CGLIB 도 있음.
- JDK Dynamic Proxy
- 설정: proxyTargetClass = false 또는 인터페이스를 상속받은 경우 자동으로 사용
- 작동 방식: 인터페이스를 상속받아 프록시 객체를 생성합니다. 인터페이스를 구현한 클래스를 대상으로 합니다.
- CGLIB Proxy
- 설정: proxyTargetClass = true
- 작동 방식: 인터페이스가 아닌 클래스를 상속받아 프록시 객체를 생성합니다. 이를 통해 인터페이스가 없는 클래스도 프록시를 통해 DI 및 AOP 기능을 사용할 수 있습니다.
- interface -> abstract class -> impl class
nGrinder 는 이런식으로 구조를 잡고 설계되어 있음. 좀 더 유연하고 안전하게 사용할 수 있기 때문이죠.
좀 더 유연하고 안전하게 사용할 수 있기 때문이죠. 그래서 저도 이런식으로 구조잡고 진행했더니! 실제 객체가 매핑되지 않고 프록시 객체가 매핑되는 경우가 있었습니다.
여기서 Spring 이 프록시 객체를 매핑하는 방법은 두 가지가 있습니다.
interface -> abstract class -> @Service extended class 이런 경우에는 CGLIB Proxy 가 사용되어야하죠
하지만, spring 에서는 interface 를 상속받는 클래스의 구현체와 프록시가 매핑됩니다.
@Service
public class UserServiceImpl implements UserService {
@Override
public void save(User user) {
System.out.println("save user");
}
}
싱글톤 init 순서
@Component
class Example(
private val apiKey: String = "adsdas" // (1) 주 생성자 호출
) {
private val field1 = createField1()
// (4) 프로퍼티 초기화: 생성자 본문보다 먼저, init보다 먼저
private fun createField1(): String {
println("(4) property initializer 실행됨: createField1() 호출됨")
return "field1-value"
}
init {
println("(5) init 블록 실행")
}
constructor(apiKey: String, option: Int) : this(apiKey) {
println("(6) 보조 생성자 본문 실행 (secondary constructor)")
}
@PostConstruct
fun post() {
println("(8) PostConstruct 실행")
}
}
Spring Proxy
- Proxy = delegate
- In Spring, proxies are used in many places.
- Parts where logic is wrapped and processed with AOP.
- Lazy Initialization
- Security
- Transaction Management
- etc.
For example, it looks like this.
class UserService(
private val userRepository: UserRepository
) {
fun save(user: User) {
userRepository.save(user)
}
}
If UserRepository is an interface, UserService is mapped to a Proxy object. This proxy object is connected to the class that implements the UserRepository interface. In other words, Proxy is the thing that maps interface -> proxy -> impl class object.
This interface-based proxy implementation method is called JDK Dynamic Proxy. There is also CGLIB, which is class-based.
- JDK Dynamic Proxy
- Setting:
proxyTargetClass = false, or automatically used when an interface is inherited - How it works: creates a proxy object by inheriting the interface. It targets classes that implement the interface.
- Setting:
- CGLIB Proxy
- Setting:
proxyTargetClass = true - How it works: creates a proxy object by inheriting a class, not an interface. Through this, even classes without interfaces can use DI and AOP features through a proxy.
- Setting:
- interface -> abstract class -> impl class
nGrinder is structured and designed like this. It is because this can be used more flexibly and safely.
Since it can be used more flexibly and safely, I also structured things like this and proceeded. Then there were cases where the actual object was not mapped and the proxy object was mapped instead.
There are two ways Spring maps proxy objects.
In a case like interface -> abstract class -> @Service extended class, CGLIB Proxy should be used.
But in Spring, the implementation class and proxy of a class that inherits an interface are mapped.
@Service
public class UserServiceImpl implements UserService {
@Override
public void save(User user) {
System.out.println("save user");
}
}
Singleton init order
@Component
class Example(
private val apiKey: String = "adsdas" // (1) primary constructor call
) {
private val field1 = createField1()
// (4) property initialization: before constructor body, before init
private fun createField1(): String {
println("(4) property initializer executed: createField1() called")
return "field1-value"
}
init {
println("(5) init block executed")
}
constructor(apiKey: String, option: Int) : this(apiKey) {
println("(6) secondary constructor body executed")
}
@PostConstruct
fun post() {
println("(8) PostConstruct executed")
}
}