[Spring] Spring 빈 조회 및 등록, AutoWired 정리 (Spring Boot 3.3.3 기준)

이명규·2024년 9월 17일
0
post-thumbnail

서론

Spring에 대한 정리글입니다.

이번 글에서는 빈 조회 방식, 빈 등록 흐름, 그리고 @Autowired 어노테이션이 실제로 어떻게 동작하는지에 대해 깊이 있게 설명합니다.

버전은 Spring Boot 3.3.3을 기준으로 하며, SpringApplication 내부에서 빈을 어떻게 스캔하고 등록하고 주입하는지에 중점을 둡니다.


SpringApplication

SpringApplication.run() 의 전체 흐름 중 실제로 빈을 등록하고 주입하는 데 영향을 주는 두 메서드를 깊이 파고듭니다.

실행 순서는 다음과 같습니다

  1. prepareContext()
  2. refreshContext()


prepareContext()

    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        
        // 생략된 다양한 작업들...
        
        if (!AotDetector.useGeneratedArtifacts()) {
            Set<Object> sources = this.getAllSources();
            Assert.notEmpty(sources, "Sources must not be empty");
            this.load(context, sources.toArray(new Object[0]));
        }

        listeners.contextLoaded(context);
    }
  • 이 메서드는 ApplicationContext 가 아직 완전히 초기화되기 전 단계에서 실행됩니다.
    • 즉 컨텍스트가 완전히 리프레시되지 않은 상태에서 실행
  • 중요한 핵심은 this.load() 메서드를 호출하는 시점입니다.
  • load() 가 실제로 빈 정의(BeanDefinition)를 생성하고 등록하는 역할을 합니다.
    • 애플리케이션 코드 즉, @Configuration 클래스나 @Component 가 붙은 클래스를 인자로 받아서 실행합니다

참고

  • 리프레시 상태 : 애플리케이션 컨텍스트가 완전히 초기화되고 모든 빈이 생성되고 설정이 끝난 상태를 의미합니다

AOT 조건

if (!AotDetector.useGeneratedArtifacts())
  • AOT(Ahead-of-Time)는 Spring Native와 관련된 컴파일 최적화 기술입니다.
  • 일반적으로는 false이므로, 위 조건은 항상 load()를 실행하게 됩니다.
  • 만약 설정을 통해 spring.aot.enabled=true 로 변경하면 이 루트가 달라집니다.


ApplicationContext 기본 구현체 생성 흐름

  • SpringApplication.run() 내부에서는 다음과 같이 ApplicationContext 를 생성합니다
context = this.createApplicationContext();

해당 메서드 호출은 다음과 같이 진행됩니다

  • ApplicationContextFactory.DEFAULTDefaultApplicationContextFactory
  • create(WebApplicationType) 호출
  • createDefaultApplicationContext() 호출

기본 설정 Spring Boot 에서의 BeanFactory 구현체를 알아야 합니다

  • 기본값인 DefaultApplicationContextFactory 를 실행하게 됩니다
public interface ApplicationContextFactory {
    ApplicationContextFactory DEFAULT = new DefaultApplicationContextFactory();
    ...
}
  • 결국 DefaultApplicationContextFactory.create() 메서드를 실행하게 됩니다
class DefaultApplicationContextFactory implements ApplicationContextFactory {
    
    public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
        try {
            return (ConfigurableApplicationContext)this.getFromSpringFactories(webApplicationType, ApplicationContextFactory::create, this::createDefaultApplicationContext);
        } catch (Exception var3) {
            Exception ex = var3;
            throw new IllegalStateException("Unable create a default ApplicationContext instance, you may need a custom ApplicationContextFactory", ex);
        }
    }
    
    private ConfigurableApplicationContext createDefaultApplicationContext() {
        return (ConfigurableApplicationContext)(!AotDetector.useGeneratedArtifacts() ? new AnnotationConfigApplicationContext() : new GenericApplicationContext());
    }
        
    private <T> T getFromSpringFactories(WebApplicationType webApplicationType, BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
        Iterator var4 = SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, this.getClass().getClassLoader()).iterator();

        Object result;
        do {
            if (!var4.hasNext()) {
                return defaultResult != null ? defaultResult.get() : null;
            }

            ApplicationContextFactory candidate = (ApplicationContextFactory)var4.next();
            result = action.apply(candidate, webApplicationType);
        } while(result == null);

        return result;
    }
}
  • 코드를 보면 getFromSpringFactories() 메서드를 호출합니다
  • SpringFactoriesLoader.loadFactories() 메서드에서 반환된 ApplicationContextFactory 구현체들을 순회하면서 적절한 ApplicationContext 를 생성하게 됩니다
  • 기본 Spring Boot 설정에서는 createDefaultApplicationContext() 메서드에 의해서 AnnotationConfigApplicationContext 객체가 ApplicationContext 로 설정됩니다

