스프링부트 해부학 : IoC컨테이너(2) - 빈의 초기화와 의존성 주입(DI)

정윤성·2022년 6월 6일
1

스프링부트 해부학

목록 보기
6/20

역할

현재 BeanDefinition에 대한 정보만 저장하고있는데 이를 실제 컨테이너에 Bean으로 등록해주어야 우리가 사용할 수 있다

이때 다양한 생성전략과 주입전략을 스프링에서 지원해준다


SingletonBean 초기화

AbstractApplicationContext.class

@Override
public void refresh() throws BeansException, IllegalStateException {
	...
    registerBeanPostProcessors(beanFactory); // PostProcessor
    ...
    onRefresh() // DispatcherServlet, Filter, WebServer, Servlet, Other..
    ...
    finishBeanFactoryInitialization(beanFactory);
}

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
	String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
	for (String weaverAwareName : weaverAwareNames) { // JPA
   		getBean(weaverAwareName);
    }
    ...
    beanFactory.preInstantiateSingletons(); // Non-Lazy Singleton
}

PostProcessor관련된 Bean들이 제일 먼저 초기화가 되며 onRefresh를 통해 DispatcherServlet, Filter등의 Servlet과 관련된 Bean들이 초기화가 되고 마지막 finishBeanFactoryInitialization을 통해 Non-Lazy한 모든 Singleton Bean들이 초기화가 된다

JPA에서 Entity Lazy로딩때문에 weaving을 사용하는것 같다 Bean의 초기화 순서를 보면 PostProcessor -> Servlet -> JPA -> BootApplication 순으로 초기화가 된다

preInstantiateSingletons에 대해 조금더 살펴보자

AbstractApplicationContext.class

@Override
public void preInstantiateSingletons() throws BeansException {
	...
	for (String beanName : beanNames) {
    	RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
        	...
            getBean(beanName);
        }
    }
}

AbstractBeanFactory.class

@Override
public Object getBean(String name) throws BeansException {
	return doGetBean(name, null, null, false);
]

protected <T> T doGetBean(
	String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
    throws BeansException {
    ...
    Object sharedInstance = getSingleton(beanName); // Instance생성
    ...
    String[] dependsOn = mbd.getDependsOn(); // 의존하는 Bean 초기화 보장
    if (dependsOn != null) {
    	for (String dep : dependsOn) {
        	...
            registerDependentBean(dep, beanName);
            getBean(dep);
            ...
        }
    }
    
    if (mbd.isSingleton()) {
    	sharedInstance = getSingleton(beanName, () -> {
        	...
            return createBean(beanName, mbd, args); // Bean 초기화
            ...
        }
        beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }
    
    return adaptBeanInstance(name, beanInstance, requiredType);
}

doGetBean메서드는 getSingleton을 통해 이미 생성되어있는 Bean이 있다면 가져오고 없다면 밑에 createBean을 통해 생성한뒤 전달한다

우리는 의존성주입에 대해 좀더 알아봐야하기에 createBean에대해 좀더 들어가보자

AbstractAutowireCapableBeanFactory.class

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {
    ...
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    return beanInstance;
}

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
	throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    ...
    if (instanceWrapper == null) {
    	instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	...
    populateBean(beanName, mbd, instanceWrapper);
    exposedObject = initializeBean(beanName, exposedObject, mbd);
    ...
    return exposedObject;
}

createBeanInstance를 통해 Instance를 생성하는데 이때 생성전략에 따른 의존성 주입이 진행된다 ( 생성자 주입, 팩토리메서드 주입 )

이 후 popluateBean을 통해 주입전략이 진행된다 ( 필드 주입, 메서드 주입 )

마지막으로 initializeBean을 통해 Bean초기화를 진행한다

즉 BeanInstance생성 -> 필드 & 메서드 주입 -> Bean초기화 단계로 진행된다

이들 각각 순차적으로 알아볼 예정이다 먼저 생성전략에대해서 알아보자


의존성 주입(생성전략)

생성자 주입

AbstractAutowireCapableBeanFactory.class

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
	throws BeanCreationException {
    ...
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    ...
}

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
	Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
    	mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
    	return autowireConstructor(beanName, mbd, ctors, args);
	}
}

protected BeanWrapper autowireConstructor(
	String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {

	return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
}
Bean의 생성 과정을 보면 Bean Instance를 우선적으로 생성하게되는데 이 때 생성자에 값 대입이 필요한경우 ConstructorResolver를 의존성 주입을 하게된다

ConstructorResolver에 대해서 좀더 살펴보자

ConstructorResolver.class

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
	@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
    ...
    ArgumentsHolder argsHolder;
    ...
    argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
    	getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
    ...
    return bw;
}

private ArgumentsHolder createArgumentArray(
	String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,
    BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,
    boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException {
    ...
    Object autowiredArgument = resolveAutowiredArgument(
    	methodParam, beanName, autowiredBeanNames, converter, fallback);
    ...
}

@Nullable
protected Object resolveAutowiredArgument(MethodParameter param, String beanName,
	@Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {
	...
    return this.beanFactory.resolveDependency(
    	new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
    ...
}

DefaultListableBeanFactory.class

@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
	@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
	...
    result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    ...
}

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
	@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    ...
    instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
    ...
}

DependencyDescriptor.class

public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
	throws BeansException {

	return beanFactory.getBean(beanName);
}

ConstructorResolver는 DI도중 Bean이 없을 수도 있기에 getBean을 통해 재귀적으로 Bean 생성과정을 한번더 거친다

ConstructorResolver.class

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
	@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
    ...
    bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
    ...
    return bw;
}
private Object instantiate(
	String beanName, RootBeanDefinition mbd, Constructor<?> constructorToUse, Object[] argsToUse) {
    ...
    InstantiationStrategy strategy = this.beanFactory.getInstantiationStrategy();
    ...
    return strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
    ...
}

SimpleInstantiationstrategy.class

@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
	final Constructor<?> ctor, Object... args) {
    ...
    return BeanUtils.instantiateClass(ctor, args);
    ...
}

의존성Bean을 생성 후 다시 ConstructorResolver.class로 돌아와 BeanUtils을 통해 Bean을 최종적으로 생성한다 ( 아직 스프링내에서 초기화는 되지않은 상태 )

팩토리 메서드 주입

다음과같이 Bean내에 매개변수로 다른Bean이 주입되는 상황을 말한다
AbstractAutowireCapableBeanFactory.class

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
	...
    if (mbd.getFactoryMethodName() != null) {
    	return instantiateUsingFactoryMethod(beanName, mbd, args);
    }
    ...
}

protected BeanWrapper instantiateUsingFactoryMethod(
	String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {

	return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs);
}

ConstrcutorResolver.class

public BeanWrapper instantiateUsingFactoryMethod(
	String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    ...
    argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
    	getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
    ...
    bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
    ...
    return bw;
}

@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
    @Nullable Object factoryBean, final Method factoryMethod, Object... args) {
    ...
    Object result = factoryMethod.invoke(factoryBean, args);
    if (result == null) {
   		result = new NullBean();
    }
    return result;
    ...
}
FactoryMethod인경우 다음과같이 instantiateUsingFactoryMethod를 거쳐 인자로 주입되는 Bean에 대해 생성자 주입과 똑같은 과정을 거치고 모든 DI가 완료되면 마지막에 Method.invoke를 통해 Method를 실행한다

그래서 Factory Method Bean의 출력결과를 보면 항상 Method에 내용이 먼저 출력되고 BeanPostProcessor, PostConstruct, afterPropertiesSet순으로 실행된다

의존성 주입(주입 전략)

위의 과정을 통해 Bean Instance를 생성했다 하지만 필드주입과 메서드주입같은 경우는 생성자가 없기에 아직은 Empty한 Instance이다

populateBean

AbstractAutowireCapableBeanFactory.class

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
	...
	for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
    	PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
    ...
}

populateBean메서드에서 PostProcessor작업을 진행하는데 이때 AutowiredAnnotationBeanPostProcessor를 만나게되면 @Autowired가 붙어있는 Property가 있는지 확인하며 있을시 주입을 시작한다

필드 vs 메서드 구분


