의존 자동 주입

정의정·2023년 6월 29일
0

Spring

목록 보기
10/12
post-thumbnail
post-custom-banner

💡 @Autowired를 이용한 의존 자동 주입

의존 대상을 생성자나 메서드를 이용해서 주입하는 방식 외에 자동으로 주입해주는 방법은 없을까?
@Autowired는 이를 위한 애노테이션이다.

📌 순서

  1. @Autowired로 의존 자동 주입하기
  2. @Qualifier로 의존 객체 선택하기
  3. 상속 클래스에서의 한정자 지정

🌟 @Autowired로 의존 자동 주입하기

의존 대상을 설정 코드에서 직접 주입하는 코드와 @Autowired 애노테이션을 이용해 의존 대상을 자동으로 주입하는 코드를 비교해보자. @Autowired 애노테이션은 필드 값에도 붙일 수 있지만, 세터 메서드에도 붙일 수 있다. 두 방법을 모두 살펴보겠다.

1. 필드 값

세터 메서드를 통한 의존 주입✏️

// 설정 클래스
	@Bean
	public ChangePasswordService changePwdSvc() {
		ChangePasswordService pwdSvc = new ChangePasswordService();
		pwdSvc.setMemberDao(memberDao());
		return pwdSvc;
	}
// 서비스 클래스
public class ChangePasswordService {

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

@Autowired를 이용한 의존 자동 주입✏️

// 설정 클래스
	@Bean
	public ChangePasswordService changePwdSvc() {
		ChangePasswordService pwdSvc = new ChangePasswordService();
		return pwdSvc;
	}
// 서비스 클래스
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;
	}
}

2. 세터 메서드

세터 메서드를 통한 의존 주입✏️

// 설정 클래스
	@Bean
	public MemberInfoPrinter infoPrinter() {
		MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
		infoPrinter.setMemberDao(memberDao());
		infoPrinter.setPrinter(memberPrinter());
		return infoPrinter;
	}
// 서비스 클래스
public class MemberInfoPrinter {

	private MemberDao memberDao;
	private MemberPrinter printer;

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

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

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

}

@Autowired를 이용한 의존 자동 주입✏️

// 설정 클래스
	@Bean
	public MemberInfoPrinter infoPrinter() {
		MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
		return infoPrinter;
	}
// 서비스 클래스
public class MemberInfoPrinter {

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

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

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

기본 생성자가 없는 클래스는 설정 클래스에서 기본 생성자를 통해 객체를 생성할 수 없기 때문에, 이 경우엔 기본 생성자를 생성해주면 된다.

지난 글에서 작성했던 MemberRegisterService와 MemberListPrinter 클래스가 이 경우에 해당한다.
아래처럼 코드를 수정하자.

MemberRegisterService.java 수정✏️

// 서비스 클래스
public class MemberRegisterService {

	@Autowired
	private MemberDao memberDao;

	public MemberRegisterService() {
	}

	public MemberRegisterService(MemberDao memberDao) {
		this.memberDao = memberDao;
	}

	public Long regist(RegisterRequest req) {
		Member member = memberDao.selectByEmail(req.getEmail());
		if (member != null) {
			throw new DuplicateMemberException("dup email " + req.getEmail());
		}
		Member newMember = new Member(req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now());
		memberDao.insert(newMember);
		return newMember.getId();
	}
}

MemberListPrinter.java 수정✏️

public class MemberListPrinter {

	private MemberDao memberDao;
	private MemberPrinter printer;

	public MemberListPrinter() {
	}

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

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

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

	public void printAll() {
		Collection<Member> members = memberDao.selectAll();
		members.forEach(m -> printer.print(m));
	}
}

@Autowired 애노테이션을 이용한 의존 자동 주입이 적용된 설정 클래스는 아래와 같이 조금 더 간결해진다.

AppCtx.java 수정✏️

@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();
	}

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

	@Bean
	public MemberListPrinter listPrinter() {
		return new MemberListPrinter();
	}

	@Bean
	public MemberInfoPrinter infoPrinter() {
		return new MemberInfoPrinter();
	}
	
	@Bean
	public VersionPrinter versionPrinter() {
		VersionPrinter versionPrinter = new VersionPrinter();
		versionPrinter.setMajorVersion(0);
		versionPrinter.setMinorVersion(1);
		return versionPrinter;
	}
}

