본문 바로가기
Spring Framework

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

by kellis 2020. 10. 12.

@Autowired는 Spring Framework에서 Dependency Injection을 위해 제공하는 어노테이션으로, 이를 모르는 웹 개발자는 없을 것입니다. 우리는 습관처럼 자연스럽게 @Autowired를 사용하여 객체를 주입받습니다. 이 어노테이션이 생성자에 붙을 수도, 필드에 붙을 수도, 혹은 setter 메서드에 붙을 수도 있다는 것도 알고 있습니다. 그렇다면 어떻게 이 짧은 코드로 직접 생성하지도 않은 객체를 주입받을 수 있는 것일까요? 

 

Spring Framework에서 Bean을 생성하고 관리하는 일은 IoC(Inversion of Control) Container가 담당하며, ApplicationContext라는 인터페이스로 구현되어 있습니다. Spring Core Framework가 빈을 생성하는 프로세스는 아래와 같습니다. 

그림에서 beans.xml이라는 스프링 컨텍스트 파일(spring boot가 나온 이후 xml 방식보다 java configuration 설정 방식을 더 많이 사용합니다)을 로딩해서 읽어 들이고, 이를 바탕으로 Final Bean Definition을 생성합니다. Bean Definition은 Bean에 대한 일종의 Configuration 정보로, 이 설정 정보를 바탕으로 실제 Bean을 생성하게 됩니다. 즉, Spring이 기동될 때 이 ApplicationContext가 @Bean이나 @Service, @Controller 등 어노테이션을 이용하여 등록한 스프링 빈을 생성하고, @Autowired 어노테이션이 붙은 위치에 의존성 주입을 수행하게 되는 것입니다. 

 

어차피 스프링이 알아서 생성하고 주입해 줄 텐데, 우리는 어노테이션을 사용할 줄만 알면 되는 것 아니냐고 생각할 수도 있습니다. 그러나 이런 기본적인 내용을 알아두는 것은 중요합니다. 위와 같은 의존성 주입은 Bean으로 등록된 클래스에서만 가능하기 때문입니다. 만약 우리가 static 메서드나 new로 생성한 인스턴스에서 Bean을 참조해야 하는 경우, 즉 Bean이 아닌 클래스에서 Bean을 주입받을 필요가 생긴다면 어떻게 해야 할까요?

 

위의 내용을 알고 있다면, @Autowired 어노테이션이 단순히 ApplicationContext에서 Bean을 찾아 주입해주는 형식이라는 것을 알고 있으므로 원하는 객체를 어노테이션 대신 코드로 주입할 수 있습니다.  

ApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
Object obj = applicationContext.getBean(beanName);

 

Spring-beans 모듈에 구현되어 있는 Autowired의 코드는 아래와 같습니다. 

/**
 * Marks a constructor, field, setter method or config method as to be
 * autowired by Spring's dependency injection facilities.
 *
 * ...중략
 *
 * <p>Note that actual injection is performed through a
 * {@link org.springframework.beans.factory.config.BeanPostProcessor
 * BeanPostProcessor} which in turn means that you <em>cannot</em>
 * use {@code @Autowired} to inject references into
 * {@link org.springframework.beans.factory.config.BeanPostProcessor
 * BeanPostProcessor} or {@link BeanFactoryPostProcessor} types. Please
 * consult the javadoc for the {@link AutowiredAnnotationBeanPostProcessor}
 * class (which, by default, checks for the presence of this annotation).
 *
 * @author Juergen Hoeller
 * @author Mark Fisher
 * @since 2.5
 * @see AutowiredAnnotationBeanPostProcessor
 * @see Qualifier
 * @see Value
 */
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}
  • Target : 이 어노테이션은 생성자와 필드, 메서드에 적용이 가능합니다. 
  • Retention : 이 어노테이션은 컴파일 이후에도(런타임 시에도) JVM에 의해 참조가 가능합니다. 런타임 시 어노테이션의 정보를 리플렉션으로 얻을 수 있습니다. 

 

위 코드 상단의 주석을 살펴보면 'actual injection is performed through a {@link org.springframework.beans.factory.config.BeanPostProcessor BeanPostProcessor}' 라는 내용을 찾을 수 있습니다. 즉 실제 타깃에 Autowired가 붙은 빈을 주입하는 것은 BeanPostProcessor라는 말입니다. 그리고 BeanPostProcessor의 구현이 바로 AutowiredAnnotationBeanPostProcessor입니다.

 

이 클래스에서 살펴 볼 것은 processInjection메서드입니다.

/**
     * 'Native' processing method for direct calls with an arbitrary target instance,
     * resolving all of its fields and methods which are annotated with <code>@Autowired</code>.
     * @param bean the target instance to process
     * @throws BeansException if autowiring failed
*/
public void processInjection(Object bean) throws BeansException {
    Class<?> clazz = bean.getClass();
    InjectionMetadata metadata = findAutowiringMetadata(clazz);
    try {
        metadata.inject(bean, null, null);
    }
    catch (Throwable ex) {
        throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex);
    }
}

@Autowired로 어노테이티드된 필드나 메서드에 대해서 객체를 주입하는 역할을 수행합니다. InjectionMetadata 클래스의 inject 메서드를 호출하여 객체를 주입하게 되는데 이 메서드의 구현은 아래와 같습니다. 

protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable {
    if (this.isField) {
        Field field = (Field) this.member;
        ReflectionUtils.makeAccessible(field);
        field.set(target, getResourceToInject(target, requestingBeanName));
    }
    else {
        if (checkPropertySkipping(pvs)) {
            return;
        }
        try {
            Method method = (Method) this.member;
            ReflectionUtils.makeAccessible(method);
            method.invoke(target, getResourceToInject(target, requestingBeanName));
        }
        catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

이 메서드가 객체를 주입할 때 ReflectionUtils라는 클래스를 사용하는 것을 볼 수 있습니다. 즉, @Autowired는 리플렉션을 통해 수행되는 것입니다. 

(실제로 AutowiredAnnotationBeanPostProcessor는 InjectMetadata를 상속받는 AutowiredFieldElement와 AutowiredMethodElement을 구현하고 있으며, 여기서 오버라이딩된 inject가 호출됩니다. 위 코드는 InjectionMetadata의 inject메서드로 다소 차이가 있으나 우리가 궁금한 것은 어떻게 동작하는가이기 때문에 위 코드로 살펴보겠습니다)

 

오토와이어링되는 객체들은 일반적으로 private으로 선언되는 경우가 많습니다. 그럼에도 객체가 주입될 수 있는 것은 리플렉션 때문이며, 위 코드상에서는 makeAccessible 메서드가 이를 가능하게 하는 것입니다. 그렇게 접근이 가능하게 되면 field.set 또는 method.invoke를 통해 객체가 주입되게 됩니다. 


자바에서 리플렉션은 굉장히 중요한 개념입니다. 스프링의 내부 구현 대부분이 리플렉션을 통해 이루어지고 있다고 말해도 과언이 아닐 만큼 큰 역할을 합니다. 따라서 다음 글에서는 리플렉션의 개념을 간단히 알아보도록 하겠습니다. 

'Spring Framework' 카테고리의 다른 글

Java Reflection을 이용한 Bean Autowiring 구현  (0) 2020.10.12

댓글