Spring 순환 의존 을 해결 하려 면 3 급 캐 시 를 해 야 합 니까?

우 리 는 모두 Spring 이 3 급 캐 시 를 통 해 순환 의존 을 해결 하 는 것 을 알 고 있 습 니 다.그러나 순환 의존 을 해결 하려 면 정말 3 급 버퍼 를 사용 해 야 합 니까?2 급 캐 시 만 사용 해도 될까요?이 글 은 Spring 이 어떻게 3 급 캐 시 를 사용 하여 순환 의존 을 해결 하 는 지 에 대해 도입부 로 삼 아 2 급 캐 시가 순환 의존 을 해결 할 수 있 는 지 검증 하고 자 한다.
순환 의존
순환 의존 을 해결 하려 면 순환 의존 이 무엇 인지 알 아야 한다.다음 그림 에서 보 듯 이:

위의 그림 을 통 해 우 리 는 알 수 있다.
  • A 는 B
  • 에 의존한다.
  • B 는 C
  • 에 의존한다.
  • C 는 A
  • 에 의존한다.
    
    public class A {
      private B b;
    }
    
    public class B {
      private C c;
    }
    
    public class C {
      private A a;
    }
    
    
    이런 의존 관 계 는 일종 의 폐쇄 적 인 고 리 를 형성 하여 순환 의존 국면 을 조성 했다.
    다음은 순환 의존 을 해결 하지 못 한 일반적인 절차 입 니 다.
  • A 를 예화 합 니 다.이때 A 는 속성 채 우기 와 초기 화 방법(@PostConstruct)의 실행 이 완료 되 지 않 았 습 니 다.
  • A 대상 은 B 대상 을 주입 해 야 하 는 것 을 발 견 했 지만 용기 에 B 대상 이 없 었 다(대상 생 성 이 완료 되 고 속성 주입 이 완료 되 고 초기 화 방법 이 실행 되면 용기 에 넣 을 것 이다).
  • B 를 예화 합 니 다.이때 B 는 속성 채 우기 와 초기 화 방법(@PostConstruct)의 실행 이 완료 되 지 않 았 습 니 다.
  • B 대상 은 C 대상 을 주입 해 야 하 는 것 을 발 견 했 으 나 용기 에는 C 대상 이 없 었 다.
  • C 를 예화 합 니 다.이때 C 는 속성 채 우기 와 초기 화 방법(@PostConstruct)의 실행 이 완료 되 지 않 았 습 니 다.
  • C 대상 은 A 대상 을 주입 해 야 하 는 것 을 발 견 했 으 나 용기 에는 A 대상 이 없 었 다.
  • 반복 절차 1.
  • 3 레벨 캐 시
    Spring 순환 의존 해결 의 핵심 은 조기 노출 대상 이 고,조기 노출 대상 은 2 급 캐 시 에 두 는 것 이다.다음 표 는 3 급 캐 시 에 대한 설명 입 니 다.
    명칭.
    묘사 하 다.
    singletonObjects
    1 급 캐 시,완전한 Bean 을 저장 합 니 다.
    earlySingletonObjects
    2 급 캐 시,미리 노출 된 Bean 을 저장 합 니 다.Bean 은 완전 하지 않 고 속성 주입 과 init 방법 을 수행 하지 못 했 습 니 다.
    singletonFactories
    3 급 캐 시 는 Bean 공장 에 저장 되 어 있 으 며 주로 Bean 을 생산 하여 2 급 캐 시 에 저장 합 니 다.
    Spring 에서 관리 하 는 모든 Bean 은 최종 적 으로 singleton Objects 에 저 장 됩 니 다.이 안에 저 장 된 Bean 은 모든 생명 주 기 를 거 쳤 습 니 다(폐 기 된 생명 주 기 를 제외 하고).완전 하여 사용자 에 게 사용 할 수 있 습 니 다.
    early Singleton Objects 는 실례 화 되 었 지만,init 방법 을 실행 하 는 Bean 을 주입 하지 않 았 습 니 다.
    singleton Factory 는 Bean 을 생산 하 는 공장 을 맡 고 있 습 니 다.
    빈 은 이미 실례 화 되 었 는데,왜 빈 을 생산 하 는 공장 이 필요 합 니까?여 기 는 사실상 AOP 와 관련 된 것 이다.만약 에 프로젝트 에서 Bean 을 위해 대리 할 필요 가 없다 면 이 Bean 공장 은 처음부터 정례 화 된 대상 으로 바로 돌아 갈 것 이다.만약 에 AOP 를 사용 하여 대리 할 필요 가 있다 면 이 공장 은 중요 한 역할 을 발휘 할 것 이다.이것 도 본 고 에서 중점적으로 주목 해 야 할 문제 중 하나 이다.
    순환 의존 해결
    Spring 은 위 에서 소개 한 3 급 캐 시 를 통 해 순환 의존 을 어떻게 해결 합 니까?여 기 는 A,B 로 형 성 된 순환 의존 만 예 를 들 면:
  • 실례 화 A.이때 A 는 속성 채 우기 와 초기 화 방법(@PostConstruct)의 실행 이 완료 되 지 않 았 고 A 는 반제품 일 뿐 입 니 다.
  • A 를 위해 Bean 공장 을 만 들 고 singleton Factory 에 넣는다.
  • A 는 B 대상 에 주입 해 야 하 는 것 을 발 견 했 으 나 1 급,2 급,3 급 캐 시 는 모두 발견 대상 B 였 다.
  • 실례 화 B.이때 B 는 속성 채 우기 와 초기 화 방법(@PostConstruct)의 실행 이 완료 되 지 않 았 고 B 는 반제품 일 뿐 입 니 다.
  • B 를 위해 Bean 공장 을 만 들 고 singleton Factory 에 넣는다.
  • B 가 A 대상 을 주입 해 야 하 는 것 을 발 견 했 는데 이때 1 급,2 급 에서 대상 A 를 발견 하지 못 했 으 나 3 급 캐 시 에서 대상 A 를 발견 하고 3 급 캐 시 에서 대상 A 를 받 아 2 급 캐 시 에 대상 A 를 넣 는 동시에 3 급 캐 시 에서 대상 A 를 삭제 했다.(주의 하 세 요.이때 의 A 는 반제품 입 니 다.속성 충전 과 초기 화 방법 이 완성 되 지 않 았 습 니 다)
  • 대상 A 를 대상 B 에 주입 한다.
  • 대상 B 는 속성 채 움 을 완료 하고 초기 화 방법 을 실행 하 며 1 급 캐 시 에 넣 고 2 급 캐 시 에 있 는 대상 B 를 삭제 합 니 다.(이때 대상 B 는 이미 하나의 완제품 이다)
  • 대상 A 는 대상 B 를 얻어 대상 B 를 대상 A 에 주입 한다.(대상 A 는 완전한 대상 B
  • 를 얻 었 다.
  • 대상 A 는 속성 채 움 을 완료 하고 초기 화 방법 을 실행 하 며 1 급 캐 시 에 넣 고 2 급 캐 시 에 있 는 대상 A 를 삭제 합 니 다.
  • 우 리 는 소스 코드 에서 전체 과정 을 분석한다.
    Bean 을 만 드 는 방법 은 Abstract AutowireCapableBean Factory:doCreate Bean()
    
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
      BeanWrapper instanceWrapper = null;
      
      if (instanceWrapper == null) {
        // ①      
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
      }
    
      final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;
      Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;
      
      // ②             ,    ,        ObjectFactory      
      boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
      if (earlySingletonExposure) {
        //               
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
      }
    
      // ③     
      this.populateBean(beanName, mbd, instanceWrapper);
      // ④        ,     
      exposedObject = initializeBean(beanName, exposedObject, mbd);
      
      return exposedObject;
    }
    
    
    3 급 캐 시 를 추가 하 는 방법 은 다음 과 같 습 니 다.
    
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
      Assert.notNull(singletonFactory, "Singleton factory must not be null");
      synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) { //              
          this.singletonFactories.put(beanName, singletonFactory); //        
          this.earlySingletonObjects.remove(beanName); //            
          this.registeredSingletons.add(beanName);
        }
      }
    }
    
    @FunctionalInterface
    public interface ObjectFactory<T> {
      T getObject() throws BeansException;
    }
    
    
    이 코드 를 통 해 우 리 는 Spring 이 실례 화 대상 이 된 후에 빈 공장 을 만 들 고 이 공장 을 3 급 캐 시 에 넣 을 것 이라는 것 을 알 수 있다.
    따라서 스프링 이 처음에 미리 노출 된 것 은 정례 화 된 빈 이 아니 라 빈 을 포장 한 오 브 젝 트 팩 토리 다.왜 그 랬 을 까?
    이 는 실제로 AOP 와 관련 된 것 으로,만 든 Bean 이 에이전트 가 있다 면 원본 Bean 이 아 닌 에이전트 Bean 을 주입 해 야 한다.그러나 Spring 은 처음에 Bean 이 순환 의존 이 있 는 지 알 지 못 했 습 니 다.일반적인 상황 에서(순환 의존 이 없 는 경우)Spring 은 속성 을 채 우 고 초기 화 방법 을 실행 한 후에 대 리 를 만 듭 니 다.그러나 순환 의존 이 나타 나 면 Spring 은 프 록 시 대상 을 미리 만들어 야 합 니 다.그렇지 않 으 면 프 록 시 대상 이 아 닌 원시 대상 을 주입 해 야 합 니 다.그래서 대리 대상 을 어디서 미리 만들어 야 하 는가?
    Spring 의 방법 은 Object Factory 에서 대리 대상 을 미리 만 드 는 것 이다.빈 을 얻 기 위해 getObject()방법 을 실행 합 니 다.실제로 그것 이 진정 으로 실행 하 는 방법 은 다음 과 같다.
    
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
      Object exposedObject = bean;
      if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
          if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
            //       ,         ;        
            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
          }
        }
      }
      return exposedObject;
    }
    
    
    프 록 시 를 미리 진행 하 였 기 때문에 나중에 프 록 시 대상 을 반복 적 으로 만 들 지 않도록 early Proxy References 에 대 리 된 대상 을 기록 합 니 다.
    
    public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
      @Override
      public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        //          
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
      }
    }
    
    
    위의 분석 을 통 해 우 리 는 Spring 이 3 급 캐 시 를 필요 로 하 는 목적 은 순환 의존 이 없 는 상황 에서 대리 대상 의 생 성 을 지연 시 키 고 Bean 의 생 성 을 Spring 의 디자인 원칙 에 부합 하도록 하 는 것 임 을 알 수 있다.
    어떻게 의존 을 얻 습 니까?
    우 리 는 스프링 의 3 급 의존 작용 을 이미 알 고 있 지만,스프링 은 속성 을 주입 할 때 어떻게 의존 을 얻 습 니까?
    그 는 getSingleton()방법 을 통 해 필요 한 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) {
              // Bean       Bean
              singletonObject = singletonFactory.getObject();
              //         
              this.earlySingletonObjects.put(beanName, singletonObject);
              this.singletonFactories.remove(beanName);
            }
          }
        }
      }
      return singletonObject;
    }
    
    
    Spring 이 한 Bean 에 속성 을 채 울 때 먼저 주입 대상 의 이름 을 찾 은 다음 getSingleton()방법 을 순서대로 실행 하여 주입 대상 을 얻 습 니 다.대상 을 가 져 오 는 과정 은 1 급 캐 시 에서 가 져 오 는 것 입 니 다.1 급 캐 시 에 없 으 면 2 급 캐 시 에서 가 져 오고 2 급 캐 시 에 없 으 면 3 급 캐 시 에서 가 져 옵 니 다.3 급 캐 시 에 도 없 으 면 doCreate Bean()방법 으로 이 Bean 을 만 듭 니 다.
    2 급 캐 시
    세 번 째 캐 시 는 프 록 시 대상 의 생 성 을 지연 시 키 기 위 한 목적 임 을 알 고 있 습 니 다.순환 에 의존 하지 않 으 면 프 록 시 를 미리 만 들 필요 가 없 기 때문에 초기 화 완료 후 만 들 수 있 습 니 다.
    목적 이 지연 되 는 것 이 라면 우 리 는 생 성 을 지연 시 키 지 않 고 정례 화 된 후에 프 록 시 대상 을 만 들 수 있 는 것 이 아 닙 니까?그러면 우 리 는 3 급 캐 시 를 필요 로 하지 않 습 니 다.따라서 우 리 는 addSingleton Factory()방법 을 개조 할 수 있다.
    
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
      Assert.notNull(singletonFactory, "Singleton factory must not be null");
      synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) { //              
          object o = singletonFactory.getObject(); //          Bean
          this.earlySingletonObjects.put(beanName, o); //         
          this.registeredSingletons.add(beanName);
        }
      }
    }
    
    
    이렇게 되면 빈 을 예화 한 후 프 록 시 대상 을 만 들 고 2 급 캐 시 에 추가 합 니 다.테스트 결 과 는 완전히 정상 입 니 다.Spring 의 초기 화 시간 도 큰 영향 을 미 치지 않 을 것 입 니 다.Bean 자체 가 대리 가 필요 하지 않 으 면 원본 Bean 으로 직접 돌아 가 는 것 이기 때문에 복잡 한 대리 Bean 을 만 드 는 절 차 를 밟 을 필요 가 없습니다.
    결론.
    테스트 에 의 하면 2 급 캐 시도 순환 의존 을 해결 할 수 있다 는 것 이 증명 되 었 다.왜 Spring 은 2 급 캐 시 를 선택 하지 않 고 캐 시 를 한 층 더 추가 해 야 합 니까?
    Spring 이 순환 의존 을 해결 하기 위해 2 급 캐 시 를 선택 하면 모든 Bean 이 예화 가 완 료 된 후에 바로 대 리 를 만들어 야 한 다 는 것 을 의미 합 니 다.Spring 의 디자인 원칙 은 Bean 초기 화가 완 료 된 후에 야 대 리 를 만 드 는 것 입 니 다.그래서 Spring 은 3 급 캐 시 를 선 택 했 습 니 다.그러나 순환 의존 이 생 겨 서 Spring 은 프 록 시 를 미리 만 들 수 밖 에 없 었 습 니 다.프 록 시 대상 을 미리 만 들 지 않 으 면 원본 대상 을 주입 하면 오류 가 발생 할 수 있 기 때 문 입 니 다.
    Spring 순환 의존 을 해결 하려 면 3 급 캐 시 를 해 야 합 니까?  순환 의존 3 급 캐 시 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 도 많은 응원 부탁드립니다!

    좋은 웹페이지 즐겨찾기