개발/Spring

[Spring] 스프링 의존 자동 주입 (Auto Injection)이란?

nova_dev 2020. 11. 16. 17:26
반응형

Spring 
스프링 의존 자동 주입 (Auto Injection)

앞에 Spring DI가 무엇인지 공부하며 Autowired의 사용법을 잠깐 살펴보았다. 이에 대해 더 자세히 알아보자.

 

 

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

스프링에서 자동 주입을 설정하려면 @Autowired 어노테이션이나 @Resource 어노테이션을 사용하면 된다. Resource 어노테이션은 자바에서 제공하는 어노테이션으로 스프링은 @Resource 뿐만 아니라 자바에서 제공하는 @Inject 어노테이션 또한 지원한다. 단, 스프링에서는 주로 @Autowired를 사용하므로 해당 어노테이션에 관하여 자세히 다루어 보도록 하자.

자동 주입 기능을 사용하면 스프링이 알아서 의존 객체를 찾아서 주입한다. 자동 주입 기능을 사용하는 것은 매우 간단하다. 의존을 주입할 대상에 @Autowired 어노테이션을 붙이기만 하면 된다. 

import org.springframework.beans.factory.annotation.Autowired;

public class ChangePasswordService {

	@Autowired
	private MemberDao memberDao;

	public void changePassword(String email, String oldPwd, String newPwd) {
		Member member = memberDao.selectByEmail(email);
		if (member == null)
			throw new MemberNotFoundException();

		member.changePassword(oldPwd, newPwd);

		memberDao.update(member);
	}

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

}
@Configuration
public class AppCtx {

	@Bean
	public MemberDao memberDao() {
		return new MemberDao();
	}
	
	@Bean
	public MemberRegisterService memberRegSvc() {
		return new MemberRegisterService();
	}
	
	@Bean
	public ChangePasswordService changePwdSvc() {
		return new ChangePasswordService();
	}
	...
}

의존을 주입하지 않아도 스프링이 @Autowired가 붙은 필드에 해당 타입의 빈 객체를 찾아서 주입한다. 만약 @Autowired 애노테이션을 설정한 필드에 알맞은 빈 객체가 주입되지 않았다면 ChangePasswordService의 memberDao 필드는 null일 것이다. 그러면 암호 변경 기능을 실행할 때 NullPointException이 발생하게 된다. 암호 변경 기능이 정상 동작 하는 것은 @Autowired 애노테이션을 붙인 필드에 실제 MemberDao 타입의 빈 객체가 주입되었음을 의미한다.

@Autowired 애노테이션은 메서드에도 붙일 수 있다. 예를 들면 아래와 같은 setMemberDao, setPrinter 메서드에 @Autowired 애노테이션을 붙여서 확인해보자.

public class MemberInfoPrinter {

	private MemberDao memDao;
	private MemberPrinter printer;

	public void printMemberInfo(String email) {
		Member member = memDao.selectByEmail(email);
		if (member == null) {
			System.out.println("데이터 없음\n");
			return;
		}
		printer.print(member);
		System.out.println();
	}

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

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

}

위 MemberInfoPrinter를 가져오는 부분은 아래와 같다.

	@Bean
	public MemberInfoPrinter infoPrinter() {
		MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
		return infoPrinter;
	}

@Autowired 애노테이션을 필드나 세터 메서드에 붙이면 스프링은 타입이 일치하는 빈 객체를 찾아서 주입한다. changePasswordService의 memberDao 필드 타입은 MemberDao이므로 일치하는 타입을 가진 memberDao 빈이 주입된다. 비슷하게 MemberInfoPrinter의 setMemberDao()메서드의 memberDao 파라미터 타입이 MemberDao이므로 setMemberDao() 메서드에 일치하는 타입을 가진 memberDao 빈이 주입된다.

 일치하는 빈이 없는 경우

그렇다면 @Autowired 애노테이션을 적용한 대상에 일치하는 빈이 없으면 어떻게 될까? 설정 클래스의 memberDao 메서드를 주석처리하고 실행해보자

@Configuration
public class AppCtx {

//	@Bean
//	public MemberDao memberDao() {
//		return new MemberDao();
//	}
	
	@Bean
	public MemberRegisterService memberRegSvc() {
		return new MemberRegisterService();
	}
    ...
}

이 상태에서 앱을 실행하게 되면 UnsatisfiedDependencyException : NoSuchBeanDefinitionException: No qualifying bean of type 'spring.MemberDao' available: excepted at least 1 bean 과 같은 익셉션을 볼 수 있다. 이 에러 메세지는 @Autowired 애노테이션을 붙인 MemberRegisterService의 memberDao 필드에 주입할 MemberDao 빈이 존재하지 않아 에러가 발생했다는 사실을 알려준다.

반대로 @Autowired 애너테이션을 붙인 주입 대상에 일치하는 빈이 두 개 이상이면 어떻게 될까? 이럴 경우는 NoUniqueBeanDefinitionException: No qualifying bean of type 'spring.MemberPrinter' availiable: expected single matching bean but found 2와 같은 에러를 볼 수 있다. 이런 경우, 아래와 같이 @Qualifier 애노테이션으로 직접 자동주입할 빈을 지정해준다. 

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

만약 @Qualifier 애너테이션이 없다면, 빈 이름을 한정자로 지정한다.

 @Autowired 애노테이션의 필수 여부 (선택값 처리)

@Autowired 애노테이션은 기본적으로 @Autowired 애노테이션을 붙인 타입에 해당하는 빈이 존재하지 않으면 익셉션이 발생한다. 그런데 만약 값이 존재할 경우만 자동 주입을 하고 싶은 경우는 어떻게 해야할까? 아래에서는 선택적으로 자동주입을 하는 방법 3가지를 설명한다.

1. @Autowired 애노테이션의 required 속성을 false로 지정하기

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

@Autowired 애노테이션의 required 속성을 false로 지정하면 매칭되는 빈이 없어도 익셉션이 발생하지 않으며 자동 주입을 수행하지 않는다. 위 예에서 DateTimeException 타입의 빈이 존재하지 않으면 익셉션을 발생하지 않고 setDateFormatter() 메서드를 실행하지 않는다.

2. Java8의 Optional 이용하기

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

자동 주입 대상 타입이 Optional인 경우, 일치하는 빈이 존재하지 않으면 값이 없는 Optional을 인자로 전달하고(익셉션이 발생하지 않는다.), 일치하는 빈이 존재하면 해당 빈을 값으로 갖는 Optional을 인자로 전달한다. Optional을 사용하는 코드는 값의 존재 여부에 따라 알맞게 의존 객체를 사용하면 된다.

3. @Nullable 애노테이션 사용하기

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

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

 

관련글

위 글은 스프링5 프로그래밍 입문 (저자: 최범균) 책을 공부하면서 작성한 글입니다. (링크)

[스프링 DI] 1. 스프링 DI(Dependency Injection)란? (링크)
[스프링 DI] 2. Spring DI 설정 (링크)
[스프링 DI] 3. Spring에서 두 개 이상의 설정 파일 이용하기 (링크)
[스프링 DI] 4. getBean() 메서드 사용과 Bean 관련 Exception 정리 (링크)

 

 

 

반응형