@Autowired 개념을 알기 위해 먼저 DI 개념을 알아야 하므로 아래 포스팅 참고.
@Autowired 동작 원리
@Autowired는 Spring Framework에서 DI(Dependency Injection)을 위해 제공하는 어노테이션이다.
위 DI 포스팅을 살펴보았다면 해당 어노테이션이 생성자, 필드, 혹은 setter 메서드에 붙을 수도 있다는 것을 알게 됐을 것이다.
그렇다면 어떻게 @Autowired를 이용하여 직접 생성하지 않은 객체를 주입받을 수 있는 것일까?
Spring Framework에서 Bean을 생성하고 관리하는 일은 스프링 Container가 담당하며, ApplicationContext라는 인터페이스로 구현되어 있다.
Spring Core Framework가 빈을 생성하는 프로세스는 아래와 같다.
- beans.xml이라는 스프링 컨텍스트 파일(spring boot가 나온 이후 xml 방식보다 java configuration 설정 방식을 더 많이 사용합니다)을 로딩해서 읽어 들인다.
- 이를 바탕으로 Final Bean Definition을 생성한다.
- Bean Definition은 Bean에 대한 일종의 Configuration 정보로, 이 설정 정보를 바탕으로 실제 Bean을 생성하게 된다.
즉, Spring이 기동될 때 이 ApplicationContext가 @Bean이나 @Component 등 어노테이션을 이용하여 등록한 스프링 빈을 생성하고, @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이다.
BeanPostProcessor
빈은 스프링 컨테이너가 관리하기 위한 생명주기(LifeCycle)가 존재한다.
이때 BeanPostProcessor는 빈의 초기화(Initialization) 단계의 이전과 이후에 또 다른 부가적인 작업을 할 수 있게 해주는 인터페이스이다.
따라서 빈에 접근해야 하기 때문에 당연히 빈을 관리하는 '스프링 컨테이너'에 등록되어 있다.
※ 참고
싱글톤 Bean 생명 주기
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존 관계 주입 -> 초기화 콜백 -> 사용 -> 소멸 전 콜백 -> 스프링 종료
위 그림의 우측에 파란색으로 표시해둔 부분을 보면 방금 설명한 빈의 초기화 단계의 이전과 이후에 해당하는 postProcessBeforeInitialization 메서드와 postProcessAfterInitialization 메서드가 BeanPostProcessor에 있는 것을 확인할 수 있다. 여기서 초기화 단계의 이전에 동작하는 메서드 덕분에 빈을 찾을 수 있는 것이다.
AutowiredAnnotationBeanPostProcessor
위에서 설명한 BeanPostProcessor도 결국 구현체가 있어야 메서드를 이용할 수 있는데, @Autowired에 있어서 핵심이 되는 클래스가 바로 AutowiredAnnotationBeanPostProcessor이다.
이 또한 빈에 접근해야 하기 때문에 당연히 빈을 관리하는 '스프링 컨테이너'에 등록되어 있다.
위에서 @Autowired에 대해 설명할때 필드, 메서드, 클래스에 대해 의존관계 주입을 해준다고 했었는데
이는 processInjection 메서드가 빈의 클래스 정보를 읽어와서 (getClass()) 자동(auto)으로 의존관계를 설정할 메타데이터를 얻고 주입(inject()) 하는 것이다.
아래는 processInjection메서드 구현 내용이다.
코드를 보면 InjectionMetadata 클래스의 inject 메서드를 호출하여 객체를 주입하게 되는데 이 메서드의 구현은 아래와 같다.
이 메서드가 객체를 주입할 때 ReflectionUtils라는 클래스를 사용하는 것을 볼 수 있다. 즉, @Autowired는 리플렉션을 통해 수행되는 것이다.
실제로 AutowiredAnnotationBeanPostProcessor는 InjectMetadata를 상속받는 AutowiredFieldElement와 AutowiredMethodElement을 구현하고 있으며, 여기서 오버라이딩된 inject가 호출된다.
위 코드는 InjectionMetadata의 inject메서드로 다소 차이가 있으나 동작 과정만 살펴보면 충분할 것이다.
Autowiring되는 객체들은 일반적으로 private으로 선언되는 경우가 많다. 그럼에도 객체가 주입될 수 있는 것은 리플렉션 때문이며, 위 코드상에서는 makeAccessible 메서드가 이를 가능하게 하는 것이다. 리플렉션으로 메서드 정보를 makeAccessible 하게 만들어서 method.invoke() 로 빈을 주입하는 것이다.
AbstractAutowireCapableBeanFactory
위에서 간단히 설명한 빈 라이프사이클에 따르면 PostProcessor가 빈을 주입하기 전에 당연히 빈이 생성되어있어야 하고 의존관계가 설정되어 있어야 한다.
@Autowired에 있어서는 AbstractAutowireCapableBeanFactory가 빈의 생성, 빈의 의존관계 주입(DI)을 담당한다.
BeanFactory는 BeanPostProcessor 타입의 빈을 꺼내 일반적인 빈들을 @Autowired로 의존관계 주입이 필요한 빈들에게 @Autowired를 처리하는 로직을 적용한다.
공식문서는 아래처럼 설명이 적혀있다.
Abstract bean factory superclass that implements default bean creation, with the full capabilities specified by the RootBeanDefinition class.
해석 : 기본 빈 생성을 구현하는 추상 빈 팩토리 상위클래스이며, RootBeanDefinition 클래스에서 지정한 모든 기능을 사용할 수 있다.
Implements the AutowireCapableBeanFactory interface in addition to AbstractBeanFactory's createBean(java.lang.Class<T>) method.
해석 : AutowireCapableBeanFactory 인터페이스를 구현하며 빈 생성을 담당하는 메서드인 AbstractBeanFactory 클래스의 createBean 메서드가 있다.
Provides bean creation (with constructor resolution), property population, wiring (including autowiring), and initialization.
해석 : 어떤 생성자 기반의 빈 생성을 할지, 엔티티에서 프로퍼티 설정은 어떻게 할지, 빈 간의 관계 설정(자동설정 포함)과 초기화에 대한 기능을 제공한다.
Handles runtime bean references, resolves managed collections, calls initialization methods, etc. Supports autowiring constructors, properties by name, and properties by type.
해석 : 런타임 빈 참조, 스프링에서 관리되는 컬렉션 타입 선택, 초기화 메서드 호출 등을 다룬다. 생성자 autowiring, 이름에 의한 프로퍼티 설정, 타입에 의한 프로퍼티 설정을 지원한다.
@Service
public class ArticleServiceImpl implements ArticleService {
private final ArticleMapper articleMapper;
// ArticleMapper 필드를 ArticleServiceImpl 생성자에 Autowiring 하는 것
@Autowired
ArticleServiceImpl(ArticleMapper articleMapper) {
this.articleMapper = articleMapper;
}
}
위 코드를 예시로 분석해보자. 생성자 주입 방식으로 ArticleMapper 필드를 ArticleServiceImpl 생성자에 주입받아야 하는 경우이다.
DefaultListableBeanFactory 클래스의 preInstantiateSingletons 메서드에서 생성자 타입인 articleServiceImpl 빈의 이름을 토대로 빈을 생성하고
AbstractAutowireCapableBeanFactory 클래스의 createBeanInstance 메서드에서
determineConstructorsFromBeanPostProcessors 메서드로 Autowire DI 대상이 될 수 있는 생성자 정보(생성자 클래스 ArticleServiceImpl, 파라미터 ArticleMapper)를 받아 반환하고
ConstructorResolver 클래스의 autowireConstructor 메서드에서 생성자 정보를 토대로 빈을 생성하고 설정해두면
후에 리플렉션 기반으로 생성자 인스턴스를 생성(newInstance)해서 ArticleServiceImpl 생성자에 ArticleMapper를 의존관계 주입 해주는 것이다
※ 참고
리플렉션으로 어떤 과정을 통해 의존관계가 주입되는지에 대한 것은 이 링크에 있는 설명글을 참고 바란다.
빈(Bean) 등록과 조회 규칙
빈(Bean) 등록
Spring은 기본적으로 메소드/클래스의 이름을 Bean의 이름으로 사용한다.
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
하지만 이처럼 개발자가 직접 빈의 이름을 부여할 수도 있다.
@Bean("fixDiscountPolicy")
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
@Bean("rateDiscountPolicy")
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
빈(Bean) 조회 규칙(전략)
@Autowired가 등록된 빈을 찾을 때에는 다음과 같은 매칭 규칙으로 빈을 조회한다.
- 주입 받고자하는 타입(Type)으로 매칭을 시도한다.
- 타입이 여러 개면 필드 또는 파라미터 이름으로 매칭을 시도한다.
하지만 빈의 이름이 충돌되어 빈 이름만으로 해결이 불가능한 경우 또는 빈에 추가 구분자나 우선순위를 부여하고 싶은 경우에 @Qualifer나 @Primary 어노테이션을 이용해 편리하게 해결할 수 있다.
추가로 Spring은 자바 표준 어노테이션인 @Resource라는 어노테이션도 기능을 지원하고 있다. @Resource는 @Autowired와 달리 필드 이름으로 빈을 찾는다.
- @Autowired: 필드 타입을 기준으로 빈을 찾음
- @Resource: 필드 이름을 기준으로 빈을 찾음
해당 타입의 빈이 없는 경우
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DiscountService {
DiscountPolicy discountPolicy;
@Autowired
public DiscountService(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
생성자에 @Autowired 어노테이션이 있는 상태이다. DiscountPolicy가 빈으로 등록되어 있지 않다고 가정하면 생성자에서 에러가 발생한다. 이유는 Autowired가 의존성을 주입하기 위해서는 빈으로 등록되어 있는 객체 중에서 찾기 때문이다. DiscountPolicy라는 객체는 빈으로 등록되어 있지 않기 때문에 의존성 주입에 실패해서 에러가 발생한다.
(Autowired의 기본값이 true이다)
Autowired가 기본값이 true라고 했는데 그렇다면 false로 하면 어떻게 될까?
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DiscountService {
DiscountPolicy discountPolicy;
@Autowired(required = false)
public DiscountService(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
위와 같이 쓴다면 DiscountService의 객체는 빈으로 등록이 되지만 DiscountPolicy는 빈으로 등록되지 않게 된다. (required = false이기 때문이다)
하지만 생성자에 Autowired를 쓴 상황에서 DiscountPolicy가 빈으로 등록되어 있지 않다면 DiscountService도 빈으로 등록되지 못하는 경우가 생긴다.
해당타입의 빈이 여러 개인 경우
만약 DiscountPolicy라는 인터페이스 아래에 FixDiscountPolicy, RateDiscountPolicy라는 클래스가 존재하고 둘다 빈으로 등록이 되어 있을 때 바로 위의 코드처럼 DiscountService생성자 Autowired를 적용하면 어떻게 될까? 주입을 해줄 수 없게 된다. 왜냐하면 등록해야 할 빈이 2개이기 때문에 스프링은 어떤걸 해야할지 모르기 때문이다.
이때 사용할 수 있는 방법이 @Qualifier와 @Primary 이다.
@Qualifier와 @Primary
@Qualifier - 빈의 Alias(구분자)
빈의 이름만으로 부족하고, 추가적인 정보가 필요할 수 있다. 그런 상황에서 Qualifier 어노테이션을 통해 빈에 추가 구분자(Alias)를 붙여줄 수 있다. (Bean의 이름을 바꾸는 것은 아니다.)
만약 우리가 위의 두 가지 DiscountPolicy 중에서 FixDiscountPolicy를 중점적으로 활용하고 싶다면, 해당 Bean에 mainDiscountPolicy라는 별칭 또는 구분자를 부여해줄 수 있다.
@Qualifier("mainDiscountPolicy")
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
그러면 다음과 같이 빈을 찾고자 하는 경우에 @Qualifer 어노테이션을 부여하여 빈을 찾도록 할 수 있다.
public DiscountService(@Qualifier("mainDiscountPolicy") fixDiscountPolicy) {
this.discountPolicy = fixDiscountPolicy;
}
- 해당 @Qualifier가 붙은 빈을 조회한다.
- @Qualifier가 붙은 빈을 못찾으면 필드 또는 파라미터 이름으로 매칭을 시도한다.
- 그래도 찾지 못하면 NoSuchBeanDefinitionException 이 발생한다.
물론 빈을 찾고자하는 경우에 @Qualifier를 붙이지 않아도 정상적으로 작동한다. 하지만 이렇게 하면 유지보수 하면서 헷갈릴 수 있으므로 빈을 생성하는 곳과 찾는 곳 모두에 @Qualifier를 붙여줘야한다.
@Primiary - 빈의 우선순위 부여
여러 타입의 빈이 존재할 때, 특정 빈을 우선적으로 주입하도록 하고 싶다면 @Primary 어노테이션을 사용할 수 있다.
Spring이 타입으로 빈을 찾다가 Primary가 붙어있는 빈을 발견하면, 바로 해당 빈을 주입시킨다. 즉, @Primary는 여러 개의 빈들 중에서 우선순위를 부여하는 방법이다.
@Primary
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
만약 Primary와 Qualifier 모두 등록이 되어있다면 이름을 직접 지정해주는 Qualifier가 우선순위를 갖는다.
그리고 마지막으로는 해당타입의 빈을 모두 주입받는 경우도 있다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DiscountService {
@Autowired
List<DiscountPolicy> discountPolicyList;
@Autowired
Map<String, DiscountPolicy> discountPolicyMap;
}
위와 같이 List와 Map를 이용하면 DiscountPolicy에 해당하는 타입의 빈을 모두 주입받을 수 있다.
여러 방법중에서 제일 추천하고 싶은 방법은 @Primary이다
빈 등록 출동 발생
하지만 위와 같이 설정해주지 않아 빈 등록 시에 충돌이 발생한다면 Spring에서는 다음과 같이 처리가 된다.
- 자동 빈 vs 자동 빈: 빈 이름 중복 에러 발생
- 수동 빈 vs 자동 빈: 과거에는 수동빈이 자동 빈을 덮어버렸음 But 최근에는 에러를 발생시키도록 변경됨
어떻게 @Autowired 어노테이션만으로 의존성 주입이 가능할까?
위에서 이미 설명한바 있지만 마지막으로 정리를 해보고자 한다.
public interface BeanPostProcessor
BeanPostProcessor라는 라이프 사이클 인터페이스의 구현체인 AutowiredAnnotationBeanPostProcessor에 의해 의존성 주입이 이루어진다. BeanPostProcessor는 빈의 initializing(초기화) 라이프 사이클 이전, 이후에 필요한 부가 작업을 할 수 있는 라이프 사이클 콜백이다. 그리고 BeanPostProcessor의 구현체인 AutowiredAnnotationBeanPostProcessor가 빈의 초기화 라이프 사이클 이전, 즉 빈이 생성되기 전에 @Autowired가 붙어있으면 해당하는 빈을 찾아서 주입해주는 작업을 하는 것이다.
예상 면접 질문 및 답변
Q. Autowiring 과정에 대해서 설명해주세요.
컨테이너에서 타입(인터페이스 또는 오브젝트)을 이용해 의존 대상 객체를 검색하고 할당할 수 있는 빈 객체를 찾아 주입한다
참고