preview image
허원철의 개발 블로그

Spring - @Autowired는 어떻게 동작하는 걸까?

2020-07-04

@Autowired의 동작 원리를 간단하게 이해해보자.

스프링에서 Bean으로 등록된 객체에 특정 Bean에 대한 의존성을 주입할 때, 스프링에서 제공(@Autowired, @Value)하는 혹은 자바 제공(@Inject, @Resource)하는 애노테이션들이 어떤 원리로 주입이 되는 것?에 대한 궁금증이 생겼다. 그것은 바로 BeanPostProcessor 이라는 클래스에 해답을 얻을 수 있다.

BeanPostProcessor는 스프링 컨테이너 안에서 만든 bean에 전/후처리 작업을 할 수 있도록 만든 인터페이스이다. 여러 구현체들을 보면 AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor 등등 다양한 BeanPostProcessor들이 존재하며, 서드파티 라이브러리 중에서도 스프링 위에서 동작할 수 있도록 BeanPostProcessor를 추가로 제공하기도 한다. (MeterRegistryBeanPostProcessor, ArmeriaBeanPostProcessor, …)

1
2
3
4
5
6
public interface BeanPostProcessor {
// 빈 생성 이전에 실행되는 메소드
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
// 빈 생성 이후에 실행되는 메소드
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

인터페이스는 굉장히 깔끔하고 단순하다.

그렇다면 BeanPostProcessor의 생성시점은 언제일까? 코드를 들여다 보면 다음과 같다.

https://github.com/spring-projects/spring-framework/blob/5.2.x/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java#L535

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

...

호출 시점만 놓고 보자면 Bean 작업 이전에 BeanPostProcessor에 대한 작업을 한 후에 Bean 작업이 되는 것을 볼 수 있다.

커스텀 BeanPostProcessor 만들어보기

간단한 컴포넌트와 커스텀 BeanPostProcessor를 준비하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Component
public class Person {

private String name;

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

// ...

@Configuration
public MyBeanPostProcessor implements BeanPostProcessor {

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Person) {
Person heowc = (Person) bean;
heowc.setName("heowc");
}
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}

간단하게 이름을 필드로 갖는 Person이라는 클래스를 만들었다. 그리고 이것이 Bean으로 등록이 된다면, Person 객체 name 필드에 'heowc’를 초기화해주는 코드다. 이를 테스트한 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
@SpringBootTest
class MyBeanPostProcessorTest {

@Autowired
private Person heowc;

@Test
void test_personInjection() {
assertThat(heowc.getName()).isEqualTo("heowc");
}
}