케네스로그

[스프링] 의존 자동 주입 & @Autowired 애노테이션 본문

Dev/스프링

[스프링] 의존 자동 주입 & @Autowired 애노테이션

kenasdev 2022. 4. 20. 22:59
반응형

기존의 의존관계 주입

@Configuration
public class AppCtx {
	@Bean
	public MemberRepository memberRepository() {
		return new MemberRepository();
	}

	@Bean
	public ChangePasswordService changePwdSvc() {
		ChangePasswordService pwdSvc = new ChangePasswordService();
		pwdSvc.setMemberRepository(memberRepository()); // setter() 의존 주입
		return pwdSvc;
	}
}

이전 챕터에서 배웠던 의존관계 주입의 예시를 가져왔다. ChangePasswordService객체를 생성하는 changePwdSvc() 메소드 내부에는 setter()메소드를 통해서 의존관계에 있는 MemberRepository 빈을 주입받는다.

이번에 배울 자동 주입을 배우면 setter()를 이용해서 직접 주입하는게 아니라, 스프링 컨테이너가 자동으로 주입하게 한다.

 

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

1) 클래스 필드에 애노테이션을 붙이는 경우

public class ChangePasswordService {
	@Autowired
	private MemberDao memberDao;
	
	public void changePassword(...) { ... }

	public void setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
	}
}

자동주입 기능을 사용하는 방법은 의존을 주입할 대상에 @Autowired 애노테이션을 붙이면 된다. 이렇게 @Autowired 애노테이션을 붙이면 설정클래스에서 의존을 직접 주입하지 않아도 된다. 스프링이 해당 타입의 빈 객체를 찾아서 필드에 할당하게 된다.

 

@Configuration
public class AppCtx {
	@Bean
	public MemberDao memberDao() {...}

	@Bean
	public ChangePasswordService changePasswordService() {
		ChangePasswordService pwdSvc = new ChangePasswordService();
		// 스프링이 의존객체를 찾아서 자동으로 객체(pwdSvc)에 주입한다.
		return pwdSvc;
	}

}

 

 

2) 클래스 메소드에 애노테이션을 붙이는 경우

@Configuration
public class AppCtx {
	@Bean
	public MemberDao memberDao() {...}

	@Bean
	public MemberPrinter memberPrinter() {...}

	@Bean
	public MemberInfoPrinter infoPrinter() {
		MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
		/*
			infoPrinter.setMemberDao(memberDao());
			infoPrinter.setPrinter(memberPrinter());
		*/
		// 위의 주석코드는 setter()메소드를 통해서 직접 주입하는 경우다.
		return infoPrinter;
	}
}

MemberInfoPrinter클래스는 멤버 정보가 저장된 MemberDao를 참조하고, 각 멤버의 정보를 출력하는 역할을 담당하는 MemberPrinter객체를 이용해서 멤버정보를 출력한다. 그렇다면, MemberDao와 MemberPrinter는 의존객체에 해당된다.

 

설정 클래스에서 MemberInfoPrinter를 등록하기 위해서는 위 2개의 클래스 객체를 주입받아야한다: MemberDao, MemberPrinter. 이전이라면 setter()를 통해서 직접 주입하겠지만, 자동주입 기능을 이용하면 @Autowired된 stter()메소드를 통해서 스프링이 자동으로 주입된다.

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

	// 출력 메소드
	public void printMemberInfo(String email) { ... }
}

자동주입으로 설정하기 위해서 setter()메소드에 @Autowired 애노테이션을 붙인다.

 

@Autowired 애노테이션을 필드나 setter()메소드에 붙이고,
스프링은 타입이 일치하는 빈 객체를 찾아서 주입해준다.

 

자동 의존주입에 실패하는 경우

Autowired를 통해서 자동 주입으로 설정하더라도 의존주입에 실패하는 경우가 있다.

  • 등록된 빈 중에 일치하는 빈이 없는 경우
  • 등록된 빈 중 일치하는 빈이 2개 이상인 경우

그럼, 각 경우의 예제를 코드예제와 함께 알아보도록 하겠다.

 

일치하는 빈이 없는 경우

@Configuration
public class AppCtx {
    
//    @Bean
//    public MemberDao memberDao() {
//        return new MemberDao();
//    }

}

위와 같이 대부분의 클래스에서 사용되는 MemberDao를 주석처리하여 없앴다. 이렇게 되면, 해당 객체를 필요로 하는 다른 빈 객체에서 의존객체를 주입받지 못하게 된다.

🚫Error creating bean with name 'memberRegSvc': Unsatisfied dependency expressed through field 'memberDao'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'com.example.sp5ch04.spring.MemberDao' available
: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

위와 같은 에러 메세지가 발생한다. 스프링은 굉장히! 친절하게도 어떤 문제인지 자세히 알려준다. 에러 메세지를 읽어보면, memberDao 빈 객체를 찾을 수 없으니 자동주입 설정을 확인하라고 한다.

 

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

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

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