AnnotationConfigApplicationContext

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
    ...
}
  • 기본 Spring Boot 설정으로 인해 생성된 AnnotationConfigApplicationContextGenericApplicationContext 를 상속받아서 구현하고 있습니다
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
    private final DefaultListableBeanFactory beanFactory;
    
        public final ConfigurableListableBeanFactory getBeanFactory() {
            return this.beanFactory;
        }
        // 그외 많은 기능들
}
  • GenericApplicationContext 안에는 DefaultListableBeanFactory 타입의 beanFactory 를 가지고 있습니다
  • getBeanFactory() 를 통해 외부에서 꺼내서 사용할 수 있습니다
  • 해당 beanFactory 가 결국 모든 빈 정의를 등록/조회/생성하는 역할을 담당합니다

참고
ApplicationContext 가 곧 beanFactory 를 확장한 것 인데 내부에 beanFactory 필드가 또 있는 이유는 ?

  • ApplicationContext 는 기능이 많기 때문에, 핵심 빈 관리 책임을 beanFactory 에 위임하는 구조로 설계되었다


load()

protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
        logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }

    BeanDefinitionLoader loader = this.createBeanDefinitionLoader(this.getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
        loader.setBeanNameGenerator(this.beanNameGenerator);
    }

    if (this.resourceLoader != null) {
        loader.setResourceLoader(this.resourceLoader);
    }

    if (this.environment != null) {
        loader.setEnvironment(this.environment);
    }

    loader.load();
}

private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
    if (context instanceof BeanDefinitionRegistry registry) {
        return registry;
    } else if (context instanceof AbstractApplicationContext abstractApplicationContext) {
        return (BeanDefinitionRegistry)abstractApplicationContext.getBeanFactory();
    } else {
        throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
    }
}

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
    return new BeanDefinitionLoader(registry, sources);
}
  • getBeanDefinitionRegistry(context) 를 통해 빈 정의 레지스트리를 가져옵니다.
    • 대부분의 경우 DefaultListableBeanFactory 가 됩니다.
    • registry 는 간단히 이야기해서 "빈에 대한 정의를 생성하는 객체" 라고 이해하면 될 것 같습니다
  • 이후 BeanDefinitionLoader 를 생성합니다
  • 마지막에 loader.load() 를 호출하며 BeanDefinitionLoaderAnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner 를 활용하여 애플리케이션의 클래스 경로나 어노테이션을 스캔하여 빈 정의를 생성합니다
    • AnnotatedBeanDefinitionReader : 주로 @Configuration, @Component 등의 어노테이션을 읽고 빈 정의를 생성합니다
    • ClassPathBeanDefinitionScanner : 특정 패키지를 스캔하여 @Component, @Service, @Repository, @Controller 등으로 선언된 클래스를 빈으로 등록합니다
class BeanDefinitionLoader {
    private static final Pattern GROOVY_CLOSURE_PATTERN = Pattern.compile(".*\\$_.*closure.*");
    private final Object[] sources;
    private final AnnotatedBeanDefinitionReader annotatedReader;
    private final AbstractBeanDefinitionReader xmlReader;
    private final BeanDefinitionReader groovyReader;
    private final ClassPathBeanDefinitionScanner scanner;
    private ResourceLoader resourceLoader;
    
    BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
        Assert.notNull(registry, "Registry must not be null");
        Assert.notEmpty(sources, "Sources must not be empty");
        this.sources = sources;
        ...
    }
    
    void load() {
        for(Object source : this.sources) {
            this.load(source);
        }
    }
    
    ...
 }
  • BeanDefinitionLoaderload() 메서드에서 각 타입에 맞게 load() 메서드를 오버로딩하여 타입에 맞는 Scanner 혹은 Reader 를 호출.
  • 결국에는 Reader , Scanner 둘다 BeanDefinitionReaderUtils.registerBeanDefinition() 를 호출하게 됩니다
