[스프링 IoC 컨테이너와 빈]_03 빈 주입 방법
@Autowired
필요한 의존 객체의 “타입”에 해당하는 빈을 찾아 주입한다.
@Autowired
● required: 기본값은 true (따라서 못 찾으면 애플리케이션 구동 실패)
사용할 수 있는 위치
● 생성자 (스프링 4.3 부터는 생략 가능)
● 세터
● 필드
경우의 수
● 해당 타입의 빈이 없는 경우
● 해당 타입의 빈이 한 개인 경우
● 해당 타입의 빈이 여러 개인 경우
○ 빈 이름으로 시도,
■ 같은 이름의 빈 찾으면 해당 빈 사용
■ 같은 이름 못 찾으면 실패
같은 타입의 빈이 여러개 일 때
● @Primary
● 해당 타입의 빈 모두 주입 받기
● @Qualifier (빈 이름으로 주입)
동작 원리
● 첫시간에 잠깐 언급했던 빈 라이프사이클 기억하세요?
● BeanPostProcessor
○ 새로 만든 빈 인스턴스를 수정할 수 있는 라이프 사이클 인터페이스
● AutowiredAnnotationBeanPostProcessor extends BeanPostProcessor
○ 스프링이 제공하는 @Autowired와 @Value 애노테이션 그리고 JSR-330의
@Inject 애노테이션을 지원하는 애노테이션 처리기.
※ [실습]
프로젝트 생성 : [Spring Initializr] > [Dependencies] : web
선택
BookService와 BookRepository 클래스를 생성 후, BookService만 @Service 로 빈으로 만들고 BookRepository는 빈으로 등록하지 않은 상태에서 BookService에서 BookRepository 의존성 주입을 해보자
@Autowired 사용할 수 있는 위치
1. 생성자로 주입
IDE에서 BookRepository로 등록된 빈이 없어서 알려주고 있지만 의도한 코드이므로 무시하고 넘어간다.
애플리케이션을 실행하여 오류를 확인
⇒ 오류 내용 : BookService 생성자의 0번째 파라미터에 해당하는 BookRepository 빈이 없다.
⇒ 해결 방안 : BookRepository에 해당하는 빈을 정의해라
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in dev.solar.demospring51.BookService required a bean of type 'dev.solar.demospring51.BookRepository' that could not be found.
Action:
Consider defining a bean of type 'dev.solar.demospring51.BookRepository' in your configuration.
Process finished with exit code 1
BookRepository에 @Repository 를 붙여주자
@Component 어노트에션을 붙일 수도 있지만 Repository로 활용할 클래스이기 때문에 @Repository를 붙여주는 것이 좋다. 특정한 기능을 실행시킬 수도 있고, AOP에서도 사용하기 더 좋기 때문에 구분해서 쓰는 것이 더 좋다.
다시 실행하면 에러 없이 잘 주입이 되는 것을 확인할 수 있다.
2. Setter로 주입
BookRepository에 @Repository를 붙이지 않고 실행하면 동일하게 오류가 나는 것을 확인할 수 있다.
생성자로 주입한 경우와 Setter로 주입한 경우에 발생한 에러에서 알 수 있는 점
생성자로 주입한 경우 BookService
를 만드려고 할 때 부터 에러가 나는 것은 분명하다.
Setter로 주입하는 경우, Setter를 호출할 때 해당하는 빈이 없어서 오류가 나는 것은 맞지만, BookService 인스턴스 자체는 만들 수 있는 것 아닌가?
맞는 말이지만, @Autowired
라는 어노테이션이 붙어있기 때문에 BookService 빈을 생성할 때
의존성을 주입하려고 시도한다. 따라서 실행 시 오류가 발생하는 것이다.
@Autowired
어노테이션이 필수가 아니라 옵셔널한 사항이라면 @Autowired(required = false)
로 설정해서 실행하면 에러가 발생하지 않는다.
@Service
public class BookService {
BookRepository bookRepository;
@Autowired(required = false)
public void setBookRepository(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
}
3. 필드로 주입
필드 주입 시에도 @Autowired 설정을 required = false로 설정해서 옵셔널하게 지정하면 에러 없이 빈 생성이 가능하다.
@Service
public class BookService {
@Autowired(required = false)
BookRepository bookRepository;
}
-
생성자 인젝션과 Setter, 필드 인젝션의 차이
Setter와 필드 의존성 주입시에는 옵셔널하게 설정해서 해당하는 빈이 없이도 인스턴스 자체는 만들도록 할 수 있다.
해당 타입의 빈이 여러 개인 경우
BookRepository를 인터페이스로 수정하고, 이를 구현한 MyBookRepository와 SolarBookRepository 두 개의 구현체가 있는 경우
다음과 같이 BookService에서 BookRepository를 주입받으려고 하면 오류가 발생한다.
@Service
public class BookService {
@Autowired
BookRepository bookRepository;
}
- 오류 메시지
BookService에서 필요한 bookRepository 필드에 하나의 빈이 필요한데, 해당하는 타입의 빈이 2개가 발견됐다.
myBookRepository, solarBookRepository 둘 중에 어떤 것을 써야할 지 모르겠다.
-
추천하는 액션
- 여러가지 빈들 중에
@Primary
를 붙여서 마킹을 해라
@Qualifier
로 어떤 빈이 필요한지 지정해라 - 여러가지 빈들 중에
Description:
Field bookRepository in dev.solar.demospring51.BookService required a single bean, but 2 were found:
- myBookRepository: defined in file [/Users/ssun/Develop/Project/SpringProjects/study-spring/project/demospring51/target/classes/dev/solar/demospring51/MyBookRepository.class]
- solarBookRepository: defined in file [/Users/ssun/Develop/Project/SpringProjects/study-spring/project/demospring51/target/classes/dev/solar/demospring51/SolarBookRepository.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
1. @Primary 를 붙여서 마킹(추천)
사용하고자 하는 빈에 @Primary
어노테이션을 붙인다.
@Repository @Primary
public class SolarBookRepository implements BookRepository{}
어떠한 빈이 주입됐는지 확인하기 힘드므로 ApplicationRunner
구현체를 하나 만들어 주자 (참고. 이 내용은 스프링 부트 강좌에서)
BookService에서 주입받은 BookRepository 구현체가 어떤 것이지 내용을 출력해주는 메서드 생성
@Service
public class BookService {
@Autowired
BookRepository bookRepository;
public void printBookRepository() {
System.out.println(bookRepository.getClass());
}
}
Runner에서 해당 메서드를 호출
@Component
public class BookServiceRunner implements ApplicationRunner {
@Autowired
BookService bookService;
@Override
public void run(ApplicationArguments args) throws Exception {
bookService.printBookRepository();
}
}
애플리케이션을 실행하면 (자동으로) Runner들을 전부 쭉 호출해준다.
2. @Qualifier(빈 이름) 으로 주입
@Repository 어노테이션을 쓰면 이 빈의 id는 small case로 시작하는 빈의 이름과 동일하게 된다.
@Repository
public class SolarBookRepository implements BookRepository{} //-> 빈 id : solarBookRepository
@Qualifier("빈이름")
으로 지정해주면 된다.
@Service
public class BookService {
@Autowired @Qualifier("solarBookRepository")
BookRepository bookRepository;
public void printBookRepository() {
System.out.println(bookRepository.getClass());
}
}
하지만, 좀 더 Type Safety한 @Primary
방식을 사용할 것을 추천한다.
3. 해당 타입의 빈 모두 주입받기
@Service
public class BookService {
@Autowired
List<BookRepository> bookRepositories;
public void printBookRepository() {
this.bookRepositories.forEach(System.out::println);
}
}
4. 빈의 이름과 동일한 필드명으로 주입받기(비추천)
@Autowired는 타입 뿐만 아니라 이름도 확인한다.
@Service
public class BookService {
@Autowired
BookRepository myBookRepository;
public void printBookRepository() {
System.out.println(myBookRepository.getClass());
}
}
우선순위
빈과 동일한 필드명으로 적었어도 특정 빈에 @primary
가 붙어있다면 해당하는 빈으로 생성된다.
동작원리
BeanPostProcessor
라는 라이프 사이클 인터페이스의 구현체에 의해서 동작한다.
BeanPostProcessor
-
새로 만든 빈 인스턴스를 수정할 수 있는 라이프 사이클 인터페이스
- 빈을 만들고 빈의 인스턴스를 만든 다음에 빈의 initialize 라이프 사이클이 있다.
- 빈의 initialize 라이프 사이클 전후로 추가적인 동작을 구현할 수 있는 라이프 사이클 콜백이
BeanPostProcessor
이다.
1. @PostConstruct
@Service
public class BookService {
@Autowired
BookRepository myBookRepository;
public void printBookRepository() {
System.out.println(myBookRepository.getClass());
}
@PostConstruct
public void setUp() {
//이 빈이 만들어진 다음에 해야할 동작을 정의
}
}
2. InitializingBean 상속
- afterPropertiesSet() 를 구현해야함
@Service
public class BookService implements InitializingBean {
@Autowired
BookRepository myBookRepository;
public void printBookRepository() {
System.out.println(myBookRepository.getClass());
}
@Override
public void afterPropertiesSet() throws Exception {
//이 빈이 만들어진 다음에 해야할 동작을 정의
}
}
그 중 AutowiredAnnotationBeanPostProcessor
가 동작해서 @Autowired 어노테이션을 처리(해당 하는 빈을 찾아서 주입)해주는 것이다.
따라서 기존에 생성된 빈 정보를 출력하는 printBookRepository() 코드를 @PostConstruct
으로 작성해도 동일하다.
이미 빈이 생성된 후의 라이프 사이클에서 동작하는 코드 임을 알아야 한다.
@Service
public class BookService {
@Autowired
BookRepository myBookRepository;
@PostConstruct
public void setUp() {
System.out.println(myBookRepository.getClass());
}
}
클래스 이름이 찍힌 위치가 다르다. Runner는 애플리케이션이 구동이 완료된 이후에 실행되는 반면에 @PostConstruct 라이프사이클 콜백은 InitializingBean’s afterPropertiesSet
단계에서 실행되는 것이기 때문이다.
※ 라이프 사이클
- BeanNameAware’s
setBeanName
- BeanClassLoaderAware’s
setBeanClassLoader
- BeanFactoryAware’s
setBeanFactory
- EnvironmentAware’s
setEnvironment
- EmbeddedValueResolverAware’s
setEmbeddedValueResolver
- ResourceLoaderAware’s
setResourceLoader
(only applicable when running in an application context) - ApplicationEventPublisherAware’s
setApplicationEventPublisher
(only applicable when running in an application context) - MessageSourceAware’s
setMessageSource
(only applicable when running in an application context) - ApplicationContextAware’s
setApplicationContext
(only applicable when running in an application context) - ServletContextAware’s
setServletContext
(only applicable when running in a web application context) postProcessBeforeInitialization
methods of BeanPostProcessors- InitializingBean’s
afterPropertiesSet
<—— ** - a custom init-method definition
postProcessAfterInitialization
methods of BeanPostProcessors
BeanPostProcessor
구현체 중 하나가 동작하는 것이라고 생각하면된다.
BeanFactory가 자신에게 등록되어있느 BeanPostProcessor
타입의 빈을 찾는다. 구현체 중에 하나인 AutowiredAnnotationBeanPostProcessor
가 빈으로 등록되어있는 것이다. AutowiredAnnotationBeanPostProcessor
에 실제 어노테이션을 처리하는 로직들이 정이되어있고 이를 다른 일반적인 빈들한테 적용하는 것이다.
※ AutowiredAnnotationBeanPostProcessor
가 빈으로 등록되어 있다는 것을 확인하는 방법
빈을 직접 가져올 수 있고, 빈을 담고 있는 ApplicationContext를 가져와서 빈을 꺼낼 수 있다.
- ApplicationRunner를 생성 후, ApplicationContext를 주입받아서 AutowiredAnnotationBeanPostProcessor 빈을 꺼내와 확인
@Component
public class MyRunner implements ApplicationRunner {
@Autowired
ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) throws Exception {
AutowiredAnnotationBeanPostProcessor bean = applicationContext.getBean(AutowiredAnnotationBeanPostProcessor.class);
System.out.println(bean);
}
}
- AutowiredAnnotationBeanPostProcessor 가 이미 빈으로 등록이 되어있으므로 @Autowired 로 바로 받아올 수 있다.
@Component
public class MyRunner implements ApplicationRunner {
@Autowired
AutowiredAnnotationBeanPostProcessor processor;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(processor);
}
}