이전 포스팅에서는 스프링에서 직접 의존 주입하는 방법에 대해서 다뤘습니다.
이 포스팅에서는 @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);
}
}
의존을 주입할 대상에 @Autowired 어노테이션을 붙이면 의존 자동 주입 완성입니다.
이전처럼 생성자, 세터 방식을 사용하지 않고 어노테이션 하나만 붙여주면 자동으로 스프링이 해당 타입의 빈 객체를 찾아서 의존성이 주입됩니다.
그렇기 때문에 다른 클래스에서 ChangePasswordService 객체를 생성할 때 생성자나 세터 방식으로 의존성을 주입하지 않아도 자동으로 MemberDao 타입의 빈 객체가 주입됩니다.
@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;
}
}
예제코드와 같이 메소드에 @Autowired 어노테이션을 붙였습니다.
빈 객체의 메소드에 @Autowired 어노테이션을 붙이면 스프링은 해당 메소드를 호출해 메소드 파라미터 타입에 해당하는 빈 객체를 찾아 인자로 주입합니다.
@Bean
public MemberPrinter memberPrinter() {
return new MemberPrinter();
}
예를 들어 위와 같은 코드를 작성하면 따로 의존주입 하지 않아도 setPrinter 메소드에 @Autowired가 붙어있기 때문에 자동 주입이 적용됩니다.
만약 자동 주입 가능한 빈이 두 개 이상이면 어떻게 해야 할까요?
@Qualifier 어노테이션을 사용하면 자동 주입 대상 빈을 한정할 수 있습니다.
public class MemberInfoPrinter {
private MemberPrinter printer;
@Autowired
@Qualifier("printer")
public void setPrinter(MemberPrinter printer) {
this.printer = printer;
}
}
@Configuration
public class AppCtx {
@Bean
@Qualifier("printer")
public MemberPrinter memberPrinter1() {
return new MemberPrinter();
}
}
AppCtx 클래스에서 memberPrinter1 메소드에 @Qualifier 어노테이션을 붙여 한정 값을 지정해 줬습니다.
MemberInfoPrinter 클래스에서 @Autowired와 함께 똑같이 @Qualifier("printer")를 붙여주면 "printer"라는 이름을 찾아서 자동 주입해 줍니다.
@Qualifier는 빈의 이름을 한정자로 지정합니다.
위의 코드로 보자면 원래 memberPrinter1가 한정자이지만 @Qualifier("printer")라고 적었기 때문에 빈의 한정자는 printer가 됩니다.
그럼 만약 @Qualifier가 없으면 생기는 문제는 무엇일까요?
@Configuration
public class AppCtx {
@Bean
public MemberPrinter memberPrinter1() {
return new MemberPrinter();
}
@Bean
public MemberSummaryPrinter memberPrinter2() {
return new MemberSummaryPrinter();
}
}
MemerPrinter를 상속받은 MemberSummaryPrinter가 있다고 가정해 보겠습니다.
이 코드를 실행하면 익셉션이 발생합니다. 왜일까요?
@Autowired를 통해 자동주입하면 MemberPrinter 타입의 빈을 memberPrinter1에 주입할지 memberPrinter2에 주입할지 모르기 때문입니다.
public class MemberInfoPrinter {
private MemberPrinter printer;
//1
@Autowired
@Qualifier("printer2")
public void setPrinter(MemberPrinter printer) {
this.printer = printer;
}
//2
@Autowired
public void setPrinter(MemberSummaryPrinter printer) {
this.printer = printer;
}
}
@Configuration
public class AppCtx {
@Bean
@Qualifier("printer1")
public MemberPrinter memberPrinter1() {
return new MemberPrinter();
}
@Bean
@Qualifier("printer2")
public MemberSummaryPrinter memberPrinter2() {
return new MemberSummaryPrinter();
}
}
그렇다면 어떻게 해야 할까요? 두 가지 방법이 있습니다.
첫 번째는 @Autowired를 붙인 곳에 @Qualifier로 주입할 빈을 한정하면 됩니다.
두 번째는 한정자 없이 @Autowired를 하는 대신 파라미터를 MemberSummaryPrinter로 지정해 줍니다.
이렇게 하면 자동주입 대상이 두 개가 되는 문제를 해결할 수 있습니다.
@Autowired는 객체에 빈을 자동으로 주입해 주는 역할을 합니다.
그런데 만약에 그 빈이 존재하지 않는다면 어떻게 될까요? 당연히 익셉션이 발생합니다.
이를 해결하기 위한 방법 세 가지를 소개합니다.
1) @Autowired 속성 required 사용
required는 필요하다는 뜻으로 true면 필수, false면 없어도 된다는 의미를 가집니다.
즉, 속성을 false로 설정하면 빈이 없으면 자동 주입을 하지 않습니다.
즉, 실행도 되지 않기 때문에 익셉션이 발생하지 않습니다. 그렇기 때문에 값 자체를 할당하지도 않습니다.
public class MemberInfoPrinter {
private MemberPrinter printer;
@Autowired(required = false)
public void setPrinter(MemberSummaryPrinter printer) {
this.printer = printer;
}
}
2) Optional 사용
자바 8의 Optional은 값의 존재 여부를 나타내는 컨테이너 클래스입니다.
존재 여부를 확인한 후에 없다면 에러가 나지 않게 값을 미리 처리할 수 있습니다.
public class MemberInfoPrinter {
private MemberPrinter printer;
@Autowired
public void setPrinter(Optional<MemberSummaryPrinter> printer) {
if (printer.isPresent()) {
this.printer = printer;
}
else {
this.printer = null;
}
}
}
이런 식으로 코드를 작성하면 익셉션을 방지하고 값을 null로 처리해 줄 수 있습니다.
만약 그냥 변수에 Optional을 적용한 경우에는 값이 없는 Optional을 할당합니다.
3) @Nullable 사용
@Nullable 어노테이션을 사용하면 자동 주입할 빈이 존재하면 해당 빈을 인자로 전달하고, 그렇지 않은 경우는 인자를 null로 전달합니다.
public class MemberInfoPrinter {
private MemberPrinter printer;
@Autowired
public void setPrinter(@Nullable MemberSummaryPrinter printer) {
this.printer = printer;
}
}
이 세 가지 방법들은 메소드 말고도 변수에도 사용이 가능합니다.
만약 printer의 값을 기본 생성자로 값을 설정한 후에 1,2,3번을 각각 수행하면 2,3번은 printer가 null로 변하지만 1번은 아예 메소드를 실행하지 않기 때문에 처음에 생성자로 설정한 값이 남아있게 됩니다.
실제로 코딩할 때 null값과 바뀌지 않은 값의 처리를 유의하도록 합시다!
이렇게 @Autowired로 자동 주입하는 방법을 배워 봤습니다.
그런데... 이 자동 주입과 수동 주입을 동시에 사용하면 과연 어떻게 될까요?
같이 확인해 보겠습니다.
public class MemberInfoPrinter {
private MemberPrinter printer;
@Autowired
@Qualifier("printer1")
public void setPrinter(MemberPrinter printer) {
this.printer = printer;
}
@Autowired
@Qualifier("printer2")
public void setPrinter(MemberSummaryPrinter printer) {
this.printer = printer;
}
}
@Configuration
public class AppCtx {
@Bean
@Qualifier("printer1")
public MemberPrinter memberPrinter1() {
return new MemberPrinter();
}
@Bean
@Qualifier("printer2")
public MemberSummaryPrinter memberPrinter2() {
return new MemberSummaryPrinter();
}
@Bean
public MemberInfoPrinter infoPrinter() {
MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
infoPrinter.setPrinter(memberPrinter2());
return infoPrinter;
}
}
이런 코드가 있다고 가정하겠습니다.
infoPrinter 메소드에서는 memberPrinter2 메소드를 불러서 MemberSummaryPrinter 형식으로 의존 주입을 해줬습니다.
하지만 MemberInfoPrinter 클래스는 MemberPrinter가 @Autowired, 즉 자동 주입되어 있는 상태입니다.
이때 infoPrinter는 MemberSummaryPrinter 형식일까요? 아니면 MemberPrinter 형식일까요?
정답은 @Autowired된 MemberPrinter 형식입니다. 스프링에서 수동 주입보다 자동 주입을 더 우선순위로 처리한다는 것을 알 수 있습니다. 그렇기 때문에 꼭 수동 주입을 해야 하는 상황이 아니라면 자동 주입을 더 권장한다고 합니다.
출처: 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문
'Spring' 카테고리의 다른 글
[Spring] 빈(Bean) 라이프사이클과 범위 (0) | 2023.08.27 |
---|---|
[Spring] 컴포넌트 스캔 (@Component, @ComponentScan) (0) | 2023.08.27 |
[Spring] 스프링 DI 설정 방식 (생성자, 세터 방식) (0) | 2023.07.28 |
[Spring] 스프링 DI란? (Dependency Injection, 의존성 주입) (feat. 객체 조립기) (0) | 2023.07.23 |
[Spring] 스프링 Web MVC 프로젝트 시작하기 (feat. maven, gradle의 차이) (0) | 2023.07.15 |