본문 바로가기

스프링 부트

📋 4. 의존 자동 주입

2021-05-29글

@Autowired 어노테이션을 통한 의존 자동 주입

  • 자동 주입 기능을 사용하면 스프링이 알아서 의존 객체를 찾아 주입한다.
  • 사용 방법은 의존을 주입할 대상에 @Autowiwred 어노테이션을 붙이면 된다.
  • 해당 어노테이션이 붙어있으면 스프링이 이를 찾아 필드에 할당한다.

*@Autowired를 적용한 대상에 일치하는 빈이 없으면? *

해당 필드에 대한 의존을 충족하지 않는다는 내용과 함께 빈이 존재하지 않는다는 에러 메시지가 출력된다.

*만약 두개 이상이면? *

해당 타입의 빈이 한개가 아닌 여러개를 발견했다는 예외 메시지가 출력된다.


@Qualifier를 이용한 의존 객체 선택

  • 자동 주입 가능한 빈이 두 개 이상인 경우 자동 주입을 할 빈을 지정하는 방법

사용 위치

@Bean 을 붙인 빈 설정 메서드

@Configuration
public class ApplicationContextTestResourceQualifier {

    @Bean
      @Qualifier("defaultFile")
    public File defaultFile() {
        File defaultFile = new File("defaultFile.txt");
        return defaultFile;
    }
}

@Autowired에서 자동 주입할 빈을 한정할 때 사용

public class MemberListPrinter {

    private MemberDao memberDao;
    private MemberPrinter printer;

    public MemberListPrinter(MemberDao memberDao, MemberPrinter printer) {
        this.memberDao = memberDao;
        this.printer = printer;
    }

  @Autowired
  @Qualifier("printer")
    public void setMemberPrint(MemberPrinter printer) {
        this.printer = printer;
    }
}
  • @Qualifier의 속성으로 주입할 빈의 후보를 한정한다.
  • 빈 설정에 해당 어노테이션이 없으면 빈의 이름을 한정자로 지정한다.

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

MemberPrinter 클래스를 상속한 MeberSummaryPrinter 클래스가 있다고 하자.

public class MemberSummaryPrinter extends MemberPrinter {

    @Override
    public void print(Member member) {
        System.out.printf(
                "회원 정보: 이메일=%s, 이름=%s\n", 
                member.getEmail(), member.getName());
    }

}

그리고 AppCtx 설정에서 memberPrinter2() 가 의 빈 객체를 설정하도록 변경한다.

@Configuration
public class AppCtx {
    // ...

    @Bean
    public MemberPrinter memberPrinter1() {
        return new MemberPrinter();
    }

    @Bean
    public MemberSummaryPrinter memberPrinter2() {
        return new MemberSummaryPrinter();
    }
}
  • memberPrinter2() 의 빈을 MemberSummaryPrinter으로 변경해도 빈 중복 에러가 발생한다.
  • MemberSummaryPrinter는 MemberPrinter 타입에도 할당할 수 있기 때문이다.
  • 스프링 컨테이너는 MemberPrinter 타입 빈을 자동 주입해야하는 @Autowired를 만나면 `memberPrinter1(), memberPrinter2() 중 어떤 빈을 주입해야하는 지 알 수 없다.

이 문제는 두가지 방법으로 처리할 수 있다.

@Qualifier

    @Bean
    @Qualifier("printer")
    public MemberPrinter memberPrinter1() {
        return new MemberPrinter();
    }

    @Bean
    @Qualifier("summaryPrinter")
    public MemberSummaryPrinter memberPrinter2() {
        return new MemberSummaryPrinter();
    }

MemberListPrinter가 MemberSummaryPrinter를 사용하도록 (Composition)

public class MemberListPrinter {

    private MemberDao memberDao;
    private MemberPrinter printer;

  // ...

    @Autowired
    public void setMemberPrinter(MemberSummaryPrinter printer) {
        this.printer = printer;
    }
}

@Autowired의 필수 여부 지정 방법

public class MemberPrinter {
    private DateTimeFormatter 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 setDateFormatter(DateTimeFormatter dateTimeFormatter) {
        this.dateTimeFormatter = dateTimeFormatter;
    }

 // ...

dateTimeFormatter가 null인지 여부에 따라 날짜 형식을 바꿔 출력한다.
즉 반드시 setDateFormatter()를 통해 의존 객체를 주입할 필요가 없다.
하지만 @Autowired는 해당하는 빈이 존재하지 않으면 예외를 발생시킨다.

@Autowired(required = false)

이 경우 @Autowired(required = false) 설정으로 자동 주입 대상이 필수가 아님을 명시한다.
이러면 매칭되는 빈이 없어도 예외가 발생하지 않고 자동 주입을 수행하지 않는다.

@Autowired(required = false)
public void setDateFormatter(DateTimeFormatter dateTimeFormatter) {
  this.dateTimeFormatter = dateTimeFormatter;
}

Optional

스프링 5부터는 required 속성 말고, Optional을 사용할 수도 있다.

@Autowired
public void setDateFormatter(Optional<DateTimeFormatter> formatterOpt) {
    if (formatterOpt.isPresent()) {
        this.dateTimeFormatter = formatterOpt.get();
    } else {
       this.dateTimeFormatter = null;
    }
}

@Nullable

해당 어노테이션을 의존 주입 대상 파라미터에 붙이면 세터 메서드를 호출할 때 자동 주입할 빈이 존재할 경우 인자로 받고, 존재하지 않으면 null을 전달한다.
이 어노테이션은 스프링이 제공하는 어노테이션이다.

@Autowired
public void setDateFormatter(@Nullable DateTimeFormatter dateTimeFormatter) {
    this.dateTimeFormatter = dateTimeFormatter;
}

required 속성을 false로 할 때와 차이점?

@Nullable 어노테이션을 사용하면 자동 주입할 빈이 존재하지 않아도 세터 메서드가 호출된다.
@Autowired(required = false)의 경우 대상 빈이 존재하지 않을 경우 세터 메서드를 호출하지 않는다.

위의 세가지 방식은 메서드 뿐만 아니라 필드에도 동일하게 적용할 수 있다.

생성자 초기화와 필수 여부 지정 방식 동작

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 setDateFormatter(DateTimeFormatter dateTimeFormatter) {
        this.dateTimeFormatter = dateTimeFormatter;
    }
}

@Autowired(required = false)일 경우

  • dateTimeFormatter에 null을 할당하지 않는다. setDateFormatter() 가 호출되지 않기 때문이다.

@Nullable

  • 일치하는 빈이 없을 때 기본 생성자에서 초기화해주고 있어도, dateTimeFormatter에 null 값을 할당한다.

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