의존 대상을 생성자나 메서드를 이용해서 주입하는 방식 외에 자동으로 주입해주는 방법은 없을까?
@Autowired는 이를 위한 애노테이션이다.📌 순서
- @Autowired로 의존 자동 주입하기
- @Qualifier로 의존 객체 선택하기
- 상속 클래스에서의 한정자 지정
의존 대상을 설정 코드에서 직접 주입하는 코드와 @Autowired 애노테이션을 이용해 의존 대상을 자동으로 주입하는 코드를 비교해보자. @Autowired 애노테이션은 필드 값에도 붙일 수 있지만, 세터 메서드에도 붙일 수 있다. 두 방법을 모두 살펴보겠다.
세터 메서드를 통한 의존 주입✏️
// 설정 클래스 @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; } }
세터 메서드를 통한 의존 주입✏️
// 설정 클래스 @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 애노테이션이다.
빈 설정 메서드와 @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 애노테이션을 이용하면 의존 자동 주입 대상을 한정할 수 있다.
빈 설정에 @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; }
빈 이름 @Qualifier 한정자 printer printer printer2 mprinter mprinter infoPrinter infoPrinter
하위 클래스에 빈을 자동 주입하는 경우한정자를 어떻게 지정할 수 있는지 알아보자. 우선 MemberPrinter 클래스를 상속하는 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 클래스에 어떤 빈을 자동으로 주입해야 할지 결정하지 않으면 익셉션을 발생시킬 것이다. 주입할 빈을 한정하는 방법 두 가지를 살펴보자.
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; } }
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 프로그래밍 입문 | 최범균님 저