🌟 @Qualifier로 의존 객체 선택하기

그런데 자동 주입이 가능한 빈이 두 개 이상이라면 어떻게 주입 빈을 지정할 수 있을까?
이 때 이용하는 것이 @Qualifier 애노테이션이다.

1. @Qualifier로 한정자 지정하기

빈 설정 메서드와 @Autowired 애노테이션을 붙인 빈에 @Qualifier("한정 값")을 붙이면 된다.

쉽게 이해하기 위해 예시를 살펴보자.

AppCtx.java 에 @Qualifier 적용✏️

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

MemberListPrinter.java 와 MemberInfoPrinter.java 에 @Qualifier 적용✏️

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

이렇게 @Qualifier 애노테이션을 이용하면 의존 자동 주입 대상을 한정할 수 있다.

2. 빈 이름 기본 한정자

빈 설정에 @Qualifier 애노테이션이 없다면 빈의 이름을 한정자로 지정한다.
예를 들어 아래와 같이 빈 메서드가 있다고 하자.

기본 한정자 예시

	@Bean
	public MemberPrinter Printer() {
		return new MemberPrinter();
	}
	
	@Bean
    @Qualifier("mprinter")
	public MemberPrinter Printer2() {
		return new MemberPrinter();
	}
   
    @Bean
	public MemberPrinter2 Printer2() {
		return new MemberPrinter2();
	}

여기서 쓰이는 MemberPrinter2 클래스는 아래와 같다.

public class MemberInfoPrinter2(){
	
    @Autowired
    private MemberPrinter printer;
   
}

3. 빈 이름과 @Qualifier에 따른 한정자

빈 이름@Qualifier한정자
printerprinter
printer2mprintermprinter
infoPrinterinfoPrinter

🌟 상속 클래스에서의 한정자 지정

하위 클래스에 빈을 자동 주입하는 경우한정자를 어떻게 지정할 수 있는지 알아보자. 우선 MemberPrinter 클래스를 상속하는 MemberSummaryPrinter 클래스를 작성하고 예로 들어보겠다.

1. MemberSummaryPrinter 작성

MemberSummaryPrinter.java✏️

public class MemberSummaryPrinter extends MemberPrinter {

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

}

AppCtx.java 설정 추가✏️

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

MemberInfoPrinter 클래스와 MemberListPrinter 클래스에 어떤 빈을 자동으로 주입해야 할지 결정하지 않으면 익셉션을 발생시킬 것이다. 주입할 빈을 한정하는 방법 두 가지를 살펴보자.

2. @Qualifier 적용

MemberInfoPrinter에는 MemberPrinter를 주입하고, MemberListPrinter에는 MemberSummaryPrinter를 주입하도록, @Qualifier 애노테이션을 통해 한정자를 지정하는 방법이다.

AppCtx.java에 @Qualifier 적용✏️

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

MemberInfoPrinter.java에 @Qualifier 적용✏️

public class MemberInfoPrinter {

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

MemberListPrinter.java에 @Qualifier 적용✏️

public class MemberListPrinter {

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

3. 파라미터 타입 변경

AppCtx.java @Qualifier 적용✏️

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

MemberInfoPrinter.java✔️

public class MemberInfoPrinter {

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

MemberListPrinter.java 수정✏️

public class MemberListPrinter {

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

이렇게 되면 자동 주입할 대상이 두 개 이상이어서 발생하는 문제를 피할 수 있다.


🎀 줄이며...

이번 포스트는 의존 자동 주입과 한정자 지정에 대한 내용을 다뤘다.

현업에서는 @Autowired 애노테이션을 사용하기보단 @Service 애노테이션과 final을 이용하는 형태를 많이 쓴다고 한다.

기회가 된다면 이에 대해서도 포스팅할 생각이다.

📖 교재 및 출처

  • 초보 웹 개발자를 위한 스프링5 프로그래밍 입문 | 최범균님 저
profile
배움 기록
post-custom-banner

0개의 댓글