자동주입
1. 자동 주입이란?
스프링이 자동으로 의존하는 빈 객체를 주입해주는 기능이 있다. 이를 자동 주입이라고 한다. 스프링에서 의존 자동 주입을 설정하려면 @Autowired 애노테이션이나 @Resource 애노테이션을 사용하면 되는데 이 페이지에서는 @Autowired 애노테이션의 사용 방법을 살펴본다.
2. @Autowired
자동 주입 기능을 사용하는 것은 매우 간단하다. 의존을 주입할 대상에 @Autowired 애노테이션을 붙이기만 하면 된다.
2.1 필드
1
2
3
4
5
public class ChangePasswordService {
@Autowired
private MemberDao memberDao;
...
}
- 필드에 @Autowired 애노테이션이 붙어 있으면 스프링이 해당 타입의 빈 객체를 찾아서 필드에 할당한다.
- @Autowired 애노테이션을 설정한 필드에 알맞은 빈 객체가 주입되지 않았다면 memberDao 필드는 null일 것이다. 그러면 memberDao를 호출할 때 NullPointerException이 발생하게 된다.
2.2 메서드
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MemberInfoPrinter {
private MemberDao memberDao;
private MemberPrinter printer;
@Autowired
public void setMemberDao(MemberDao memberDao) {
this.memberDao = memberDao;
}
@Autowired
public void setPrinter(MemberPrinter printer) {
this.printer = printer;
}
}
빈 객체의 메서드에 @Autowired 애노테이션을 붙이면 스프링은 해당 메서드를 호출한다. 이때 메서드 파라미터 타입에 해당하는 빈 객체를 찾아 인자로 주입한다.
스프링이 해당 메서드를 호출하기 때문에 따로 두 setter 메서드를 호출하지 않아도 된다.
@Autowired 애노테이션을 필드나 세터 메서드에 붙이면 스프링은 타입이 일치하는 빈 객체를 찾아서 주입한다.
3. 일치하는 빈이 없는 경우
@Autowired 애노테이션을 적용한 대상에 일치하는 빈이 없으면 어떻게 될까?
그렇다면 Exception이 발생하면서 제대로 실행되지 않는다.
반대로 @Autowired 에노테이션을 붙인 주입 대상에 일치하는 빈이 두 개 이상이면 어떻게 될까?
이 또한 Exception이 발생하면서 제대로 실행되지 않는다.
자동 주입을 하려면 해당 타입을 가진 빈이 어떤 빈인지 정확하게 한정할 수 있어야 하는데 빈이 두 개 이상이면 어떤 빈을 자동 주입 대상으로 선택해야 할지 한정할 수 없다.
4. @Qualifier 애노테이션을 이용한 의존 객체 선택
자동 주입 가능한 빈이 두 개 이상이면 자동 주입할 빈을 지정할 수 있는 방법이 필요하다, 이때 @Qualifier 애노테이션을 사용한다. 이 것을 사용하면 자동 주입 대상 빈을 한정할 수 있다.
@Qualifier 애노테이션은 두 위치에서 사용 가능하다
4.1 @Bean 설정 메서드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Qualifier;
...
@Configuration
public class AppCtx{
...
@Bean
@Qualifier("printer")
public MemberPrinter memberPrinter1() {
return new MemberPrinter();
}
@Bean
public MemberPrinter memberPrinter2() {
return new MemberPrinter();
}
}
- 이 코드에서 memberPrinter1() 메서드에 “printer” 값을 갖는 @Qualifier 애노테이션을 붙였다. 이 설정은 해당 빈의 한정 값으로 “printer”를 지정한다.
- 이렇게 지정한 한정 값은 @Autowired 애노테이션에서 자동 주입할 빈을 한정할 때 사용한다.
4.2 @Autowired를 붙인 필드나 메서드
1
2
3
4
5
6
7
8
9
10
public class MemberListPrinter {
private MemberDao memberDao;
private MemberPrinter printer;
...
@Autowired
@Qualifier("printer")
public void setMemberPrinter(MemberPrinter printer) {
this.printer = printer;
}
}
@Qualifier 애노테이션 값이 “printer”이므로 한정 값이 “printer”인 빈을 의존 주입 후보로 사용한다. 앞서 스프링 설정 클래스에서 @Qualifier 애노테이션의 값으로 “printer”를 준 MemberPrinter 타입의 빈(memberPrinter1)을 자동 주입 대상으로 사용한다.
@Autowired 애노테이션을 필드와 메서드에 모두 적용할 수 있으므로 @Qualifier 애노테이션도 필드와 메서드에 적용할 수 있다.
5. 빈 이름과 기본 한정자
빈 설정에 @Qualifier 애노테이션이 없으면 빈의 이름을 한정자로 지정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class AppCtx {
...
@Bean
public MemberPrinter printer() {
return new MemberPrinter();
}
@Bean
@Qualifier("mprinter")
public MemberPrinter printer2() {
return new MemberPrinter();
}
}
여기서 printer() 메서드로 정의한 빈의 한정자는 빈 이름인 “printer”가 된다. printer2빈은 @Qualifier 애노테이션 값인 “mprinter”가 한정자가 된다.
@Autowired 애노테이션도 @Qualifier 애노테이션이 없으면 필드나 파라미터 이름을 한정자로 사용한다. 예를 들어 다음 코드는 printer 필드에 일치하는 빈이 두 개 이상 존재하면 한정자로 필드 이름인 “printer”를 사용한다.
1
2
3
4
public classs MemberInfoPrinter{
@Autowired
private MemberPrinter printer;
}
6. 상위/하위 타입 관계와 자동 주입
아래 코드를 보면, 이 클래스는 MemberPrinter 클래스를 상속한 MemberSummaryPrinter 클래스이다.
1
2
3
4
5
6
7
8
public class MemberSummaryPrinter extends MemberPrinter{
@Override
public void print(Member member){
System.out.printf(
"회원 정보: 이메일=%s, 이름=%s\n",
member.getEmail(), member.getName());
}
}
그리고 아래는 설정 클래스인 AppCtx 클래스이다. 설정에서 서브클래스 타입의 빈 객체를 설정하도록 변경한 코드이다.
1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class AppCtx{
...
@Bean
public MemberPrinter printer1(){
return new MemberPrinter();
}
public MemberSummaryPrinter printer2(){
return new MemberSummaryPrinter();
}
}
이렇게 해서 실행하면 Exception이 발생할 수 있다.
- printer2 빈을 MemberSummaryPrinter 타입으로 변경했음에도 같은 에러가 발생하는 이유는 MemberSummaryPrinter 클래스가 MemberPrinter 클래스를 상속했기 때문이다.
- MemberSummaryPrinter 클래스는 MemberPrinter 타입에도 할당할 수 있다.
- 따라서 스프링 컨테이너는 MemberPrinter 타입 빈을 자동 주입해야 하는 @Autowired 애노테이션 태그를 만나면 printer1 빈과 printer2 빈 중에서 어떤 빈을 주입해야 할지 알 수 없다.
- 따라서 Exception이 발생한다.
6.1 해결
위 코드에서 예를 들면 MemberPrinter 타입의 빈을 자동 주입하는 클래스에 어떤 빈을 주입할지 결정해야 한다. 그 때 @Qualifier 애노테이션을 사용해서 주입할 빈을 한정한다. 다음과 같이 설정 크래스와 @Autowired 애노테이션을 붙인 곳에 동일한 @Qualifier 애노테이션을 붙여서 주입할 빈을 한정한다.
이때 자동 주입할 빈은 두 가지 방법으로 처리할 수 있다.
- Qualifier 애노테이션 사용
1 2 3 4 5 6 7 8 9 10 11 12
@Configuration public class AppCtx{ ... @Bean @Qualifier("printer") public MemberPrinter printer1(){ return new MemberPrinter(); } public MemberSummaryPrinter printer2(){ return new MemberSummaryPrinter(); } }
1 2 3 4 5 6 7 8 9
public class MemberInfoPrinter{ private MemberPrinter printer; ... @Autowired @Qualifier("printer") public void setPrinter(MemberPrinter printer){ this.printer = printer; } }
- 하위 클래스 타입을 사용하도록 수정
1 2 3 4 5 6 7 8
public class MemberInfoPrinter{ private MemberPrinter printer; ... @Autowired public void setPrinter(MemberSummaryPrinter printer){ this.printer = printer; } }
7. @Autowired 애노테이션의 필수 여부
아래 코드를 예로 들겠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MemberPrinter {
prinvate DateTimeFormmater dateTimeFormatter;
public void print(Member member) {
if(dateTimeFormatter == null){
System.out.printf(
"회원 정보 : 아이디 = %d, 이메일 = %s, 이름 = %s, 등록일 = %tF\n",
member.getId(), member.getEmail(),
member.getName(), member.getRegisterDateTime());
}else{
System.out.printf(
"회원 정보 : 아이디 = %d, 이메일 = %s, 이름 = %s, 등록일 = %s\n",
member.getId(), member.getEmail(), member.getName(),
dateTimeFormatter.format(member.getRegisterDateTime()));
}
}
@Autowired
public void setDateTimeFormatter(DateTimeFormatter dateTimeFormmater){
this.dateTimeFormatter = dateTimeFormatter;
}
}
- print()메서드는 dateTimeFormatter가 null인 경우에도 알맞게 동작한다.
- 즉 반드시 setDateTimeFormatter()를 통해서 의존 객체를 주입할 필요는 없다.
- 하지만 @Autowired 애노테이션은 기본적으로 해당하는 빈이 존재하지 않으면 Exception을 발생한다.
이렇게 자동 주입할 대상이 필수가 아닌 경우 해결 방법이 3가지 존재한다.
7.1 required 속성
@Autowired 애노테이션의 required 속성을 다음과 같이 false로 지정한다.
1
2
3
4
@Autowired(required = false)
public void setDateTimeFormatter(DateTimeFormatter dateTimeFormmater){
this.dateTimeFormmater = dateTimeFormatter;
}
7.2 Optimal
스프링 5 버전부터는 @Autowired 애노테이션의 required 속성을 false로 하는 대신에 다음과 같이 의존 주입 대상에 자바 8의 Optimal을 사용해도 된다.
1
2
3
4
5
6
7
8
@Autowired
public void setDateTimeFormatter(Optimal<DateTimeFormatter> formatterOpt){
if(formatterOpt.isPresent()){
this.dateTimeFormatter = formatterOpt.get();
}else{
this.dateTumeFormatter = null;
}
}
- 자동 주입 대상 타입이 Optimal 인 경우, 일치하는 빈이 존재하지 않으면 값이 없는 Optimal을 인자로 전달한다.(Exception은 발생하지 않는다.)
- 일치하는 빈이 존재하면 해당 빈을 값으로 갖는 Optimal을 인자로 전달한다.
7.3 @Nullable
1
2
3
4
@Autowired(required = false)
public void setDateTimeFormatter(@Nullable DateTimeFormatter dateTimeFormmater){
this.dateTimeFormmater = dateTimeFormatter;
}
@Nullable 애노테이션을 의존 주입 대상 파라미터에 붙이면,스프링 컨테이너는 세터 메서드를 호출할 때
자동 주입할 빈이 존재하면 해당 빈을 인자로 전달하고,
존재하지 않으면 인자로 null을 전달한다.
@Nullable 애노테이션과 require 속성을 사용할 때 차이점은
@Nullable 애노테이션을 사용하면 자동 주입할 빈이 존재하지 않아도 메서드가 호출된다는 점이다.
require 속성이 false이면 대상 빈이 존재하지 않으면 세터 메서드를 호출하지 않는다.
앞서 설명한 세 가지 방식은 필드에도 그대로 적용된다.
1
2
3
4
5
6
7
8
9
10
11
12
// required 방식
@Autowired(required = false)
private DateTimeFormatter dateTimeFormatter;
// Optional 방식
@Autowired
private Optiomal<DateTimeFormmatter> dateTimeFormatter;
// @Nullable 방식
@Autowired
@Nullable
private DateTimeFormatter dateTimeFormatter;
7.4 생성자 초기화와 필수 여부 지정 방식 동작 이해
- required 속성
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 MemberPrinter { private DateTimeFormatter dateTimeFormatter; public MemberPrinter() { dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일"); } public void print(Member member) { if(dateTimeFormatter == null){ System.out.printf( "회원 정보 : 아이디 = %d, 이메일 = %s, 이름 = %s, 등록일 = %tF\n", member.getId(), member.getEmail(), member.getName(), member.getRegisterDateTime()); }else{ System.out.printf( "회원 정보 : 아이디 = %d, 이메일 = %s, 이름 = %s, 등록일 = %s\n", member.getId(), member.getEmail(), member.getName(), dateTimeFormatter.format(member.getRegisterDateTime())); } } @Autowired(required = false) public void setDateTimeFormatter(DateTimeFormatter dateTimeFormmater){ this.dateTimeFormatter = dateTimeFormatter; } }
이 실행 결과를 통해 @Autowired 애노테이션의 required 속성이 false이면 일치하는 빈이 존재하지 않을 때 자동 주입 대상이 되는 필드나 메서드에 null을 전달하지 않는다는 것을 알 수 있다.
- @Nullable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class MemberPrinter { private DateTimeFormatter dateTimeFormatter; public MemberPrinter() { dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일"); } public void print(Member member) { ... 동일 코드 } @Autowired public void setDateTimeFormatter(@Nullable DateTimeFormatter dateTimeFormmater){ this.dateTimeFormatter = dateTimeFormmater; } }
- @Nullable 애노테이션을 사용할 경우 스프링 컨테이너는 의존 주입 대상이 존재하지 않으면 null을 값으로 전달한다.
- 스프링 컨테이너는 빈을 초기화하기 위해 기본 생성자를 이용해서 객체를 생성하고
- 의존 자동 주입을 처리하기 위해 setDateFormatter() 매서드를 호출한다.
- 그래서 기본 생성자에서 dateTimeFormatter 필드를 초기화 해도 setDateTimeFormatter() 메서드가 null을 전달해서 다시 null로 바뀐 것이다.
일치하는 빈이 없으면 값 할당 자체를 하지 않는 @Autowired(required = false)와 달리 @Nullable 애노테이션을 사용하면 일치하는 빈이 없을 때 null 값을 할당한다. 유사하게 Optimal 타입은 매칭되는 빈이 없으면 값이 없는 Optimal을 할당한다. 기본 생성자에서 자동 주입 대상이 되는 필드를 초기화할 때는 이 점을 유의해야 한다.
8. 자동 주입과 명시적 의존 주입 간의 관계
설정 클래스에서 의존 주입했는데 자동 주입 대상이면 어떻게 될까? 아래의 설정 클래스를 보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class AppCtx {
...
@Bean
@Qualifier("printer")
public MemberPrinter printer1() {
return new MemberPrinter();
}
@Bean
@Qualifier("summaryPrinter")
public MemberSummaryPrinter printer2() {
return new MemberSummaryPrinter();
}
@Bean
public MemberInfoPrinter infoPrinter() {
MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
infoPrinter.setPrinter(printer2());
return infoPrinter;
}
}
1
2
3
4
5
6
7
8
public class MemberInfoPrinter {
...
@Autowired
@Qualifier("printer")
public void setPrinter(MemberPrinter printer) {
this.printer = printer;
}
}
이 상태에서 실행하면 설정 클래스에서 setPrinter를 통해 주입한 printer2 빈(MemberSummaryPrinter 타입 객체)이 아닌 printer1 빈(MemberPrinter 타입 객체)을 사용해서 실행한다.
즉 설정 클래스에서 세터 메서드를 통해 의존을 주입해도 해당 세터 메서드에 @Autowired 애노테이션이 붙어 있으면 자동 주입을 통해 일치하는 빈을 주입한다.
Ref.
-
- 최범균, 스프링프로그래밍입문5, 가메출판사.