AutowiredAnnotationBeanPostProcessor.class

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
	...
    metadata = buildAutowiringMetadata(clazz);
    ...
}

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
	...
	MergedAnnotation<?> ann = findAutowiredAnnotation(field);
    if (ann != null) {
    	currElements.add(new AutowiredFieldElement(field, required));
    }
    ...
    MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
    if (ann != null) {
    	currElements.add(new AutowiredMethodElement(method, required, pd));
    }
    
    return InjectionMetadata.forElements(elements, clazz);
}

findAutowiringMetadata메서드가 호출되면 내부적으로 buildAutowiringMetadata를 실행하는데 여기서 Annotation이 Field에있으면 FieldElement, Method에있으면 MethodElement로 저장한다

필드 주입

AutowiredAnnotationBeanPostProcessor.class

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
	...
    InjectionMetadata metadata = findAutowiringMetadata(clazz.getName(), clazz, null);
	...
    metadata.inject(bean, beanName, pvs);
    ...
}

InjectionMetadata.class

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	element.inject(target, beanName, pvs); // element = AutowiredAnnotationBeanPostProcessor.FieldElement
}



AutowiredFieldElement.class

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	...
    value = resolveFieldValue(field, bean, beanName);
    ...
}

@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
	DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
    ...
    value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
    ...
}

DefaultListableBeanFactory.class

@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
	@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    ...
    result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    ...
}           

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
	@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    ...
    instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
    ...
}

찾은 Metadata가 필드라면 AutowiredFieldElement를 통해 injection작업을 진행하는데 이 과정은 생성자 주입과 일치한다

메서드 주입

AutowiredMethodElement.class

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	...
    arguments = resolveMethodArguments(method, bean, beanName);
    ...
    if (arguments != null) {
    	...
    	method.invoke(bean, arguments);
    }
    ...
}

@Nullable
Qprivate Object[] resolveMethodArguments(Method method, Object bean, @Nullable String beanName) {
	...
    Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
    ...
]

메서드 주입같은경우 기존 Field주입방식을 그대로 사용한다
차이점이라면 Arguments에 주입시킨 뒤 이를 method.invoke를 통해 실제 setter 메서드를 실행시키는 것 이다

Bean의 초기화

위의 과정을 통해 Bean Instance생성과 Property주입을 진행하였다
이제 남은건 Bean초기화 작업만 남았다 !

AbstractAutowireCapableBeanFactory.class

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
	Object wrappedBean = bean;
    ...
    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); // postProcessBeforeInitialization
    ...
    invokeInitMethods(beanName, wrappedBean, mbd); // afterPropertiesSet
    ...
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); // postProcessAfterInitialization
    ...
    return wrappedBean;
}

initializeBean메서드를 통해 PostProcessorBefore, afterPropertiesSert, PostProcessorAfter의 작업을 순차적으로 진행한다

이 과정때 우리가등록한 BeanPostProcessor, @PostConstruct, Bean의 InitializingBean 생명주기가 진행된다

PostProcessor를 디버깅해보면 진행순서 또한 확인해볼 수 있는데

이를통해 BeanPostProcessor는 7번, @PostConstruct와 관련된 CommonAnnotationBeanPostProcessor는 12번에 있는걸 알 수 있다 따라서 우리가 설정한 BeanPostProcessor가 먼저 출력되는 것이다

정리

  1. BeanDefinition을 토대로 Bean을 생성하고 초기화 하는 방법에 대해 알 수 있었다
  2. Bean을 생성할때는 생성자 주입, 팩토리메서드 주입이 있고 초기화 과정중에는 필드 주입과 메서드 주입이 있다 ( 마지막은 결국다 DependencyDescriptor.class를 통해 주입이 됨 )
  3. 이렇게 Bean이 생성되면 스프링은 Bean생명주기 초기화 작업을 진행한다

마무리

이렇게 ComponentScan을 통해 BeanDefinition을 정의하는 법부터 이를 토대로 의존성 주입과 Bean을 스프링에서 어떻게 초기화 하는지에 대해 알아봤습니다

이 다음으로는 Event에 대해서 알아보도록 하겠습니다

profile
게으른 개발자

0개의 댓글