Spring의 registerSingleton 메서드는 DefaultListableBeanFactory에서 싱글톤 빈을 등록할 때 호출된다. 싱글톤 빈의 수동 등록 및 타입별 캐시 초기화 작업을 수행하여 애플리케이션 전반에서 동일한 빈 인스턴스를 사용할 수 있도록 한다. 이 과정에서 Consumer, Predicate, synchronized와 같은 함수형 인터페이스와 동기화 키워드가 사용된다. 또한, 순환 참조와 빈 초기화 지연 문제를 해결하기 위한 earlySingletonObjects와 singletonFactories 캐시, 빈 생성의 안정성을 보장하는 manualSingletonNames 리스트 관리도 이 과정에 포함된다.
@Override
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
super.registerSingleton(beanName, singletonObject);
updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName));
clearByTypeCache();
}
super.registerSingleton(beanName, singletonObject);DefaultSingletonBeanRegistry의 registerSingleton 메서드를 호출하여 singletonObjects 캐시에 beanName과 singletonObject를 등록합니다.IllegalStateException을 던져 중복 등록을 방지한다.updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName));updateManualSingletonNames는 Consumer와 Predicate를 사용하여 빈의 수동 등록을 관리한다.Consumer<Set<String>>: 빈 이름(beanName)을 manualSingletonNames 목록에 추가하는 역할을 한다. 여기서는 set -> set.add(beanName) 표현식을 사용하여 특정 조건을 만족하면 beanName을 추가한다.Predicate<Set<String>>: beanDefinitionMap에 beanName이 존재하지 않는 경우에만 manualSingletonNames에 추가한다. 이는 기존에 등록된 빈과 충돌하지 않도록 조건을 검사하는 역할을 한다.hasBeanCreationStarted()):hasBeanCreationStarted() 메서드는 빈 생성이 이미 시작된 이후에는 manualSingletonNames를 변경하지 않도록 안정성을 보장한다.manualSingletonNames 목록에 추가하고, 빈 생성이 시작된 후에는 기존 manualSingletonNames를 복사해 업데이트하여 빈 생성 중 불안정한 상태에서의 수동 등록을 안정적으로 관리한다.clearByTypeCache()clearByTypeCache는 타입별 캐시를 초기화하여 새로운 빈 정보가 반영될 수 있도록 한다.clearByTypeCache()는 빈이 새로 추가될 때마다 최신 정보를 반영하여 정확성을 보장한다.DefaultSingletonBeanRegistry는 Spring IoC 컨테이너에서 싱글톤 빈을 등록하고 관리하는 핵심 레지스트리 클래스이다. 여기서 registerSingleton은 싱글톤 캐시에 빈을 등록하는 핵심 로직을 포함한다.
@Override
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
Assert.notNull(beanName, "Bean name must not be null");
Assert.notNull(singletonObject, "Singleton object must not be null");
synchronized (this.singletonObjects) {
Object oldObject = this.singletonObjects.get(beanName);
if (oldObject != null) {
throw new IllegalStateException("Could not register object [" + singletonObject +
"] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
}
addSingleton(beanName, singletonObject);
}
}
singletonObject가 null이 아닌지 검사한다.singletonObjects 캐시에서 동일한 이름의 빈이 이미 존재하는지 확인한다. 동일한 이름의 빈이 있다면 예외를 던져 중복 등록을 방지한다.singletonObjects 캐시에 대한 접근을 synchronized로 감싸서, 여러 스레드가 동시에 이 맵을 수정하지 못하게 한다. 이를 통해 멀티스레드 환경에서 데이터 일관성을 보장하고, 빈 등록 과정에서 발생할 수 있는 경쟁 상태를 방지한다.addSingleton 메서드를 호출하여 실제로 singletonObjects에 빈을 등록한다.addSingleton 메서드는 singletonObjects 캐시에 빈을 저장하여 이후에 빈을 재사용할 수 있도록 한다. 동시에, 순환 참조 해결과 초기화 지연 문제 해결을 위해 earlySingletonObjects와 singletonFactories 캐시에서 해당 빈을 제거한다.
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
singletonObjects.put(beanName, singletonObject): singletonObjects 맵에 beanName과 singletonObject를 저장하여 싱글톤 빈이 IoC 컨테이너에 등록되도록 한다.earlySingletonObjects와 singletonFactories 는 순환 참조 및 초기화 지연 문제를 해결하기 위한 캐시로, 초기화 중이거나 미완성된 빈을 잠시 저장하는 역할을 한다.addSingleton에서는 최종적으로 빈이 등록되었음을 의미하므로, earlySingletonObjects와 singletonFactories에서 beanName을 제거하여 불완전한 상태가 아님을 명확히 한다.registeredSingletons.add(beanName)을 통해 수동으로 등록된 빈 이름을 목록에 추가하여 다른 빈이나 컴포넌트에서 동일한 빈 이름으로 빈을 조회할 수 있게 한다.updateManualSingletonNames 메서드는 수동으로 등록된 싱글톤 빈 이름을 관리하며, 빈이 정의되지 않은 경우 수동으로 추가된 싱글톤을 manualSingletonNames 목록에 추가한다.
private void updateManualSingletonNames(Consumer<Set<String>> action, Predicate<Set<String>> condition) {
if (hasBeanCreationStarted()) {
synchronized (this.beanDefinitionMap) {
if (condition.test(this.manualSingletonNames)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
action.accept(updatedSingletons);
this.manualSingletonNames = updatedSingletons;
}
}
} else {
if (condition.test(this.manualSingletonNames)) {
action.accept(this.manualSingletonNames);
}
}
}
Consumer와 Predicate의 사용:Consumer<Set<String>>: manualSingletonNames 목록에 빈 이름을 추가하는 작업을 정의한다. action.accept(updatedSingletons)에서 실행된다.Predicate<Set<String>>: manualSingletonNames 목록이 업데이트 가능한지를 검사합니다. 여기서는set -> !this.beanDefinitionMap.containsKey(beanName)를 통해 빈 정의가 없는 경우에만 수동 등록하도록 조건을 확인한다.hasBeanCreationStarted):hasBeanCreationStarted() 메서드를 통해 빈 생성이 이미 시작된 이후라면, manualSingletonNames를 새로운 객체로 복사해 업데이트하여, 빈 생성 중 수동 등록 리스트가 변경되는 불안정한 상태를 방지한다.clearByTypeCache 메서드는 타입별 캐시를 초기화하여 새로 추가된 빈이 올바르게 반영될 수 있도록 한다.
private void clearByTypeCache() {
this.allBeanNamesByType.clear();
this.singletonBeanNamesByType.clear();
}
allBeanNamesByType와 singletonBeanNamesByType는 빈을 타입별로 캐시하여 빈 조회 성능을 높이지만, 빈이 새롭게 추가되면 해당 캐시가 최신 상태를 반영하지 않을 수 있다.해당 코드 부분은 SpringApplicaion.run() 메소드 내에서 호출된 prepareContext() 메소드로 IoC Container의 register()에 해당한다고 볼 수 있다. Spring Framework에서는 Singleton 패턴으로 Bean을 등록하는 것을 알 수 있다.
registerSingleton 메서드는 특정 빈을 IoC 컨테이너에 등록하는 과정을 의미하며, beanName과 singletonObject를 IoC 컨테이너의 싱글톤 캐시에 추가하여 애플리케이션이 빈을 사용할 수 있도록 준비한다.earlySingletonObjects, singletonFactories와 같은 캐시는 순환 참조 문제 해결과 지연 초기화 관리를 지원하여, 등록된 빈이 올바르게 초기화되도록 돕는다. 이는 IoC 컨테이너가 빈을 관리할 때 발생할 수 있는 복잡한 상황을 처리하기 위한 메커니즘이다.clearByTypeCache()와 같은 캐시 초기화 작업은 IoC 컨테이너가 빈을 더 효율적으로 검색할 수 있게 지원하며, 이를 통해 빈을 빠르게 찾고 의존성 주입이 원활히 이루어지도록 한다.