스프링이다 스프링 부트를 이용해서 개발을 할 때 의존성 주입(DI-Dependency Injection)을 자주 사용하게 됩니다.
이때 보통 @Autowired을 이용해서 의존성 주입(DI)을 합니다.
스프링에서 지원해주는 자동 injection은 3가지 방식이 있습니다.
- 필드 injection
- setter injection
- 생성자 injection
각각을 간단한 코드로 먼저 살펴보겠습니다.
회원(Member)을 관리하는 service에서 DB 접근 로직이 있는 회원 repository(MemberRepository)를 DI를 통해 사용한다는 가정으로 소스를 작성해 보겠습니다.
field injection(필드 injection)
필드 injection은 class에 선언한 필드에 @Autowired를 통해 의존관계 주입을 하는 방법입니다.
아래의 소스처럼 필드 변수 위에 어노테이션을 선언해 줍니다.
@Service
@Transactional(readOnly = true)
public class MemberService {
@Autowired
private MemberRepository memberRepository;
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
setter injection
setter injection은 아래와 같이 setter()에 @Autowired를 선언하여 injection 하는 방법입니다.
스프링은 setter의 parmaparameter를 비교하여 자동으로 의존 주입을 시도합니다.
@Service
@Transactional(readOnly = true)
public class MemberService {
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
construct injection(생성자 injection)
생성자 injection방식은 @Autowired를 생성자 위에 선언하는 방법입니다.
스프링 4.3부터는 생성자가 1개일 경우 @Autowired 생략이 가능합니다. (doc 참조)
@Service
@Transactional(readOnly = true)
public class MemberService {
private MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
어떤 방법을 사용해야 할까?
결론부터 말하자면 생성자 injection 방식을 사용하는 것을 추천합니다. (보통의 경우)
생성자 방식을 사용할 경우 injection이 자유로워지고 런타임 과정에서도 변경할 수 있는 느슨한 결합이 가능해집니다.
이 부분은 OCP(open-closed-principle) 관점에서도 유용합니다. 수정에는 닫혀 있지만 확장에는 자유롭기 때문입니다.
그렇다면 setter injection 방식을 사용해도 느슨한 결합이 이루어지는데 왜 생성자 injection 방식을 사용할까요?
setter를 이용하면 런타임 도중에 injection을 수정할 수 있기 때문입니다.
보통 injection을 사용은 과정에서는 처음 application이 실행되는 과정에서 세팅이 완료되고 서비스 중간에 injection을 변경할 일이 없습니다. 하지만 setter를 사용하면 이러한 변경이 가능해지는 문제점이 생기게 됩니다.
또한 느슨한 연결을 통해 테스트 케이스를 작성할 때도 자유로운 모킹이 가능합니다.
그래서 결론적으로 생성자 injection 방식을 추천하고 있습니다.
이 방법은 빈 순환 문제를 발생시키기도 합니다.
더 좋은 방법
생성자 injection을 이용하는 더 좋은 tip을 소개해 드립니다.
보통의 service application에서는 한번 injection이 일어나면 이를 runtime과정에서 변경하는 것은 드문 상황입니다.
그래서 생성자를 이용하되 이를 변경할 수 없도록 final 키워드를 이용하는 방법이 있습니다.
이 방법을 이용하면 한번 injection 된 필드를 변경할 수 없게 됩니다.
final 키워드를 이용하면 컴파일 시점에서 검증도 가능한 장점이 있습니다.
스프링 4.3부터는 생성자가 1개일 경우에는 @Autowired를 생략 가능합니다.
@Service
@Transactional(readOnly = true)
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
한 번 더 나아가 더 간단한 코드를 만들어 보겠습니다.
lombok를 이용한 방법인데 @RequiredArgsConstructor를 사용하는 방법입니다.
@RequiredArgsConstructor를 선언하면 class내에 있는 required 한 필드(final로 선언한 필드)만 모아서 생성자를 자동으로 생성해 줍니다.
스프링 최신 버전에서는 생성자에 @Autowired를 자동으로 선언해 주기 때문에 @RequiredArgsConstructor를 선언하는 것만으로도 자동으로 injection을 수행해 줍니다.
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
참조
'Java & 스프링' 카테고리의 다른 글
Spring의 Jackson은 어떻게 동작할까? with ObjectMapper (0) | 2020.12.27 |
---|---|
[Spring] @Bean vs @Component 무엇이 다를까? (0) | 2020.12.25 |
[Spring] 트랜잭션 전파 알아보기, @Transaction, propagation (0) | 2020.12.15 |
[Spring] 스프링의 DI를 알아보자 (Dependency Injection) (0) | 2020.12.12 |
[spring] AOP를 이용하여 controller의 input data 자동 로그 기록하기 (0) | 2019.12.27 |
댓글