본문 바로가기

Programing/Framework

[Spring] Bean 생성시 필드주입 시점은?

객체의 생성은 프레임워크가 해준다. 문제는 프레임워크가 JVM을 넘어 마술을 부리는 줄 아는 경우가 있다.

아래 글을 읽어보면 결국 프레임워크도 JVM이 제공해주는 리플렉션 도구들을 이용해서 객체를 만들고 private 필드에 값을 설정함을 알 수 있다.

질문

import org.springframework.stereotype.Service;

@Service
public class TestService {
    public void print() {
        System.out.println("TestService - print");
    }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class TestByFieldInject {
    @Autowired
    TestService testService;

    public TestByFieldInject() {
        testService.print();
    }
}

Q Test 클래스에서 common프로젝트 TestService를 필드 주입을 받으면 null 예외가 발생하는데

생성자 주입을 받으면 정상적으로 실행이 됩니다.

왜 이런 차이가 발생하는건가요??

 

이유는?

결론부터 이야기하면 필드주입이라는 것이 생성자가 수행이 완료되고 호출되기 때문이다.

스프링에서는 객체를 보통 new를 쓰듯이 만들지는 않는다.

 

일종의 org.springframework.beans 패키지에 있는 BeanUtils의 instantiateClass 정적 메서드를 이용해서 Bean 객체를 생성한다.

결론적으로는 자바의 java.lang.reflect 패키지의 newInstance 가 호출이 되는 것이다. (리플렉션을 이용한 객체 생성)

 

이후 빈 내부의 @Autowire 마킹이 되어 있는 빈들의 주입은 populateBean 호출에 의해 수행된다.

참고로 생성자 호출은 createBeanInstance 호출이 수행한다. 코드상 훠얼씬 위에 있다.

구체적으로는?

위에서는 막연하게 populateBean 이라고 했는데,

BeanPostProcessor 들을 통해 빈 후처리기들을 통해 빈을 구성하는 작업을 수행한다.

이중 스프링 2.5에 추가된 AutowiredAnnotaionBeanPostProcessor가 그 역할을 수행한다.

 

AutowiredAnnotationBeanPostProcessor 는 아래 그림처럼 InstantiationAwareBeanPostProcessor 인터페이스를 구현하고 있는데

이 인터페이스의 postProcessProperties 메서드가 프로퍼티들을 처리하는 역할이다. (5.1 전에는 postProcessPropertyValues 가 그 역할을 수행했으나 deprecated되었다.)

package org.springframework.beans.factory.config;

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
	// ... abbr
	/**
	 * @since 5.1
	 */
	@Nullable
	default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
			throws BeansException {

		return null;
	}
    
    @Deprecated
	@Nullable
	default PropertyValues postProcessPropertyValues(
			PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

		return pvs;
	}

}   

실제 Processor에 의해 필드 주입은 InjectionMetadata의 inject의해 수행되지만 결국 다시 AutowiredAnnotationBeanPostProcessor 내부에 있는 AutowiredFieldElement 클래스의 inject 가 호출된다.

필드가 private인데 어떻게 주입이 될까?

예상했겠지만 리플렉션을 통해 액세스가 가능하게 한다.

package org.springframework.beans.factory.annotation;

public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
		implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
	// ...
	private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {
		// ...
		@Override
		protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			// ..
			if (this.cached) {
				value = resolvedCachedArgument(beanName, this.cachedFieldValue);
			}
			else {
				// ..
                value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
                // ..
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}
		}
	}