IoC 컨테이너의 가장 기본적인 역할은 코드를 대신하여 애플리케이션을 구성하는 오브젝트를 생성하고 관리하는 것이다. 컨테이너는 빈 설정 메타정보를 통해 빈의 클래스와 이름을 제공받아 활용한다.
설정 메타정보는 외부 리소스에 맞는 리더를 통해 읽어 BeanDefinition 타입의 오브젝트로 변환되고 이를 컨테이너가 활용하는 것이다.
BeanDefinition은 순수한 오브젝트로 표현되는 빈 생성 정보로 메타정보가 작성되는 형식의 종류와 작성 방식에 독립적이다.
몇 가지 필수 값을 제외하면 컨테이너에 미리 지정된 디폴트 값이 적용된다. BeanDefinition은 여러 개의 빈을 만들기 위해 재사용될 수도 있다. 메타정보는 같으나 이름이 다른 여러 개의 빈 오브젝트를 만들 수도 있기 때문이다. 따라서 BeanDefinition에는 빈의 이름이나 아이디를 나타내는 정보는 포함되지 않는다.
빈 등록 방법
빈 등록은 빈 메타정보를 작성해서 컨테이너에게 건네주면 된다.
가장 직접적인 방식으로 BeanDefinition 구현 오브젝트를 직접 생성하는 것이지만, 보통 XML이나 프로퍼티 파일, 자바 configuration을 통한 외부 리소스로 빈 메타정보를 작성하고 리더나 변환기를 통해 애플리케이션 컨텍스트가 사용할 수 있는 정보로 변환해주는 방법을 사용한다.
자동 인식을 이용한 빈 등록
XML이 아닌 특별한 애너테이션을 통해 자동으로 대상 클래스를 찾아 빈으로 등록하는 방법이 있다.
스프링의 빈 스캐너는 지정한 클래스 패스 이하 모든 패키지의 클래스를 대상으로 @Component 애너테이션 또는 이 애너테이션을 메타 애너테이션으로 사용하는 클래스를 선택하여 빈으로 등록한다.
@Component나 이 애너테이션을 메타 애너테이션으로 사용하는 @Controller / @Service / @Repository 등을 스테레오 타입 애너테이션 이라고 부른다.
다음과 같이 @Component를 메타 애너테이션을 사용해서 자신만의 스테레오 타입의 애너테이션을 만들 수도 있다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 빈 스캐너 디폴트 필터의 자동 인식이 대상이 되게 한다.
public @interface cutomStereotypeAnnotation {
String value() default "";
}
@Component를 통해 등록된 빈의 id는 첫 글자만 소문자로 바꾼 클래스 이름으로 등록된다. 물론 다른 이름을 지정할 수도 있다.
@Component("OtherId")
public class Hello {
}
빈 스캐닝 방식은 XML처럼 상세한 메타정보 항목을 지정할 수 없고, 클래스 당 한 개 이상의 빈을 등록할 수 없다는 제한이 있다.
XML 파일에서는 다음과 같이 지정하여 자동으로 빈 스캐너를 등록할 수 있다.
<context:component-scan base-package="" />
ContextLoaderListener를 통한 루트 애플리케이션 등록할 때 빈 스캐닝을 사용하려면 AnnotationConfigApplicationContext 클래스와 contextConfigLocation 파라미터에 패키지 이름을 지정하면 된다.
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>springbook.learningtest.spring.ioc.bean</param-value>
</context-param>
서블릿 컨텍스트라면 <init-param> 으로 지정하면 된다.
@Resource
주입할 빈을 빈의 id로 지정할 때 사용한다. 자바 클래스의 setter 뿐만 아니라 필드에도 붙일 수 있다.
@Resource(name = "printer")
public void setPrinter(Printer printer) {
this.printer = printer
}
@Resource와 같은 애너테이션으로된 의존관계 정보를 통해 DI가 되게하려면 다음 세 가지 방법 중 하나를 사용해야 한다.
@Resource // name을 생략가능하며, 생략하면 빈의 이름이 프로퍼티나 필드 이름과 같다고 간주한다.
private Printer printer;
@Resource는 위와 같이 name을 지정하지 않았을 때, 주입할 적절한 빈을 찾지 못하면 타입을 이용해 다시 한번 빈을 찾는다.
@Autowired / @Inject
두 애너테이션 모두 타입에 의한 자동와이어링 방식을 사용할 때 붙인다.
@Inject는 JavaEE6 표준 스펙인 JSK-330에서 정의된 것으로 @Autowired와 동일하다. 스프링이 아닌 다른 프레임워크에서 DI를 사용하려면 이 애너테이션을 사용한다.
@Autowired를 사용할 때 같은 타입의 빈이 하나 이상 존재할 경우, 그 빈들을 모두 DI 받게 할 수도 있다.
@Autowired
private Collection<Printer> printerList;
...
@Autowired
private Printer[] printerArray;
...
@Autowired
private Map<String, Printer> printerMap; // 빈의 이름을 키로 하는 맵으로 DI 받는다.
@Qualifier
타입 외의 정보를 추가하여 자동와이어링을 세밀하게 제어할 때 사용한다. 동일한 타입을 가진 여러 개의 빈이 존재할 때, @Autowired를 사용하면 에러가 발생한다. 이를 위해 빈 선정을 도와주기 위한 부가 정보를 부여할 때 사용하는 애너테이션이다.
@Component
@Qualifier("main")
public class DataSource {
}
@Component
@Qualifier("slave")
public class DataSource {
}
...
@Autowired
@Qualifier("main")
private DataSource dataSource;
@Qualifier를 사용시 해당 한정자에 대한 메타정보를 가지는 빈이 없다면, @Qualifier에서 지정한 이름을 가진 빈을 찾는다. 그러나 이 방식은 권장되지 않으며 @Resource로 사용해야 한다.
Setter나 생성자에서 사용할 경우 다음과 같이 각 파라미터마다 애너테이션을 지정해야 한다.
@Autowired
public void setDataSource(@Qualifier("main") DataSource dataSource) {
...
}