public abstract class BeanDefinitionReaderUtils {
    public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            String[] var4 = aliases;
            int var5 = aliases.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                String alias = var4[var6];
                registry.registerAlias(beanName, alias);
            }
        }
    }
}
  • 여기서 registry.registerBeanDefinition()DefaultListableBeanFactory.registerBeanDefinition() 를 호출하고, 인자로 beanName 의 문자열을 받아서 beanDefinitionNames 에 추가하는 작업을 합니다
  • 이름을 통해 beanDefinitionMap 을 생성하고 이 맵은 모든 빈의 정의들의 중앙 저장소 역할을 합니다
  • 이로 인해 스프링 컨텍스트는 빈 정의를 쉽게 조회하고 인스턴스화할 수 있습니다


빈 등록 및 빈 후처리 작업

  • prepareContext() 를 통해 빈 정의를 생성하고 현재 컨텍스트 기준으로 beanDefinitionMap 을 생성했습니다
  • 이후 refreshContext() 를 통해 빈 등록 및 빈 후처리 작업을 시작하게 됩니다


BeanPostProcessor

  • 빈 후처리기 (BeanPostProcessor)를 알아보겠습니다

  • Bean 의 인스턴스를 만든 이후 BeanInitialization Life Cycle 의 이전, 이후에 실행되는 작업들을 의미합니다

    • Bean 초기화 전
      • postProcessBeforeInitialization() : Bean 이 초기화되거 전에 호출
    • Bean 초기화 후
      • postProcessAfterInitialization() : Bean 의 초기화가 완료된 후에 호출
      • 예시) @PostConstruct, InitializingBean.afterPropertiesSet()
  • @AutowiredAutowiredAnnotationBeanPostProcessor 를 통해 구현되어 있습니다



refreshContext()

  • refreshContext()ApplicationContextrefresh() 메서드를 실행하게 되는데 기본 설정으로는 AbstractApplicationContext.refresh() 메서드를 실행하게 됩니다
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
    public void refresh() throws BeansException, IllegalStateException {
        ...
        try {
            ...
            this.registerBeanPostProcessors(beanFactory);
            this.finishBeanFactoryInitialization(beanFactory);
            ...
        } catch (Error | RuntimeException var12) {
            ...
        }
    }
}
  • 현재 설명할 중요한 메서드만 명시했습니다
    • registerBeanPostProcessors()
    • finishBeanFactoryInitialization()

registerBeanPostProcessors()

  • 해당 메서드는 PostProcessorRegistrationDelegate.registerBeanPostProcessors() 를 호출하고 있습니다
final class PostProcessorRegistrationDelegate {
    public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
        String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
        ...
        List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList();
        List<BeanPostProcessor> internalPostProcessors = new ArrayList();
        List<String> orderedPostProcessorNames = new ArrayList();
        List<String> nonOrderedPostProcessorNames = new ArrayList();
        ...
        
        String ppName;
        BeanPostProcessor pp;
        for(int var10 = 0; var10 < var9; ++var10) {
            ppName = var8[var10];
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class);
                priorityOrderedPostProcessors.add(pp);
             }
         }
         
         ...
        registerBeanPostProcessors(beanFactory, (List)nonOrderedPostProcessors);
        sortPostProcessors(internalPostProcessors, beanFactory);
        registerBeanPostProcessors(beanFactory, (List)internalPostProcessors);
    }
    
    private static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, List<? extends BeanPostProcessor> postProcessors) {
        if (beanFactory instanceof AbstractBeanFactory abstractBeanFactory) {
            abstractBeanFactory.addBeanPostProcessors(postProcessors);
        } else {
            Iterator var3 = postProcessors.iterator();

            while(var3.hasNext()) {
                BeanPostProcessor postProcessor = (BeanPostProcessor)var3.next();
                beanFactory.addBeanPostProcessor(postProcessor);
            }
        }

    }
}
  • 위 메서드는 registerBeanPostProcessors() 를 두가지 형태로 오버로딩한 메서드입니다
    • public 메서드
    • private 메서드
  • 맨 처음 (public 메서드) registerBeanPostProcessors() 의 시작은 위의 첫번째 메서드부터 시작되며 beanFactory.getBeanNamesForType() 를 통해 BeanPostProcessor 타입을 모두 조회합니다
    • 모든 BeanPostProcessor 타입을 가져옵니다
    • 이때 @Autowired 의 빈 후처리기인 AutowiredAnnotationBeanPostProcessor 구현체를 가져오게 됩니다
  • 모든 빈 후처리기를 가져와서 각 빈의 순서에 따라 정렬을 합니다
  • 이후 순서대로 순회하면서 beanFactory.getBean() 을 호출하게 됩니다
  • 구현체인 AbstractBeanFactory.getBean() 를 호출하게 되며 이는 doGetBean() 인 내부 메서드를 호출하며 빈을 가져오는 과정에서 빈을 생성할지, 기존의 인스턴스를 반환할지를 결정합니다
    • 이에 대한 내용은 아래 따로 구별해놓았습니다
  • 이후 두번째 (private 메서드) registerBeanPostProcessors() 메서드를 통해 abstractBeanFactory.addBeanPostProcessor(postProcessors) 를 실행하게 됩니다

