• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

"Prototype/프록시"로 설정했는데 동일한 객체로 나옵니다

21.03.24 19:11 작성 조회수 272

0

@Getter
@Component
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Prototype {
    private final UUID uuid = UUID.randomUUID();
}
@Getter
@Component
@RequiredArgsConstructor
public class Singleton {

    private final Prototype prototype;

    public void print() {
        System.out.println(prototype.getUuid());
        System.out.println(prototype.getUuid());
        System.out.println(prototype.getUuid());
    }

}
Singleton singleton = context.getBean(Singleton.class);
singleton.print();
Prototype p1 = singleton.getPrototype();
Prototype p2 = singleton.getPrototype();

System.out.println(p1);
System.out.println(p2);
System.out.println(p1.getUuid());
System.out.println(p2.getUuid());

System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
System.out.println(new Prototype().equals(new Prototype()));

Singleton bean 안에 class를 target으로 하는 proxy로 prototype의 bean을 필드로 정의했습니다.

근데 singleton 객체에서 prototype 필드를 print하면 메모리 주소는 다르게 나오는데

==, equals 등으로 비교하면 true가 나오는 기이한 현상을 겪고 있습니다. 제 자바 근간이 흔들리고 있어요;

왜 그런 걸까요..?

답변 4

·

답변을 작성해보세요.

1

Singleton은 싱글톤 타입의 객체라 한번만 만들었으니 해당 인스턴스가 가지고 있는 Prototype이 계속 바꿀 수는 없고 (그러려면 Singleton을 프록시로 만들어야겠죠.) 대신 Prototype 클래스를 상속받은 프록시를 만들어 주입해 두고, 오퍼레이션이 발생할 때마다 다른 인스턴스를 만들어 사용하는 식으로 동작하는거죠. 그래서 사실은 Prototype의 실긍톤 프록시가 주입되어 있지만 그 프록시 객체가 프로토타입처럼 동작하도록 만들어져 있는겁니다.

0

Im Sejin님의 프로필

Im Sejin

질문자

2021.03.25

감사합니다, 디버거를 완전히 잊고 있었네요...

덕분에 대부분의 의문이 풀렸습니다.

근데 디버거로 메서드 콜 스택을 따라가 보니까 의문이 들었던 게 있습니다.

---

먼저 operation과는 상관없이 class proxy instance(Prototype$$EnhancerBySpringCGLIB$$라고 출력된 것)는 딱 1개만 생성되었습니다.

origin target의 method 실행 시, origin target의 constructor를 실행하고 MethodInterceptor를 통해

CglibMethodInvocation을 실행하여 method를 invoke하는 걸로 파악했습니다.

---

제가 알고 있기론 class 기반 proxy는 target class를 상속받는 걸로 알고 있습니다.

정작  class proxy instance는 하나밖에 생성되지 않고, target class의 constructor만 매 method 실행할 때마다 동작하더라고요.

상속을 받는다면 proxy instance가 여러 개여야 하는데 singleton인 게 이해가 안 갑니다.

혹시 Spring AOP에서는 class의 proxy가 아니라, method의 proxy를 생성해서 실행하는 건가요?

0

디버거로 p1이랑 p2 객체를 살펴보세요.

toString, equals도 디버거로 따라가 보시면 이해할 수 있지 않을까요?

0

싱글통에서 getPrototype으로 꺼낸 객체는 사실 다 같은 객체입니다. 싱글콘은 안바뀌니까요.

그런데 그 객체를 통해서 하는 모든 오퍼레이션(toString, get..) 등을 호출하는 순간 프록시가 적용되어 새로운 객체를 만든 다음에 그 객체의 오퍼레이션을 호출하기 때문에 매번 다른 해시코드나 UUID가 출력되고 있는겁니다.

Im Sejin님의 프로필

Im Sejin

질문자

2021.03.25

답변 감사합니다.

---

프록시를 적용하면 bean을 inject받았을 때 CGLIB이 해당 bean을 상속받아 프록시 클래스를 만들고 그걸 inject해주는 줄 알았는데, 다른가 보네요. 아래의 순서대로 prototype의 bean을 사용하는 건가요?

1. 프록시가 적용되지 않은 순수한 Prototype을 먼저 singleton으로 생성해 모든 의존관계에 inject.

2. 해당 Prototype의 instance에 접근할 때마다 CGLIB이 매번 프록시 객체를 생성하여, 매번 다른 value를 return.

---

System.out.println(prototype)을 하면 말씀하신 toString(), 즉 operation이 발생해서

CGLIB의 proxy객체가 찍혀서 아래처럼 나와야 할 거 같은데,

io.github.imsejin.springstudy.model.Prototype$$EnhancerByCGLIB$$eb3cdb4a
io.github.imsejin.springstudy.model.Prototype$$EnhancerByCGLIB$$1e2158ab

실제로는 proxy 객체의 FQCN이 보이지가 않네요...

io.github.imsejin.springstudy.model.Prototype@6b3adb48
io.github.imsejin.springstudy.model.Prototype@6e2158a1

equals 메서드도 operation이니까 proxy가 생성되어 false를 반환해야 할 거 같은데,

true를 반환하는 게 아직 좀 이해가 어렵네요 ㅜㅜ

toString은 operation인데 equals는 operation에 포함되지 않는 건가요?

---

@Getter
@Component
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Prototype {
    private final UUID uuid = UUID.randomUUID();
    public final UUID uuid2 = UUID.randomUUID();
}

그리고 마지막으로 궁금한 건

Singleton singleton = context.getBean(Singleton.class);

singleton.getPrototype().getUuid(); // a69571e5-6931-4303-b0fc-a2012815183a
singleton.getPrototype().uuid2; // null
singleton.getPrototype().getUuid2(); // abab093a-45b0-4e1f-9024-5276182a90c9

메서드를 이용하여 필드값에 접근하면 정상적으로 값을 반환하는데,

필드에 직접 접근하여 null을 반환하네요.

proxy 객체가 생성되었다면 당연히 uuid2 필드도 상속받아야 하고, final 필드에 값도 바로 초기화해줬으니 UUID 객체가 반환되어야 하는데, null을 반환하는 게 이해가 안 갑니다... ㅜ