위와 같이 MemberPrinter타입 빈 객체를 다른 이름으로 2개를 등록했다. 이렇게 되면, MemberPrinter 빈 객체와 의존관계에 있는 클래스에게 스프링이 어떤것을 주입해줘야하는지 혼란이 오게 된다.

🚫No qualifying bean of type 'com.example.sp5ch04.spring.MemberPrinter' available: expected single matching bean but found 2: memberPrinterA,memberPrinterB

위와 같은 에러 메세지를 확인할 수 있다. 읽어보면, 1개의 빈을 예상했는데, 2개가 발견했다고 알려준다.

 

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

자동 주입이 가능한 빈이 2개 이상인 경우, 스프링은 어떠한 빈을 주입해줘야하는지 모른다. 이를 위해 개발자는 @Qualifier 애노테이션을 통해 어떤 빈이 사용될지 명시할 수 있다.

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

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

앞서 빈 중복 예시에서 @Qualifier 애노테이션을 통해 printer라는 워드를 통해 명시했다. 이 말은, @Autowired를 통해 자동주입받는 메소드에서 printer라는 키워드를 통해 빈을 식별함을 의미한다. HTML에서 id 또는 class를 통해서 여러 태그들을 구분하는 것과 유사한 매커니즘으로 이해했다.

 

public class MemberListPrinter {
	private MemberDao memberDao;
	private MemberPrinter memberPrinter;

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

위의 Bean 등록부의 @Qualifier와 클래스 내부의 자동주입으로 설정된 클래스의 @Qualifier가 동일함을 알 수 있다. 즉, set메소드에서 MemberPrinter를 주입 받을 때, 어떤 것으로 주입받을지 명시된 것이다.

 

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

public class MemberPrinter {
	//@Autowired
	private DateTimeFormatter dataTimeFormatter;

	public void print(Member member) {
		if(dataTimeFormatter == null) {
			System.out.printf("등록일 = %tF\n", member.getRegisterDateTime());
		} else {
			System.out.printf("등록일 = %s\n", dateTimeFormatter.format(member.getRegisterDateTime()));
		}
	}

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

위의 예시는 dataTimeFormatter가 주입되지 않아서 null이 발생하는 경우에 따라서 동작을 달리 하도록 했다.

그리고, setter()메소드는 @Autowired를 통해 DateTimeFormatter를 자동주입 받도록 설정했다. 하지만, DateTimeFormatter는 자동주입 설정을 하지 않은 상태이다. setter()메소드를 통해서 주입받지 않더라도, print()내부의 null처리를 하였기에, 논리적으로는 아무런 문제가 없어 보인다.

 

하지만, @Autowired를 붙인 타입에 해당하는 빈이 존재하지 않음으로 예외를 발생시킨다.

 

@Autowired 애노테이션의 required 속성

public class MemberPrinter {
	private DateTimeFormatter dateTimeFormatter;

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

@Autowired 애노테이션의 required 속성을 false로 지정하면, 매칭되는 빈이 없음으로 자동 주입을 실행하지 않고 예외를 발생시키지 않는다.

 

@Autowired 애노테이션이 붙은 메소드의 인자로 Optional 타입 전달

public class MemberPrinter {
	private DateTimeFormatter dateTimeFormatter;

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

자동 주입 대상의 타입이 Optional인 경우, 일치하는 빈이 존재하지 않으면 값이 없는 Optional을 인자로 전달하고, 일치하는 빈이 존재하면 해당 빈을 값으로 갖는 Optional인자를 전달한다.

 

@Autowired 메소드의 @Nullable 애노테이션

public class MemberPrinter {
	private DateTimeFormatter dateTimeFormatter;

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

스프링 컨테이너는 setter()메소드를 호출할 때, 자동 주입할 빈이 없으면 Null을 주입하고, 빈이 존재하면 해당 빈을 찾아서 주입한다.

 

💡required 옵션을 사용하는 것과의 차이점!
required는 매칭되는 빈이 없으면 해당 메소드를 실행하지 않는다. @Nullable의 경우, 빈이 존재하지 않아도 실행된다.

 

필수 여부를 필드에 바로 적용하기

required 옵션

public class MemberPrinter {
	@Autowired(required=false)
	private DateTimeFormatter dateTimeFormatter;

	public void print(Member member) {...}
}

Optional<클래스 타입>

public class MemberPrinter {
	@Autowired
	private Optional<DateTimeFormatter> formatterOpt;

	public void print(Member member) {
		DateTimeFormatter dateTimeFormatter = formatterOpt.orElse(null);
		if(dateTimeFOrmatter == null) }
			...
		}
	}
}

@Nullable 인자

public class MemberPrinter {
	@Autowired
	@Nullalbe
	private DateTimeFormatter dateTimeFormatter;
}

 

 

 

Spring 포스팅 시리즈는 "초보 웹 개발자를 위한 스프링5 프로그래밍 입문(최범균 저)"을 독학하며 정리한 글입니다. 책 내용을 기반으로 정리하며, 개인적으로 궁금한점을 첨언하거나 소스코드를 더 이해하기 쉽게 작성합니다.

잘못된 내용이 있다면 언제든 피드백 부탁드립니다🙏

반응형