addBeanPostProcessors()

  • 이어서 빈 후처리기를 등록하는 메서드를 확인해봅시다
public void addBeanPostProcessors(Collection<? extends BeanPostProcessor> beanPostProcessors) {
    synchronized(this.beanPostProcessors) {
        this.beanPostProcessors.removeAll(beanPostProcessors);
        this.beanPostProcessors.addAll(beanPostProcessors);
    }
}
  • 메서드 내용은 간단합니다, 기존의 등록된 빈 후처리기들이 있으면 모두 삭제하고 다시 등록합니다.
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    private final List<BeanPostProcessor> beanPostProcessors = new BeanPostProcessorCacheAwareList();
    @Nullable
    private BeanPostProcessorCache beanPostProcessorCache;
}
  • beanPostProcessors 를 살펴보면 BeanPostProcessorCacheAwareList 를 사용한걸로 보여집니다
  • 여기 BeanPostProcessorCacheAwareListadd 메서드를 통해 빈 후처리기를 등록하는데 등록하는 부분을 보면 beanProcessorCache 를 삭제한 후 등록하는 것으로 구현되어 있다
private class BeanPostProcessorCacheAwareList extends CopyOnWriteArrayList<BeanPostProcessor> {
        private BeanPostProcessorCacheAwareList() {
        }

        public BeanPostProcessor set(int index, BeanPostProcessor element) {
            BeanPostProcessor result = (BeanPostProcessor)super.set(index, element);
            AbstractBeanFactory.this.resetBeanPostProcessorCache();
            return result;
        }

        public boolean add(BeanPostProcessor o) {
            boolean success = super.add(o);
            AbstractBeanFactory.this.resetBeanPostProcessorCache();
            return success;
        }

        public void add(int index, BeanPostProcessor element) {
            super.add(index, element);
            AbstractBeanFactory.this.resetBeanPostProcessorCache();
        }

        public BeanPostProcessor remove(int index) {
            BeanPostProcessor result = (BeanPostProcessor)super.remove(index);
            AbstractBeanFactory.this.resetBeanPostProcessorCache();
            return result;
        }

        public boolean remove(Object o) {
            boolean success = super.remove(o);
            if (success) {
                AbstractBeanFactory.this.resetBeanPostProcessorCache();
            }

            return success;
        }
}

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
        private void resetBeanPostProcessorCache() {
            synchronized(this.beanPostProcessors) {
                this.beanPostProcessorCache = null;
            }
        }
}
  • BeanPostProcessorCacheAwarList 는 일반적인 리스트가 아니라 BeanPostProcessor 객체들을 효율적으로 관리하기 위한 Spring 의 특별한 리스트 클래스입니다
    • CopyOnWriteArrayList<BeanPostProcessor>java.util.concurrent 패키지에 포함되어 있으며 CopyOnWriteArrayList 는 스레드 안전한 리스트로 쓰기 작업 (추가, 수정, 삭제) 이 일어날 때마다 기존 배열을 복사하여 새로운 배열을 생성하는 방식으로 동작합니다
  • 내부적으로 BeanPostProcessor 들을 분류하고 캐시처럼 활용합니다
    • BeanPostProcessor 들은 빈 생성 과정에서 빈의 초기화 전후로 자주 호출됩니다, 매번 beanFactory 에서 BeanPostProcessor를 조회하는 것은 성능에 영향을 줄 수 있기때문에 Spring 은 BeanPostProcessor들을 한 번 가져와 캐시에 저장하고, 이후에는 이 캐시에서 BeanPostProcessor들을 사용합니다


