8 분 소요

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 애노테이션을 붙여서 주입할 빈을 한정한다.
이때 자동 주입할 빈은 두 가지 방법으로 처리할 수 있다.

  1. 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;
     }
    }
    
  2. 하위 클래스 타입을 사용하도록 수정
    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 생성자 초기화와 필수 여부 지정 방식 동작 이해

  1. 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;
     }
    }
    

    자동주입사진1

이 실행 결과를 통해 @Autowired 애노테이션의 required 속성이 false이면 일치하는 빈이 존재하지 않을 때 자동 주입 대상이 되는 필드나 메서드에 null을 전달하지 않는다는 것을 알 수 있다.

  1. @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;
     }
    }
    

    자동주입사진2

  • @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, 가메출판사.

카테고리:

업데이트: