@Autowired를 통해 자동 의존 주입을 할 때, 반드시 빈이 필요한 게 아니라서 존재하지 않을 때 익셉션을 발생시키기보다는 다른 처리가 가능하도록 해줄 필요가 있을 수 있다. 필수적으로 빈 객체가 있어야 하는지, 그렇지 않다면 어떻게 처리를 할 수 있는지 알아보자.
의존 자동 주입 기능의 특성
@Autowired 애노테이션을 이용하면 스프링 컨테이너에서 적합한 빈 객체를 찾아 자동적으로 의존을 주입해준다. 이전 포스트에서, 자동 주입 대상이 된 빈 객체가 존재하지 않으면 익셉션이 발생하는 것을 확인하였다. 그렇다면 자동 주입 기능을 사용하지만 빈 객체가 존재하지 않는 경우도 따로 처리해주고 싶다면 어떻게 해야 할까? 그리고 자동 주입과 명시적 의존 주입이 동시에 일어나면 어떻게 될까?
@Autowired의 필수 여부
public class PrintService {
private DateTimeFormatter dateTimeFormatter;
public void print(){
LocalDateTime today = LocalDateTime.now();
if (dateTimeFormatter == null) {
System.out.printf("오늘 날짜: %tF\n", today);
}
else {
System.out.printf("오늘 날짜: %tF\n", dateTimeFormatter.format(today));
}
}
@Autowired
public void setDateTimeFormatter(DateTimeFormatter dateTimeFormatter){
this.dateTimeFormatter = dateTimeFormatter;
}
}
PrintService 클래스의 print() 메서드는 dateTimeFormatter 필드가 null이면 오늘 날짜를 %tF 형식으로 출력하고, null이 아니면 dateTimeFormatter를 이용해서 오늘 날짜를 형식에 맞게 출력하는 메서드이다. setter 메서드는 @Autowired 애노테이션을 이용해서 자동 주입하도록 설정하였다.
print() 메서드는 dateTimeFormatter가 null인 경우에도 알맞게 동작하기 때문에 반드시 setter 메서드를 통해서 의존 객체를 주입할 필요는 없다. 그러나 @Autowired 애노테이션을 설정해 놓았기 때문에 dateTimeFormatter에 주입할 빈을 찾지 못한다면 익셉션이 발생할 것이다. 위의 코드에서는 익셉션이 발생하기보다는 단지 dateTimeFormatter 필드에 null이면 된다.
이렇게 자동 주입할 대상이 필수가 아닌 경우에는 세 가지 방식으로 대상에 null을 전달해줄 수 있다. 그 방법에는 @Autowired 애노테이션의 required 속성을 false로 하는 것, 자동 주입 대상의 타입을 Optional로 하는 것, 그리고 @Nullable 애노테이션을 사용하는 방법이 있다. 세 가지 방식 모두 필드와 메서드에 사용이 가능하다. 아래에서는 setter 메서드에 사용하는 방법을 알아보겠다.
1) @Autowired의 required 속성을 false로 하는 방식
자동 주입할 대상이 필수가 아닌 경우에 익셉션을 발생시키지 않는 방법의 첫 번째는 @Autowired의 required 옵션을 이용하는 것이다. @Autowired의 required 속성을 false로 지정하면 매칭 되는 빈이 없어도 익셉션이 발생하지 않고 자동 주입 자체를 수행하지 않는다. 따라서 PrintService 클래스의 setter 메서드 자체를 실행시키지 않는다.
public class PrintService {
private DateTimeFormatter dateTimeFormatter;
public PrintService() {
dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
}
public void print(){
LocalDateTime today = LocalDateTime.now();
if (dateTimeFormatter == null) {
System.out.printf("오늘 날짜: %tF\n", today);
}
else {
System.out.printf("오늘 날짜: %tF\n", dateTimeFormatter.format(today));
}
}
// 1. @Autowired의 required 속성을 false로 지정
// 매칭되는 빈이 없어도 Exception이 발생하지 않고 자동 주입을 수행하지 않는다.
// 이 경우에는 빈이 없으면 setter 메서드를 실행하지 않는다.
@Autowired(required = false)
public void setDateTimeFormatter(DateTimeFormatter dateTimeFormatter) {
this.dateTimeFormatter = dateTimeFormatter;
}
}
2) Optional 타입을 사용하는 방식
스프링 5 버전부터는 의존 주입 대상에 자바 8의 Optional을 사용해도 된다. 자동 주입 대상의 타입이 Optional인 경우, 일치하는 빈을 찾을 수 없다면 익셉션이 발생하지 않고 값이 없는 Optional을 인자로 전달한다. 그리고 일치하는 빈이 있다면 해당 빈을 값으로 갖는 Optional 객체를 전달한다.
public class PrintService {
private DateTimeFormatter dateTimeFormatter;
public PrintService() {
dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
}
public void print(){
LocalDateTime today = LocalDateTime.now();
if (dateTimeFormatter == null) {
System.out.printf("오늘 날짜: %tF\n", today);
}
else {
System.out.printf("오늘 날짜: %tF\n", dateTimeFormatter.format(today));
}
}
// 2. Optional 속성 (Spring 5 이상)
// 자동 주입 대상이 Optional일 때, 일치하는 빈이 존재하지 않으면 값이 없는 Optional을,
// 일치하는 빈이 존재하면 해당 빈을 값으로 갖는 Optional을 인자로 전달한다.
@Autowired
public void setDateTimeFormatter(Optional<DateTimeFormatter> formatterOpt) {
// DateTimeFormatter 빈을 값으로 갖는 Optional을 전달 받은 경우 해당 빈을 필드에 할당
if (formatterOpt.isPresent()) {
this.dateTimeFormatter = formatterOpt.get();
}
// 값이 없는 Optional을 전달 받은 경우 null값을 필드에 할당
else {
this.dateTimeFormatter = null;
}
}
}
위의 코드에서 DateTimeFormatter 타입의 빈이 존재하든 존재하지 않든 의존 주입이 수행되고 setter 메서드가 수행된다. 전달받은 Optional 객체의 값에 따라 알맞게 의존 객체를 사용하면 된다. 여기서는 빈 객체가 존재할 때 해당 객체를 dateTimeFormatter 필드에 할당해주고, 존재하지 않으면 null 값을 할당해주었다.
3) @Nullable 애노테이션을 사용하는 방식
@Autowired 애노테이션을 붙인 setter 메서드에서 @Nullable 애노테이션을 의존 주입 대상 파라미터에 붙이면, 스프링 컨테이너가 setter 메서드를 호출할 때 자동 주입할 빈이 존재하면 해당 빈을, 존재하지 않으면 null을 인자로 전달한다. 이 방식 역시 자동 주입할 빈이 존재하지 않아도 setter 메서드는 실행이 된다.
public class PrintService {
private DateTimeFormatter dateTimeFormatter;
public PrintService() {
dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
}
public void print(){
LocalDateTime today = LocalDateTime.now();
if (dateTimeFormatter == null) {
System.out.printf("오늘 날짜: %tF\n", today);
}
else {
System.out.printf("오늘 날짜: %tF\n", dateTimeFormatter.format(today));
}
}
// 3. @Nullable 애노테이션 사용
// 의존 주입 대상 파라미터에 @Nullable 애노테이션을 붙이면, 스프링 컨테이너가 setter 메서드를 호출할 때
// 자동 주입할 빈이 존재하면 해당 빈을 파라미터로 전달하고, 존재하지 않으면 null을 파라미터로 전달한다.
// 즉, required 속성을 사용하는 경우와 다르게, setter 메서드가 호출된다.
@Autowired
public void setDateTimeFormatter(@Nullable DateTimeFormatter dateTimeFormatter) {
this.dateTimeFormatter = dateTimeFormatter;
}
}
생성자 초기화와 필수 여부 지정 방식의 동작
앞선 세 방식을 소개할 때의 각 코드를 보면 클래스의 생성자에서 dateTimeFormatter 필드의 초기값을 지정해주는 것을 확인할 수 있다. 세 가지 방식 중 이 초기값 설정대로 출력되는 경우는 어떤 방식일까?
먼저, 첫 번째 방식인 @Autowired의 required 속성 값을 false로 설정하는 경우에 빈 객체를 설정하지 않았을 때의 출력 값은 아래와 같다.
오늘 날짜: 2021년 7월 31일
위 결과를 보면 @Autowired의 required 속성 값을 false로 설정하는 경우 일치하는 빈이 존재하지 않을 때 setter 메서드를 실행하지 않아 null값이 전달되지 않는 것을 확인할 수 있다. 만약 null값이 전달되었다면 생성자에서 초기화한 dateTimeFormatter의 형식으로 날짜가 출력되지 않았을 것이다.
두 번째 방식과 세 번째 방식을 이용하고 빈 객체를 설정하지 않았을 때의 출력 값은 아래와 같다.
오늘 날짜: 2021-07-31
스프링 컨테이너는 빈을 초기화하기 위해 기본 생성자를 이용해서 객체를 생성한 뒤, 의존 자동 주입을 처리하기 위해 setter 메서드를 호출한다. 따라서 생성자에서 필드를 초기화하더라도 setter 메서드가 null값을 전달받게 되어 필드가 다시 null로 바뀌게 된 것이다.
자동 주입과 명시적 의존 주입
그렇다면 설정 클래스에서 의존을 주입했는데 동시에 자동 주입 대상이면 어떻게 될까? 아래는 클래스 이름을 출력하는 ExDao 클래스와 이를 상속받는 ExDao2 클래스, 설정 클래스 AppCtx, 의존을 주입받을 클래스 TestService의 코드이다.
public class ExDao {
System.out.println("ExDao입니다.");
}
public class ExDao2 extends ExDao{
System.out.println("ExDao2 입니다.");
}
public class AppCtx {
@Bean
@Qualifier("example1")
public ExDao exDao1(){
return new ExDao();
}
@Bean
@Qualifier("example2")
public ExDao2 exDao2(){
return new ExDao2();
}
@Bean
public TestService testService(){
// setter를 이용해 ExDao2 타입의 빈 객체를 명시적으로 의존 주입하였다.
TestService testSvc = new TestService();
testSvc.setDao(exDao2());
return testSvc;
}
}
public class TestService {
...
// 한정사가 example1인 빈 객체를 자동 주입하였다.
@Autowired
@Qualifier("example1")
public void setDao(ExDao dao){
this.dao = dao;
}
}
ExDao입니다.
AppCtx의 testService() 메서드는 setter 메서드를 호출해서 exDao2 빈을 주입하고 있다. exDao2 빈은 ExDao2 객체이므로 "ExDao2 입니다."를 출력한다. TestService 클래스에서 setDao() 메서드는 example1 빈을 주입받아 필드에 할당한다. example1 빈은 exDao 빈, 즉 ExDao 객체이므로 "ExDao 입니다."를 출력한다.
실행 결과를 보면 ExDao 객체가 주입되었음을 알 수 있다. 즉 설정 클래스에서 setter 메서드를 통해 의존을 주입해도 해당 setter 메서드에 자동 주입이 설정되어 있으면 자동 주입을 통해 일치하는 빈을 주입한다. 따라서 @Autowired 애노테이션을 사용했다면 설정 클래스에서 객체를 직접 주입하기보다는 스프링이 제공하는 자동 주입 기능을 사용하는 것이 좋다.
참고 서적: <초보 웹 개발자를 위한 스프링 5 프로그래밍 입문>
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
Spring Framework 시리즈
'Java > [스프링 5 프로그래밍 입문]' 카테고리의 다른 글
[Spring] 빈 객체의 라이프사이클과 범위 (Life Cycle & Scope of Bean) (0) | 2021.08.03 |
---|---|
[Spring] 컴포넌트 스캔 - @Component, @ComponentScan 사용하기 (0) | 2021.08.02 |
[Spring] 의존 자동 주입(2) - 빈 이름과 한정사 (0) | 2021.07.31 |
[Spring] 의존 자동 주입(1) - @Autowired 애노테이션 (0) | 2021.07.30 |
[Spring] 스프링 애노테이션을 사용한 의존 주입(DI) (0) | 2021.07.28 |
댓글