getBean()

  • BeanPostProcessor 를 가져오기 위해 beanFactory.getBean() 을 호출합니다
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    public Object getBean(String name) throws BeansException {
        return this.doGetBean(name, (Class)null, (Object[])null, false);
    }

    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return this.doGetBean(name, requiredType, (Object[])null, false);
    }

    public Object getBean(String name, Object... args) throws BeansException {
        return this.doGetBean(name, (Class)null, args, false);
    }

    public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args) throws BeansException {
        return this.doGetBean(name, requiredType, args, false);
    }
    
    protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
        String beanName = this.transformedBeanName(name);
        Object sharedInstance = this.getSingleton(beanName);
        Object beanInstance;
        if (sharedInstance != null && args == null) {
            beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
        } else {
            if (this.isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }
            
            BeanFactory parentBeanFactory = this.getParentBeanFactory();
            if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
                String nameToLookup = this.originalBeanName(name);
                if (parentBeanFactory instanceof AbstractBeanFactory) {
                    AbstractBeanFactory abf = (AbstractBeanFactory)parentBeanFactory;
                    return abf.doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
                }
                if (args != null) {
                    return parentBeanFactory.getBean(nameToLookup, args);
                }
                if (requiredType != null) {
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                }
                return parentBeanFactory.getBean(nameToLookup);
            }
            
            if (!typeCheckOnly) {
                this.markBeanAsCreated(beanName);
            }

            try {
                RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
                this.checkMergedBeanDefinition(mbd, beanName, args);
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    for(int var14 = 0; var14 < var13; ++var14) {
                        String dep = prototypeInstance[var14];
                        ...
                        try {
                            this.getBean(dep);
                        } catch (NoSuchBeanDefinitionException var33) {
                            ...
                        } catch (BeanCreationException var34) {
                            ...
                        }
                    }
                }

                if (mbd.isSingleton()) {
                    sharedInstance = this.getSingleton(beanName, () -> {
                        try {
                            return this.createBean(beanName, mbd, args);
                        } catch (BeansException var5) {
                            BeansException ex = var5;
                            this.destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                } else if (mbd.isPrototype()) {
                    prototypeInstance = null;

                    Object prototypeInstance;
                    try {
                        this.beforePrototypeCreation(beanName);
                        prototypeInstance = this.createBean(beanName, mbd, args);
                    } finally {
                        this.afterPrototypeCreation(beanName);
                    }

                    beanInstance = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                } else {
                    String scopeName = mbd.getScope();
                    if (!StringUtils.hasLength(scopeName)) {
                        throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
                    }

                    Scope scope = (Scope)this.scopes.get(scopeName);
                    if (scope == null) {
                        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                    }

                    try {
                        Object scopedInstance = scope.get(beanName, () -> {
                            this.beforePrototypeCreation(beanName);

                            Object var4;
                            try {
                                var4 = this.createBean(beanName, mbd, args);
                            } finally {
                                this.afterPrototypeCreation(beanName);
                            }

                            return var4;
                        });
                        beanInstance = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    }
                }
            }
        }

    }

}
  • 최대한 필요한 부분만 추려보았습니다

1. 빈 이름 변환

  • 주어진 이름을 실제 빈 이름으로 변환하는 과정이며 빈 이름이 별칭일 수 있기 때문입니다

