Spring의 DI
앞선 포스팅 DI에서 의존이 무엇인지, DI를 이용해 의존 객체를 주입하는 방법에 대해 알아봤다. 그리고 객체를 생성하고 의존 주입을 이용해서 객체를 서로 연결해주는 조립기에 대해서 살펴봤다.
위의 내용을 정리한 이유는 스프링이 DI를 지원하는 조립기이기 때문이다.
실제로 스프링은 조립기와 유사한 기능을 제공한다. 즉, 스프링은 필요한 객체를 생성하고 생성한 객체에 의존을 주입한다. 또한 객체를 제공하는 기능을 정의하고 있다.
1. 스프링을 이용한 객체 조립과 사용
스프링을 사용하려면 먼저 스프링이 어떤 객체를 생성하고, 의존을 어떻게 주입할지를 정의한 설정 정보를 작성해야 한다. 설정 코드는 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppCtx {
@Bean
public MemberDao memberDao() {
return new MemberDao();
}
@Bean
public MemberRegisterService memberRegSvc() {
return new MemberRegisterService(memberDao());
}
@Bean
public ChangePasswordService changePwdSvc() {
ChangePasswordService pwdSvc = new ChangePasswordService();
pwdSvc.setMemeberDao(memberDao());
return pwdSvc;
}
}
@Bean 애노테이션은 해당 메서드가 생성한 객체를 스프링 빈이라고 설정한다. 위 코드의 경우 세 개의 메서드에 @Bean 애노테이션을 붙였는데 각각의 메서드마다 한 개의 반 객체를 생성한다. 이때 메서드 이름을 빈 객체의 이름으로 사용한다. 예를 들어 “memberDao”라는 이름으로 스프링에 등록된다.
객체를 생성하고 의존 객체를 주입하는 것은 스프링 컨테이너이므로 설정 클래스를 이용해서 컨테이너를 생성해야 한다.
AnnotationConfigApplcationContext 클래스를 이용해서 스프링 컨테이너를 생성할 수 있다.
1
ctx = new AnnotationConfigApplicationContext(AppCtx.class);
컨테이너를 생성하면 getBean() 메서드를 이용해서 사용할 객체를 구할 수 있다. 다음 예는 getBean() 메서드의 사용 예이다.
1
2
3
// 컨테이너에서 이름이 memberRegSvc인 빈 객체를 구한다.
MemberRegisterService regSvc =
ctx.getBean("memberRegSvc", MemberRegisterService.class);
2. DI방식 1 : 생성자 방식
아래 코드에서 memberRegSvc() 메서드를 보면 MemberRegisterServiec 클래스는 생성자를 통해 의존 객체를 주입받았다.
1
2
3
4
@Bean
public MemberRegisterService memberRegSvc() {
return new MemberRegisterService(memberDao());
}
3. DI 방식 2 : 세터 메서드 방식
생성자 외에 세터 메서드를 이용해서 객체를 주입받기도 한다. 일반적인 세터(setter) 메서드는 자바빈 규칙에 따라 다음과 같이 작성된다.
- 메서드 이름이 set으로 시작한다.
- set 뒤에 첫 글자는 대문자로 시작한다.
- 파라미터가 1개이다.
- 리턴 타입이 void이다.
1 2 3 4 5 6 7
@Bean public MemberInfoPrinter infoPrinter() { MemberInfoPrinter infoPrinter = new MemberInfoPrinter(); infoPrinter.setMemberDao(memberDao()); infoPrinter.setPrinter(memberPrinter()); return infoPrinter; }
생성자 vs 메서드
이 두 방식 중 무엇이 좋을까? 답은 없다. 상황에 따라 두 방식을 혼용해서 사용해야 한다. 각각의 장점은 다음과 같다.
-생성자 방식 : 빈 객체를 생성하는 시점에 모든 의존 객체가 주입된다.
-설정 메서드 방식 : 세터 메서트 이름을 통해 어떤 의존 객체가 주입되는지 알 수 있다.
각 방식의 장점은 곧 다른 방식의 단점이 된다. 예를 들어 생성자의 파라미터 개수가 많은 경우 각 인자가 어떤 의존 객체를 설정하는지 알아내려면 생성자의 코드를 확인해야 한다.
반면에 생성자 방식은 빈 객체를 생성하는 시점에 필요한 모든 의존 객체를 주입받기 때문에 객체를 사용할 때 완전한 상태로 사용할 수 있다. 하지만 세터 메서드 방식은 세터 메서드를 사용해서 필요한 의존 객체를 전달하지 않아도 빈 객체가 생성되기 때문에 객체를 사용하는 시점에 NullPointerException이 발생할 수 있다.
Ref.
- 최범균, 스프링프로그래밍입문5, 가메출판사.