작심삼일러의 스프링 시작하기(3)

서은경·2022년 7월 28일
0

Spring

목록 보기
6/43

의존 자동 주입

의존 대상을 설정 코드에서 직접 주입하지 않고 스프링이 자동으로 의존하는 빈 객체를 주입해주는 기능을 자동 주입이라고 한다.
아래는 설정 코드에서 직접 주입한 코드이다.

	@Bean
    public MemberDao memberDao() {
        return new MemberDao();
    }
    
 	@Bean
    public ChangePasswordService changePwdSvc() {
        ChangePasswordService pwdSvc = new ChangePasswordService();
        
        // memberDao() 의존 주입
        pwdSvc.setMemberDao(memberDao());
        
        return pwdSvc;
    }

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

자동 주입 기능을 사용하면 스프링이 알.아.서 의존 객체를 찾아 주입한다.

	@Bean
    public MemberDao memberDao() {
        return new MemberDao();
    }
    
 	@Bean
    public ChangePasswordService changePwdSvc() {
        ChangePasswordService pwdSvc = new ChangePasswordService();
        return pwdSvc;
    }

의존을 주입할 대상에 @Autowired 어노테이션을 붙이기만 하면 이렇게 @Bean 메서드에 의존을 주입하지 않아도 의존 객체가 주입된다.

🙋‍♀️일치하는 빈이 없는 경우 ?! 같은 타입의 빈이 두개인 경우 ?!
💡 스프링은 타입이 일치하는 빈 객체를 찾아 주입하기 때문에 자동 주입을 하려면 해당 타입을 가진 빈이 어떤 빈인지 정확하게 한정할 수 있어야 한다 !!!
따라서 일치하는 빈이 없는 경우 또는 일치하는 빈이 두개 이상인 경우 에러가 난다.

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

자동 주입을 해야할 같은 타입의 빈이 두개 이상이 있을 수도 있다. 이럴 경우엔 @Qulifier 어노테이션을 이용해 자동 주입 대상 빈을 한정한다.

아래 설정파일에 동일한 타입의 빈 두개가 있다고 가정했을 때

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

@Qulifier 어노테이션을 통해 memberPrinter() 메서드의 한정자를 printer로 지정해주었다. memberPrinter2()메서드로 정의한 빈의 한정자는 빈 이름인 memberPrinter2가 된다.


    @Autowired
    @Qualifier("printer")
    public void setPrinter(MemberPrinter printer) {
        this.printer = printer;
    }

의존 객체를 주입받을 곳에 @Qulifier 어노테이션을 붙이게 되면 설정파일에서 한정자 printer로 지정해준 memberPrinter()를 자동 주입 대상으로 사용하게 된다.

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

MemberPrinter 클래스를 상속받은 MemberSummaryPrinter 클래스가 있다고 가정했을 때, 설정파일에 @Qulifier 어노테이션 없이 빈을 등록하면 동일 타입 빈 두 개를 설정했을 때와 동일한 익셉션이 발생한다.

    @Bean
    public MemberPrinter memberPrinter() {
        return new MemberPrinter();
    }
    
    @Bean
    public MemberSummaryPrinter memberPrinter2() {
        return new MemberSummaryPrinter();
    }
    
    ❌ 익셉션발생
    

memberPrinter2 빈을 MemberSummaryPrinter 타입으로 변경했음에도 에러가 발생하는 이유는 MemberSummaryPrinter 클래스가 MemberPrinter 클래스를 상속했기 때문이다.

MemberSummaryPrinter 클래스는 MemberPrinter 타입에도 할당할 수 있으므로, 스프링 컨테이너는 MemberPrinter 타입 빈을 자동주입해야 하는 @Autowired 어노테이션 태그를 만나면 memberPrinter 빈과 memberPrinter2 타입 빈 중에서 어떤 빈을 주입해야할지 알 수 없다.

    @Bean
    @Qulifier("printer")
    public MemberPrinter memberPrinter() {
        return new MemberPrinter();
    }
    
    @Bean
    public MemberSummaryPrinter memberPrinter2() {
        return new MemberSummaryPrinter();
    }

따라서 @Qulifier 어노테이션을 통해 한정자를 지정해주거나,


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

아예 MemberSummaryPrinter 빈을 자동 주입받도록 코드를 수정하여 문제를 해결할 수 있다.

@Autowired 어노테이션의 필수 여부

@Autowired 어노테이션은 기본적으로 @Autowired 어노테이션을 붙인 타입에 해당하는 빈이 존재하지 않으면 기본적으로 익셉션을 발생한다.

아래 코드는 dateTimeFormatter가 null인 경우에도 정상적으로 동작하므로 setDateFormatter()에 주입할 빈이 존재하지 않아도 동작에 문제가 없다. 하지만 이대로 실행하게 되면 스프링은 에러가 날 것이다.

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

자동 주입할 대상이 필수가 아닌 경우를 해결하기 위해 총 세가지 방법이 있는데 !
1. required 속성을 이용한다.

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

required 속성을 false로 지정하면 배칭되는 빈이 없어도 익셉션이 발생하지 않으며, 자동 주입을 수행하지 않는다.

  1. 의존 주입 대상에 Optional을 사용한다.(자바 8 이상만)
	@Autowired
    public void setDateFormatter(Optional<DateTimeFormatter> formatterOpt) {
        if (formatterOpt.isPresent()) {
            this.dateTimeFormatter = formatterOpt.get();
        } else {
            this.dateTimeFormatter = null
        }
    }
    

자동 주입 대상 타입이 Optional인 경우, 일치하는 빈이 존재하지 않으면 값이 없는 Optional을 인자로 전달하고(익셉션이 발생하지 않음), 일치하는 빈이 존재하는 해당 빈을 값으로 갖는 Optional을 인자로 전달한다.

  1. @Nullable 어노테이션을 사용한다.
	@Autowired
    public void setDateFormatter(@Nullable DateTimeFormatter dateTimeFormatter) {
        this.dateTimeFormatter = dateTimeFormatter;
    }

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

🙋‍♀️required=false 와 @Nullable 어노테이션의 차이?
💡@Nullable 어노테이션을 사용하면 자동 주입할 빈이 존재하지 않아도 메서드가 호출된다. @Autowired 어노테이션의 경우 required 속성이 false인데 대상 빈이 존재하지 않으면 세터 메서드를 호출하지 않는다.

🙋‍♀️자동 주입 대상인데 설정 클래스에서도 의존이 주입되면 어떻게 될까?
💡설정 클래스에서 세터 메서드를 통해 의존을 주입해도 해당 메서드에 @Autowired 어노테이션이 붙어 있으면 자동 주입을 통해 일치하는 빈을 주입한다 !

2개의 댓글

comment-user-thumbnail
2022년 7월 28일

작심1일

1개의 답글