코드 디버깅을 통해 Spring 순환 종속성에 대해 어떻게 이야기합니까?

나는 이 인터뷰 질문을 해결하기 위해 왜 Spring 순환 종속성이 3단계 캐싱을 사용하는지 여러 번 질문을 받았는데 명확하게 설명하기가 어렵습니다. Spring 소스 코드를 읽은 후에도 순환 종속성을 해결하기 위한 Spring의 디자인 아이디어를 기록하고 이를 기술 애호가들과 공유해야 한다고 생각합니다.

 처음 인터뷰에서 질문을 받고 많은 기사를 읽고 영감을 얻었지만 실제 사례 연구를 기반으로 한 기사가 거의 없어 잊어버리기 쉬웠고 진실을 명확하게 설명해야 했습니다. 인터뷰 도중 면접관에게 어려운 내용이었습니다. 나중에는 실제 예제를 기반으로 순환 종속성을 철저히 이해하기로 결정했습니다. 오늘은 실제 예제를 사용하여 디버깅하면서 Spring이 순환 종속성을 어떻게 처리하는지 알아보겠습니다.

설명하다

이 기사를 읽는 학생들은 Spring ioc 및 di 프로세스는 물론 Spring 빈 생성 및 빈 속성 채우기에 대한 이해가 필요합니다.

Spring 컨테이너에서 빈을 가져오는 것을 기억하세요.

먼저, 세 개의 캐시에서 빈을 얻는 Spring의 방법을 찾을 수 있습니다.

    //一级缓存,存储可以直接使用的bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    //二级缓存 存储不完整对象 不能直接用的 循环依赖时,某些依赖的属性没有设置,这个时候bean就是不完整对象
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    //三级缓冲 存储bean工厂 可以延迟调用实例化 使用的时候才调用工厂方法获取对象
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    //从Spring容器获取bean对象方法
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //一级缓存
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
            //二级缓存
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                    //三级缓存
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
复制代码

여기에 3개의 캐시가 필요한 이유는 무엇입니까? 왜 2가 아닌가? 하나도 없나요? 그 이유를 살펴볼까요?

순환 종속성 시나리오 시뮬레이션

클래스 A와 B는 서로의 클래스 속성을 갖습니다.

@Service
public class A {
    
    private B b;
}
复制代码
@Service
public class B {

    private A a;
}
复制代码

순환 종속성을 서로 수동으로 주입하는 것을 시뮬레이션합니다.

객체 a는 객체 b에 의존하고 객체 b는 객체 a에 의존합니다. 속성 주입 프로세스는 B를 먼저 인스턴스화하고 A를 주입한 다음 A를 채우는 것입니다.

A a = new A();

B b = new B();

b.setA(a); //B里面注入不完整的A了

a.setB(b); //注入依赖的对象,A里面有B了,a注入完成变成完整的a

复制代码

Spring 순환 종속성 주입 처리 흐름 디버깅

동일한 시나리오에서 Spring이 이를 어떻게 처리하는지 확인하세요.

첫 번째 단계는 클래스 A의 객체 a를 만드는 것입니다.

 Spring에서는 클래스 A가 인스턴스화된 후 인스턴스 A 대신 팩토리가 캐시됩니다. 이유는 무엇입니까?

두 번째 단계에서는 객체 a의 속성 b를 주입하기 시작합니다.

 a가 b를 주입하면 캐시는 b 인스턴스 대신 팩토리 클래스를 등록합니다.

세 번째 단계에서는 b 객체가 객체에 속성을 다시 주입하기 시작합니다.

 b 객체는 다시 a 객체를 주입하기 시작하는데, 이때 a에만 해당 팩토리가 있습니다.

a의 프록시 객체는 A 팩토리를 통해 반환되며, 프록시 객체는 초기에 노출된 캐시 earlySingletonObjects(2차 캐시)에 넣어지는데, 이때 a는 b 주입을 완료하지 않았으며 아직 반제품 상태입니다. 제품이므로 a는 여전히 중간 캐시에 저장됩니다. 

4단계: b 객체를 객체에 주입

 이때 B의 주입은 완료되었으나, A의 주입은 아직 완료되지 않은 상태이다. B 안의 A 안의 B에는 값이 할당되지 않습니다.

다섯 번째 단계는 완전한 B 객체를 주입하는 것입니다(실제로는 아직 완료되지 않음).

6단계: a 객체의 b 속성 삽입

 마지막으로 A 객체에 B 속성을 주입하는데 이때 A 객체가 완성되었으므로 B 객체도 완성됩니다(이전 단계를 보완하기 위해).

객체 그래프 완성

완전한 b 객체 그래프

요약하자면, 레벨 3 캐시를 사용하는 이유는 무엇입니까?

다음 번에 면접에서 순환 의존성에 대해 질문을 받으면 면접관에게 어떻게 설명해야 할까요?

1. Spring은 순환 종속성 문제를 해결하기 위해 세 가지 캐시 맵, 즉 SingletonObjects, SingletonFactories 및 earlySingletonObjects를 사용합니다.

2. SingletonObjects 캐시는 완전한 객체를 저장하며 직접 사용할 수 있습니다.

3. SingletonFactories의 캐시는 지연된 초기화를 위한 것이며 순환 종속성 해결의 핵심입니다. 순환 종속성 주입이 있는 경우 주입된 객체를 프록시해야 할 수 있습니다. 이 팩토리 클래스는 프록시 클래스를 인스턴스화하는 데 사용됩니다.

4. earlySingletonObjects 캐시는 b를 a에 주입하고, b는 a에 주입하는데 A는 반제품이고 a와 같은 반제품 Bean의 상태를 저장하는 데 사용해야 합니다.

a가 b를 주입할 때까지 기다려야 하며 그러면 캐시가 제거됩니다.

5. SingletonFactories 및 earlySingletonObjects는 모두 저장된 개체의 중간 상태로, 최종 주입된 개체가 완료되고 종속성 주입이 완료된 후 지워지는지 확인하는 데 사용됩니다.

추천

출처blog.csdn.net/2301_76607156/article/details/130557749