2. 싱글톤 캐시에서 빈 인스턴스 확인

  • Spring 에서 싱글톤 객체는 디자인 패턴의 생성자를 이용한 싱글톤 패턴을 구현한 것이 아닙니다

  • Spring 의 싱글톤 빈은 객체에 대한 전통적인 싱글톤 패턴을 코드로 구현한 것이 아니라 Spring 컨테이너가 싱글톤 범위로 관리 하여 구현된 것 입니다

  • 즉, 캐시를 통해서 싱글톤을 구현한 것 입니다

    • getBean() 을 통해 Spring은 빈의 라이프사이클에 따라 객체를 생성합니다

    • 생성된 인스턴스는 addSingleton() 메서드를 통해 singletoneObjects 라는 캐시에 저장됩니다

      public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
          private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
          private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
      
      		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);
              }
          }
      
        @Nullable
        protected Object getSingleton(String beanName, boolean allowEarlyReference) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    synchronized(this.singletonObjects) {
                        singletonObject = this.singletonObjects.get(beanName);
                        if (singletonObject == null) {
                            singletonObject = this.earlySingletonObjects.get(beanName);
                            if (singletonObject == null) {
                                ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
      
        return singletonObject;
      }
  • 그래서 이미 생성된 싱글톤 빈이 캐시에 존재하는지 확인한 후 존재한다면 캐시된 인스턴스를 사용하게 되도록 합니다 (this.getSingleton())

3. 싱글톤 인스턴스 반환

if (sharedInstance != null && args == null) {
    // 캐시된 인스턴스를 반환 
}
  • 만약 싱글톤이 존재하고 추가적인 생성자 인자가 없다면, 이미 존재하는 인스턴스를 반환합니다
  • 이 과정은 순한 참조를 방지하고 생성된 빈을 재사용하기 위해 사용됩니다
    • 순환 참조 : 두 개 이상의 빈이 서로를 의존하고 있어 초기화할 때 무한 루프에 빠지는 상황

4. 프로토타입 빈 생성 여부 확인

if (this.isPrototypeCurrentlyInCreation(beanName)) {
    // 예외
}
  • 만약 프로토타입 빈이 현재 생성 중인 경우, 순환 의존성을 피하기 위해 예외를 던집니다
    • 현재 스레드의 프로토타입 빈 정보를 가져온 후 beanName 과 같으면 순환 의존성이 생겼다고 파악합니다

5. 부모 빈 팩토리에서 조회

  • 현재 빈 팩토리에 빈 정의가 없고 부모 팩토리가 존재한다면, 부모 팩토리에서 해당 빈을 조회합니다

6. 새로운 빈 생성 준비

if (!typeCheckOnly) {
    this.markBeanAsCreated(beanName);
}
  • 빈을 생성할 준비를 합니다. 이는 빈이 처음으로 생성되고 있음을 표시하는 작업입니다

7. 빈 정의 (BeanDefinition) 가져오기 및 종속성 확인

RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
this.checkMergedBeanDefinition(mbd, beanName, args);
  • 빈 정의를 가져오고 해당 빈 정의가 유효한지 확인합니다. 종속성이 있는 경우, 필요한 종속성 빈들을 미리 가져와야 합니다
    • getMergedLocalBeanDefinition() 은 특정 빈의 최종 BeanDefinition 을 반환하는 역할을 합니다
    • 이 과정에서 상속된 부모 빈 정의와 병합 작업을 수행합니다
    • RootBeanDefinition 과 같은 실제 빈 정의 객체를 반환합니다

8. 종속성 처리

String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
    // 종속된 빈을 순회하며 가져옵니다.
}
  • dependsOn 속성을 통해 다른 빈에 대한 종속성을 확인하고, 필요한 빈들을 먼저 생성하여 등록합니다
  • 하지만 이것은 @Autowired 와는 다릅니다
  • dependsOn 은 일반적으로 빈 정의 수준에서 사용되며 특정 빈이 먼저 초기화되어야 할 때 사용됩니다
    • 예를 들어, 데이터베이스 연결이 필요한 서비스 빈은 데이터 소스 빈에 의존할 수 있고, 이를 dependsOn 으로 설정할 수 있습니다
    • 즉, 의존성 주입이 아닌 빈에 대한 초기화 순서를 조정하기 위한 것 입니다

9. 빈 생성 방식 결정

  • 싱글톤 빈의 경우 createBean() 을 호출하여 새로 생성하고 생성된 인스턴스를 싱글톤 캐시에 저장합니다
  • 프로토타입 빈은 매번 새로 생성하여 반환합니다
  • 커스텀 스코프가 정의된 경우 해당 스코프에서 빈을 생성하여 관리합니다


finishBeanFactoryInitialization()

  • 이제 모든 싱글톤 빈을 초기화하고 의존성을 주입합니다
  • 이 단계에서 @Autowired 가 실제로 동작합니다
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    ...
    beanFactory.freezeConfiguration();
    beanFactory.preInstantiateSingletons();
}
  • freezeConfiguration() : 빈 팩토리의 설정을 "동결"합니다, 이 설정이 완료되면 빈 정의를 더 이상 변경할 수 없으며 애플리케이션의 안정적인 동작을 보장합니다
  • preInstantiateSingletons() : 메서드의 핵심으로 모든 싱글톤 빈을 미리 인스턴화합니다, 이 과정에서 @Autowired 어노테이션에 의한 의존성 주입이 실제로 발생합니다

