생각해보기
Autowire 동작 방식 알아보기 본문
현재 회사에서 기존의 레거시를 python fastapi로 전환하는 작업을 진행하고 있습니다. fastapi에서 depency injection(DI)가 spring framework 만큼 편리하지 않았기에, spring boot는 어떻게 DI를 진행하는지 궁금하게 되었습니다.
spring boot에서는 주로 @Autowire 어노테이션을 사용하여 DI 진행합니다. @Autowire을 사용하여 주입하는 bean을 주입하는 방식은 대표적으로 3가지가 있습니다
1. 생성자 주입
생성자가 호출되는 시점에 주입이 이루어집니다. 생성자를 통한 주입은 객체가 생성되는 시점에 모든 의존성이 주입되므로, 이후에는 해당 의존성이 변경되지 않는 불변성을 보장합니다.
@Service
public class MyService {
private final UserRepository userRepository;
@Autowired
public MyService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
2. 세터 주입
bean이 생성된 이후 세터 메서드가 호출되는 시점에 주입이 이루어집니다.
@Service
public class MyService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
3. 필드 주입
bean이 생성되고 나서 초기화 단계에서 직접 필드에 주입됩니다. 이 경우, 리플렉션을 통해 private 필드에도 접근이 가능하며, setter 메서드가 없어도 됩니다.
@Service
public class MyService {
@Autowired
private UserRepository userRepository;
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
그렇다면 이러한 DI에 관련한 주요한 클래스와 인터페이스는 다음 등이 있습니다.
- ApplicationContext: Spring의 IoC 컨테이너를 나타내며, BeanFactory를 확장한 서브인터페이스입니다. 전체 어플리케이션의 설정과 생명 주기를 관리합니다.
- BeanFactory: 실제로 Bean의 생성, 설정, 관리를 담당하는 인터페이스입니다. ApplicationContext 인터페이스가 이를 확장하므로 모든 ApplicationContext 구현체가 BeanFactory의 기능을 제공합니다.
- AutowireCapableBeanFactory: 자동 와이어링을 지원하는 BeanFactory의 확장입니다. autowire 메서드를 제공하여 클래스 인스턴스를 자동 와이어링할 수 있게 해줍니다.
- BeanPostProcessor: Bean의 초기화 과정에서 추가적인 처리를 수행할 수 있게 해주는 인터페이스입니다. 예를 들어, @Autowired 어노테이션을 처리하는 것과 같은 역할을 할 수 있습니다.
- AutowiredAnnotationBeanPostProcessor: @Autowired 어노테이션을 처리하는 BeanPostProcessor의 구현체입니다.
- DefaultListableBeanFactory: 대부분의 ApplicationContext 구현체에서 사용되는 기본 BeanFactory입니다. Bean 정의를 등록하고, 생성하고, 자동 와이어링하고, 초기화하는 데 사용됩니다.
- @Autowired, @Qualifier 어노테이션: 이 어노테이션들은 필드, 생성자, 메서드에 적용되어 의존성 주입을 명시합니다.
bean이 생성될 때 의존성 주입은 대략적으로 이와 같이 동작 합니다
- Client: ApplicationContext를 초기화하고, 특정 bean을 요청합니다.
- ApplicationContext: BeanFactory를 사용하여 해당 bean을 가져옵니다.
- BeanFactory: bean 정의를 확인하고, bean의 생성자와 의존성을 확인합니다. 필요한 경우 AutowireCapableBeanFactory를 통해 자동 와이어링을 수행합니다.
- AutowireCapableBeanFactory: bean을 생성하고, BeanPostProcessor를 통해 초기화를 수행합니다.
- BeanPostProcessor (예: AutowiredAnnotationBeanPostProcessor): @Autowired, @Qualifier 등의 어노테이션을 처리하고, 의존성을 주입합니다.
- BeanFactory: 완전히 초기화된 bean을 반환합니다.
- ApplicationContext: bean을 Client에 반환합니다.
실제 동작 어떻게 동작이 되는 지 코드를 통해 알아봅시다
AbstractAutowireCapableBeanFactory
//AbstractAutowireCapableBeanFactory
...
/**
* Actually create the specified bean. Pre-creation processing has already happened
* at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
* <p>Differentiates between default bean instantiation, use of a
* factory method, and autowiring a constructor.
* @param beanName the name of the bean
* @param mbd the merged bean definition for the bean
* @param args explicit arguments to use for constructor or factory method invocation
* @return a new instance of the bean
* @throws BeanCreationException if the bean could not be created
* @see #instantiateBean
* @see #instantiateUsingFactoryMethod
* @see #autowireConstructor
*/
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
// createBeanInstance호출 bean정보를 가지는 BeanWrapperImpl 객체 반환
// BeanWrapperImpl 객체는 JavaBean 스타일의 객체에 대한 접근을 캡슐화하는 역할을 하며,
// 프로퍼티의 읽기 및 쓰기, 프로퍼티 타입 조회, 프로퍼티 수정 등을 수행할 수 있다고 한다
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
....
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 빈 주입 진행
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
throw bce;
}
else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
}
}
....
}
// bean inject
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
if (bw == null) {
if (mbd.hasPropertyValues()) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
}
else {
// Skip property population phase for null instance.
return;
}
}
if (bw.getWrappedClass().isRecord()) {
if (mbd.hasPropertyValues()) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Cannot apply property values to a record");
}
else {
// Skip property population phase for records since they are immutable.
return;
}
}
// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
// state of the bean before properties are set. This can be used, for example,
// to support styles of field injection.
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
return;
}
}
}
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
int resolvedAutowireMode = mbd.getResolvedAutowireMode();
if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
// Add property values based on autowire by name if applicable.
if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}
// Add property values based on autowire by type if applicable.
if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}
pvs = newPvs;
}
if (hasInstantiationAwareBeanPostProcessors()) {
if (pvs == null) {
pvs = mbd.getPropertyValues();
}
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
// 의존성 주입
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
pvs = pvsToUse;
}
}
boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
if (needsDepCheck) {
PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
checkDependencies(beanName, mbd, filteredPds, pvs);
}
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
AbstractAutowireCapableBeanFactory 클래스의 populateBean 메서드는 Spring Framework에서 bean의 의존성을 주입하는 과정을 담당합니다.
구체적으로, populateBean 메서드는 다음과 같은 과정을 수행합니다:
- 해당 Bean의 의존성 정보 분석: 메서드는 먼저 해당 Bean의 클래스 정보와 Bean 정의를 분석하여 어떤 의존성이 필요한지 판단합니다.
- 의존성 값 결정: 이어서, 해당 의존성의 값을 결정하기 위해 BeanFactory를 사용합니다. 이 과정에서는 @Autowired, @Resource 등의 어노테이션과 XML 설정 등을 고려합니다.
- 의존성 주입: 마지막으로, 결정된 의존성 값을 해당 Bean 인스턴스의 적절한 필드나 메서드에 주입합니다. 필드 주입, 세터 메서드 주입, 생성자 주입 등의 방식이 이 과정에서 사용될 수 있습니다.
AutowiredAnnotationBeanPostProcessor
...
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
...
빈의 프로퍼티 값이 설정되기 직전에 호출되어, 이러한 어노테이션을 기반으로 의존성 주입을 수행합니다.
https://atoz-develop.tistory.com/entry/Spring-Autowired-동작-원리-BeanPostProcessor
'Spring' 카테고리의 다른 글
spring security와 filter (0) | 2023.08.20 |
---|---|
스프링 부트와 서블릿 (0) | 2023.08.12 |
Transactional 어노테이션에 대해 알아보자 (0) | 2023.07.21 |
스프링 request와 경로 매핑 (0) | 2023.07.07 |
springjpa 정리 -2- (0) | 2022.01.07 |