[전문가를 위한 스프링 5] 3장_Spring IoC 와 DI
1. IoC 종류
IoC (Inversion of Control ) 은 두 가지로 나뉜다 : DI, DL
DI 는 IoC 컨테이너가 컴포넌트에 의존성을 주입
시켜 준다. 반면, DL 은 컴포넌트 스스로 의존성 참조
를 가져온다.
DI (Dependency Injection)
생성자 주입 : IoC 컨테이너는 해당 컴포넌트를 초기화할 때, 컴포넌트에 필요한 의존성을 전달
1
2
3public ConstructorInjection(Dependency dp){
this.dp = dp;
}세터 주입 : setter method 를 호출해서, 의존성을 나중에 제공 가능
1
2
3public void setDependecy(Dependency dp){
this.dp = dp;
}필드 주입 : 스프링 컨테이너가 reflection 을 이용해 필요한 의존성을 주입
1
2
3
4
5
public class Singer {
private Inspiration inspirationBean;
...
DL (Dependency Lookup)
Depenency Pull : 중앙 registry 에서 의존성을 직접 가져오는 방식 ( register -> container )
1
context.getbean();
Contextualized Dependency Lookup : 특정 중앙 registry 에서 의존성을 가져오는 것이 아니라, 자원을 관리하는 컨테이너에서 의존성을 가져오는 방식
1
2
3publicv void lookup(Container ct){
this.dp = (Dependency) ct.getDependency("myDependency");
}
그렇다면, 의존성 주입 vs 의존성 룩업 ? 의존성 주입을 사용해라.
주입을 이용하면,
- 사용자 클래스는 IoC 컨테이너와 완전리 분리된다.
- 직접 테스트용 의존성을 주입하기 쉬우므로 테스트 하기 쉽니다.
그렇다면, 생성자 주입 vs 세터 주입 vs 필드 주입? 상황에 따라 선택해라.
- 생성자 주입 :
컴포넌트에 의존성 주입을 보장
해야할 때 - 세터 주입 :
새로운 객체를 생성하지 않고 의존성을 교체
할 때 - 필드 주입 : 다음 이유로, 권장하지 않는다.
- 클래스가 비대해지는 상황을 생성자 주입이나 수정자 주입에서는 쉽게 알아 챌 수 있지만, 필드 주입은 아니다.
- 클래스는 public interface 의 메서드나 생성자로 필요한 의존성 타입을 명확히 전달해야하는데, 필드 주입을 이용하면 어떤 타입의 의존성이 필요한지 명확하지 않다.
- final 필드에 사용할 수 없다.
- 의존성을 수동으로 주입해야하므로, 테스트 코드 작성이 어렵다.
2. BeanFactory, ApplicationContext
- Bean : 컨테이너가 관리하는 모든 컴포넌트
- BeanFactory interface : 컴포넌트의 라이프사이클과 의존성을 관리
- ApplicationContext interface (extends BeanFactory) : DI 외에도, 트랜잭션, AOP, 애플리케이션 이벤트 처리 기능 제공
3. Application Context 구성
Component 선언
AppliceionContext 를 부트스트랩할 때, 컴포넌트를 찾아서 빈 인스턴스를 생성한다.1
2
3
4
public class HelloWorldMessageProvider implements MessageProvider {
...
}1
2
3
4
5
6
7
8
9
10
11
12
public class StandardOutMessageRenderer implements MessageRenderer {
private MessageProvider messageProvider;
public void setMessageProvider(MessageProvider provider) {
this.messageProvider = provider;
}
...
}Java Configuration
Configuration 클래스에는, IoC 컨테이너가 빈 인스턴스를 만들 때 호출하는 @Bean 어노테이션이 적용된 메서드가 있다.1
2
3
4
5
6
7
8
9
public class HelloWorldConfiguration {
public MessageProvider provider() {
return new HelloWorldMessageProvider();
}
}
...
4. 주입 인자
스프링은 다른 컴포넌트나 단순 값 이외에 자바 컬렉션, 외부에 정의된 프로퍼티를 주입할 수 있도록 주입 인자에 많은 옵션을 지원한다.
단순 값 주입
1
2
3
4
5
6
7
8
9
10
11
12
13
public class InjectSimple {
private String name;
private int age;
private float height;
private boolean programmer;
private Long ageInSeconds;
...SpEL (Spring Expression Language)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class InjectSimpleSpel {
private String name;
private int age;
private float height;
private boolean programmer;
private Long ageInSeconds;
...1
2
3
4
5
6
7
8
public class InjectSimpleConfig {
private String name = "John Mayer";
private int age = 40;
private float height = 1.92f;
private boolean programmer = false;
private Long ageInSeconds = 1_241_401_112L;
...컬렉션 주입
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CollectionInjection {
/**
* @Resource(name="map") is equivalent with @Autowired @Qualifier("map")
*/
private Map<String, Object> map;
private Properties props;
private Set set;
private List list;
5. 빈 명명 규칙
모든 빈은 ApplicationContext 내에서 고유한 하나 이상의 이름을 가져야한다.
id 나 이름이 없는 같은 타입의 빈이 여러개 선언되면, 스프링은 ApplicationContext 를 초기화하는 과정에서 빈을 주입할 때 예외 (NoSuchBeanDefinitionException) 를 던진다.
다음 Singer 클래스의 경우, 클래스명 자체로 빈을 명명한다 : singer
1 |
|
다음 Singer 클래스의 경우, @Component 어노테이션의 인수가 빈의 이름이다 : johnMayer
1 |
|
별칭을 선언하려면 ? @Bean 의 name attribute 를 사용한다. 첫 번째 값이 id, 다른 값이 별칭. 즉, johnMayer 가 id
1 |
|
6. 빈 생성 방식
기본적으로 스프링의 모든 빈은 싱글톤
이다. 즉, 스프링은 빈의 단일 인스턴스를 유지하고 관리한다. ApplicationContext.getBean() 에 대한 모든 호출은 동일한 인스턴스를 반환한다.빈의 인스턴스를 가져올 때마다 새로운 빈 인스턴스
를 가져오려면 ? 다음과 같이, prototype
으로 변경해라.
1 |
|
7. Autowiring
스프링이 Autowiring 을 수행하는 다섯 가지 방식이 있다.
- byName : ApplicationContext 에서 각 프로퍼티와
이름이 같은
빈을 찾아서 연결 시도 - byType : ApplicationContext 에서
동일한 타입
의 빈을 대상 빈의 각 프로퍼티에 연결 시도 - Constructor : 주입이 수정자가 아닌 생성자를 이용해서 이루어진다는 점을 제외하면 byType 과 동일
- default : 빈에 기본 생성자가 있으면 byType 방식, 없으면 Constructor 방식
- no : 기본값
어노테이션을 이용해서 구성할 때, 기본 Autowiring 방식은 byType 이다.
이름을 기반으로 Autowiring 을 하기 위해서는 @Autowired 와 주입되어야 하는 빈의 이름을 인자로 전달하는 @Qualifier 을 적용해라.
다음 코드로 예를 들어보자.
1 |
|
1 |
|
1 |
|
그리고, TrickyTarget 클래스를 실행하면 다음과 같이 출력된다.
1 | Target.constructor() |
그런데 만약에, 빈 타입들이 서로 연관되면 상황이 복잡해진다. Foo 를 인터페이스로 변경하고 이 인터페이스를 구현하는 빈 타입 두개를 선언해보자.
1 | public interface Foo { |
1 |
|
1 |
|
그리고 다시 TrickyTarget 클래스를 실행하면, UnsatisfiedDependencyExpcetion 이 발생한다.
이 메세지는 스프링이 setFoo 메서드를 사용해서 주입해야하는 빈이 어떤 빈인지 알지 못한다는 것이다.
이를 해결 하는 두 가지 방법이 있다 : @Primary, @Qualifier
@Primary
타입을 기반으로 Autowiring 할 때, 스프링에게 자신의 우선순위를 높게 지정한다.
@Primary 는 정확히 두 개의 관련 빈 타입이 있을 경우에 유용하다.1
2
3
4
5
public class FooImplOne implements Foo {
}다시 TrickyTarget 클래스를 실행하면, 결과는 다음과 같다.
1
2
3
4Target.constructor()
프로퍼티 fooOne 설정
프로퍼티 fooTwo 설정
프로퍼티 bar 설정@Qualifier
모호한 setter 였던 setFooOne() 과 setFooTwo() 에 @Autowired 와 함께 적용한다.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
public class TrickyTarget {
Foo fooOne;
Foo fooTwo;
Bar bar;
...
public void setFooOne(Foo fooOne) {
this.fooOne = fooOne;
System.out.println("프로퍼티 fooOne 설정");
}
public void setFooTwo(Foo foo) {
this.fooTwo = foo;
System.out.println("프로퍼티 fooTwo 설정");
}
...다시 TrickyTarget 클래스를 실행하면, 결과는 다음과 같다.
1
2
3
4Target.constructor()
프로퍼티 fooOne 설정
프로퍼티 fooTwo 설정
프로퍼티 bar 설정
전문가를 위한 스프링 5 <율리아나 코스미스>