preInstantiateSingletons()

  • DefaultListableBeanFactory.preInstantiateSingletons() 를 실행합니다
    public void preInstantiateSingletons() throws BeansException {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Pre-instantiating singletons in " + this);
        }

        List<String> beanNames = new ArrayList(this.beanDefinitionNames);
        Iterator var2 = beanNames.iterator();

        String beanName;
        while(var2.hasNext()) {
            beanName = (String)var2.next();
            RootBeanDefinition bd = this.getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                if (this.isFactoryBean(beanName)) {
                    Object bean = this.getBean("&" + beanName);
                    if (bean instanceof SmartFactoryBean) {
                        SmartFactoryBean<?> smartFactoryBean = (SmartFactoryBean)bean;
                        if (smartFactoryBean.isEagerInit()) {
                            this.getBean(beanName);
                        }
                    }
                } else {
                    this.getBean(beanName);
                }
            }
        }

        var2 = beanNames.iterator();

        while(var2.hasNext()) {
            beanName = (String)var2.next();
            Object singletonInstance = this.getSingleton(beanName);
            if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
                StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize").tag("beanName", beanName);
                smartSingleton.afterSingletonsInstantiated();
                smartInitialize.end();
            }
        }
    }
  • this.beanDefinitionNames 는 이전에 load() 를 통해서 이미 beanDefinitionMap 을 만들고 beanDefinitionNames에 모든 빈을 추가했습니다
  • getBean() 을 찾아가 보면 createBean() 을 실행하는 걸 볼 수 있습니다.
  • 구현체는 AbstractAutowireCapableBeanFactory.createBean() 입니다
    • DefaultListableBeanFactory -> AbstractAutowireCapableBeanFactory
  • 이후 createBean() 은 내부 메서드인 doCreateBean() 을 호출하게 됩니다
  • doCreateBean()populateBean() 메서드를 호출하게 됩니다
    • 빈을 채운다 ("populate"), 이는 빈의 내부 상태를 설정하고 필요한 의존성을 채워 넣는 과정을 표현함
if (this.hasInstantiationAwareBeanPostProcessors()) {
    if (pvs == null) {
        pvs = mbd.getPropertyValues();
    }
    PropertyValues pvsToUse;
    for (Iterator var11 = this.getBeanPostProcessorCache().instantiationAware.iterator(); var11.hasNext(); pvs = pvsToUse) {
        InstantiationAwareBeanPostProcessor bp = (InstantiationAwareBeanPostProcessor)var11.next();
        pvsToUse = bp.postProcessProperties((PropertyValues)pvs, bw.getWrappedInstance(), beanName);
        if (pvsToUse == null) {
            return;
        }
    }
}
  • InstantiationAwareBeanPostProcessor 를 통해 @Autowired 와 같은 어노테이션이 있는 필드에 값을 주입하는 작업이 시작됩니다
  • getBeanPostProssorCache() 를 통해 이전에 가져온 BeanPostProcessor 들을 모두 가져옵니다
static class BeanPostProcessorCache {
    final List<InstantiationAwareBeanPostProcessor> instantiationAware = new ArrayList();
    final List<SmartInstantiationAwareBeanPostProcessor> smartInstantiationAware = new ArrayList();
    final List<DestructionAwareBeanPostProcessor> destructionAware = new ArrayList();
    final List<MergedBeanDefinitionPostProcessor> mergedDefinition = new ArrayList();

