Spring Component Scan


본 카테고리는 스프링 프레임워크를 다룬다.

좀 더 자세한 내용은 아래의 공식 사이트를 참고하자.

참고 스프링 프레임워크 공식 사이트

의존 자동 주입

앞선 포스팅에서 스프링의 DI에 관하여 살펴보았다.

스프링의 설정 클래스는 주입할 의존 대상을 생성자나 메소드를 이용하여 주입한다고 언급했다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class ApplicationContext {

@Bean
public ObjectDao getObjectDao() {
return new ObjectDao();
}

@Bean
public ObjectService methodB() {
ObjectService objectService = new ObjectService();
objectService.setObjectDao(getObjectDao()); // Dependency Injection
objectService.update("update");
return objectService;
}
}

스프링에서는 위와 같이 명시적으로 DI를 하지 않고 자동으로 빈(Bean) 객체를 주입해주는 기능도 존재하는데 이를 의존 자동 주입 이라고 한다.

스프링 3, 4 버전에서 초기엔 호불호가 있던 기능이었으나, 스프링부트가 나오면서 대중화되었다.

스프링에서 의존 자동 주입을 사용하려면 @Autowired 혹은 @Resource 어노테이션을 이용할 수 있다.

@Autowired를 이용한 의존 자동 주입

스프링에서 DI를 자동으로 주입하는 것은 매우 간단하다.

주입하고자 하는 대상에 @Autowired 어노테이션을 붙이면된다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ObjectService {

@Autowired
private ObjectDao mObjectDao;

public void setObjectDao(ObjectDao objectDao) {
this.mObjectDao = objectDao;
}

public update(String s) {
mObjectDao.insert(s);
}
}

@Autowired 어노테이션을 통해 특별한 설정없이도 ObjectServiceObjectDao를 주입시킬 수 있다.

다시 설정 클래스로 가자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class ApplicationContext {

// @Bean
// public ObjectDao getObjectDao() {
// return new ObjectDao();
// }

@Bean
public ObjectService methodB() {
ObjectService objectService = new ObjectService();
// objectService.setObjectDao(getObjectDao()); // Dependency Injection
objectService.update("update");
return objectService;
}
}

먼저 setObjectDao 메소드를 이용해 주입하던 코드를 주석처리한다.

위와 같이 작성하면 명시적인 주입 없이도 의존 자동 주입을 통해 처리되게 된다.

@Autowired는 변수 뿐만 아니라 메소드에도 적용할 수 있다.

새로운 클래스 Number를 정의해보자.

1
2
3
4
5
6
7
8
public class Number {

private ObjectDao mValue;

public void setValue(int value) {
this.mValue = value;
}
}

Number 클래스를 사용하는 설정 클래스를 선언해보자.

1
2
3
4
5
6
7
8
9
@Configuration
public class ApplicationContext {

public Number initNumber() {
Number number = new Number();
number.setValue(new ObjectDao());
return number;
}
}

위와 같은 상태에서 @Autowired를 메소드에 적용하면 setValue 메소드를 생략할 수 있다.

적용한 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Number.java
public class Number {

private ObjectDao mValue;

@Autowired
public void setValue(int value) {
this.mValue = value;
}
}


// ApplicationContext.java
@Configuration
public class ApplicationContext {

@Bean
public Number initNumber() {
Number number = new Number();
// number.setValue(new ObjectDao());
return number;
}
}

일치하는 빈이 없는 경우

만약 @Autowired 어노테이션을 지정한 대상에 일치하는 빈이 없는 경우엔 Exception을 호출하면서 실행되지 않는다.

Unsatisfied Dependency express through field ‘OBJECT_NAME’ 과 같은 메시지를 출력하며

이 메시지를 통해 적용가능한 즉, 일치하는 타입의 빈이 없다는 것을 알 수 있다.

일치하는 빈이 두 개 이상인 경우

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
26
27
28
// Number.java
public class Number {

private ObjectDao mValue;

@Autowired
public void setValue(int value) {
this.mValue = value;
}
}


// ApplicationContext.java
@Configuration
public class ApplicationContext {

@Bean
public Number initNumberA() {
Number number = new Number();
return number;
}

@Bean
public Number initNumberB() {
Number number = new Number();
return number;
}
}

위와 같이 initNumberA 메소드와 initNumberB 메소드로 인해 일치하는 빈이 2개가 된다.

이 경우에는

expected single matching bean but found 2: initNumberA, initNumberB 와 같은 메시지를 출력하면서