    BeanPostProcessorCache() {
    }
}
  • 각각의 List 는 빈의 라이프 사이클과 연관되어 있습니다
    • InstantiationAwareBeanPostProcessor : 빈이 인스턴스화될 때와 프로퍼티가 설정될 때 개입됨, 이 프로세서가 빈의 인스턴스화와 의존성 주입 시점에 직접적으로 영향을 미침
    • SmartInstantiationAwareBeanPostProcessor : 위 인터페이스의 하위 인터페이스이며 인스턴스화 과정에서 더욱 세밀하게 제어할 수 있는 기능을 제공함
    • DestructionAwareBeanPostProcessor : 빈의 소멸 단계에서 개입하는 프로세서이며 주로 destroy 메서드를 호출하거나, 빈이 컨테이너에서 제거될 때 필요한 작업을 처리함
    • MergedBeanDefinitionPostProcessor : 빈 정의가 병합된 후, 정의를 수정하거나 추가 작업을 수행하는 프로세서, 빈의 메타 데이터를 변경하거나 어노테이션 정보를 처리하는데 사용함
pvsToUse = bp.postProcessProperties((PropertyValues)pvs, bw.getWrappedInstance(), beanName);
  • postProcessProperties() 메서드를 통해 빈 후처리가 시작되며 AutowiredAnnotationBeanPostProcessor.postProcessProperties() 메서드가 실행되며 이때 의존성 주입이 시작됩니다
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs);

    try {
        metadata.inject(bean, beanName, pvs);
        return pvs;
    } catch (BeanCreationException var6) {
        BeanCreationException ex = var6;
        throw ex;
    } catch (Throwable var7) {
        Throwable ex = var7;
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
}
  • InjectionMetadata 클래스의 inject() 메서드를 통해 실제 의존성 주입을 하게됩니다
public class InjectionMetadata {

    public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
        Collection<InjectedElement> checkedElements = this.checkedElements;
        Collection<InjectedElement> elementsToIterate = checkedElements != null ? checkedElements : this.injectedElements;
        if (!((Collection)elementsToIterate).isEmpty()) {
            Iterator var6 = ((Collection)elementsToIterate).iterator();

            while(var6.hasNext()) {
                InjectedElement element = (InjectedElement)var6.next();
                element.inject(target, beanName, pvs);
            }
        }

    }
}
  • inject() 메서드에서는 빈들을 순환하면서 @Autowired 등의 어노테이션이 적용된 필드나 메서드에 의존성을 주입합니다


정리

  • load() 를 통해 빈을 조회하기 쉽도록 mapping table을 생성합니다
    • 이때 빈 정의 (BeanDefinition)을 모두 조회함
  • refreshContext() 를 통해 빈 후처리기 (BeanPostProcessor) 를 등록합니다
    • 이때 @Autowired 의 후처리기인 AutoWiredAnnotationBeanPostProcessor 가 등록됩니다
  • finishBeanFactoryInitialization() 를 통해 미리 조회한 빈을 가져온 후 빈으로 등록하며 생성하면서 빈 후처리기를 실행합니다
    • beanFactory.getBean()
    • InjectionMetadata.inject()

참고.

BeanFactory, & Prefix

  • 빈 이름에는 & 프리픽스가 붙어있는 빈 이름들이 있는데 이는 getBean("&test") 호출 시 getFactoryBean("test") 를 통해 FactoryBean 을 직접 반환하는 역할하는 하는 프리픽스입니다
    • FactoryBean 은 빈을 생성하는 팩토리 역할을 하는 특별한 스프링 빈입니다
    public interface BeanFactory {
      String FACTORY_BEAN_PREFIX = "&";
      ...
    }

@Value 어노테이션 처리

  • finishBeanFactoryInitialization() 메서드 과정에서 @Value("${mail.host}") 와 관련된 로직도 구현되어 있다
if (!beanFactory.hasEmbeddedValueResolver()) {
    beanFactory.addEmbeddedValueResolver((strVal) -> { 
        return this.getEnvironment().resolvePlaceholders(strVal); 
    }
);
  • 이 리졸버는 ${} 형식의 플레이스 홀더를 실제 값으로 치환하는 역할을 하며, 환경 변수나 설정 파일의 값을 해결하는데 사용됩니다
    • hasEmbeddedValueResolver()BeanFactory 에 값 리졸버가 등록되어 있는지 확인하는 부분이며 만약, 등록되어 있지 않다면 새로운 리졸버를 추가합니다
    • addEmbeddedValueResolver()${} 형식의 플레이스 홀더를 처리하기 위해 리졸버를 추가하는 작업이며, 이 리졸버는 주로 @Value 어노테이션에서 사용되는 플레이스홀더를 실제 값으로 변환하는 역할을 합니다
profile
개발자

0개의 댓글

관련 채용 정보