실행이 되지 않게된다.

정리하자면 의존 자동 주입을 활용하기 위해선 컴파일러가 해당 타입을 가진 빈이 어떠한 빈인지 명확하게 한정할 수 있도록 개발해야 한다.

@Qualifier 어노테이션을 이용한 의존 객체 선택

상술한 일치하는 빈이 두 개 이상인 경우 어떻게 빈을 명확하게 한정 시킬 수 있을까?

이 경우엔 @Qualifier 어노테이션을 사용하여 한정시킬 수 있다.

@Qualifier 어노테이션은 두 개의 위치에서 사용할 수 있다.

메소드에 @Qaulifier 사용

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
26
27
28
// Number.java
public class Number {

private ObjectDao mValue;

@Autowired
public void setValue(int value) {
this.mValue = value;
}
}


// ApplicationContext.java
@Configuration
public class ApplicationContext {

@Bean
public Number initNumberA() {
Number number = new Number();
return number;
}

@Bean
public Number initNumberB() {
Number number = new Number();
return number;
}
}

위의 코드는 오류가 발생하는 코드이다.

initNumberA 메소드의 Number객체로 의존 주입을 한정시키려면 @Qaulifier 어노테이션을 추가한다.

적용한 모습은 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ApplicationContext.java
@Configuration
public class ApplicationContext {

@Bean
@Qualifier("number")
public Number initNumberA() {
Number number = new Number();
return number;
}

@Bean
public Number initNumberB() {
Number number = new Number();
return number;
}
}

이제 initNumberA 빈은 number 라는 한정값을 지정받게 되었다.

이후 스프링에서 사용할 때도 동일한 한정값으로 해당 객체를 불러올 수 있다.

1
2
3
4
5
6
7
8
9
public Test {

@Autowired
@Qualifier("number")
public void setNumber(Number number) {
this.mNumber = number;
}

}

빈 이름과 기본 한정자

빈의 설정에서 @Qualifier 어노테이션이 없는 경우 빈의 이름 자체를 한정자로 지정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ApplicationContext.java
@Configuration
public class ApplicationContext {

@Bean
@Qualifier("number")
public Number initNumberA() {
Number number = new Number();
return number;
}

@Bean
public Number initNumberB() {
Number number = new Number();
return number;
}
}

initNumberAnumber 라는 한정 값이 있으나, initNumberB@Autowired@Qualifier가 둘 다 없기때문에 파라미터 이름을 사용하게 된다.

여기는 setValue 메소드의 파라미터명인 value 로 지정된다.

상위/하위 타입 관계와 자동 주입

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
26
27
// Number.java
public class Number {

private ObjectDao mValue;

@Autowired
public void setValue(int value) {
this.mValue = value;
}
}

// ApplicationContext.java
@Configuration
public class ApplicationContext {

@Bean
public Number initNumberA() {
Number number = new Number();
return number;
}

@Bean
public Number initNumberB() {
Number number = new Number();
return number;
}
}

위의 코드는 빈 타입이 2개 이상 사용되기 때문에 오류가 발생한다.

아래와 같이 교체해보자.

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
26
27
28
29
30
31
32
33
34
35
36
// Number.java
public class Number {

protected ObjectDao mValue;

@Autowired
public void setValue(int value) {
this.mValue = value;
}
}

public class IntegerNumber extends Number {
private boolean mPositive;

@Autowired
public void setSign(boolean pos) {
this.mPositive = pos;
}
}

// ApplicationContext.java
@Configuration
public class ApplicationContext {

@Bean
public Number initNumberA() {
Number number = new Number();
return number;
}

@Bean
public IntegerNumber initNumberB() {
IntegerNumber number = new IntegerNumber();
return number;
}
}

initNumberB에 주입될 값을 IntegerNumber로 교체하였지만, 오류는 동일하게 발생한다.

이는 IntegerNumberNumber를 상속받기 때문이다.

이 경우에도 @Qualifier 어노테이션을 사용하여 한정처리할 수 있다.

자동 주입과 명시적 의존 주입간의 관계

설정 클래스에서 DI를 명시적으로 진행했을 때, 만약 이 의존 객체가 자동 주입 대상이면 어떻게 될까?

@Autowired 어노테이션이 부여되여 의존 자동 주입이 작성된 경우, setter를 통해 객체를 주입하더라도 자동 주입의 우선순위가 더 높다.

스프링에서는 자동 의존 주입을 사용하는 것이 더욱 권장된다.