
의존 관계 주입의 종류는 4가지가 있다.
- 생성자 주입
- 수정자 주입
- 필드 주입
- 일반 메서드 주입
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
} 생성자가 1개 일 시 @Autowired 가 없어도 된다. @Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy; @Autowired
public void init(MemberRepository memberRepository,
DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
주입할 스프링 빈이 없더라도 동작해야 할 경우가 있는데
일반적으로 @Autowired 를 쓴다면 required 가 true 로 설정되어 있기에 오류가 날 수 있다.
이 때 오류를 처리할 수 있는 방법은 다음이 있다.
@Autowired (required = false): 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨.org.springframework.lang.@Nullable: 자동 주입할 대상이 없으면 null이 입력된다.Optional<>: 자동 주입할 대상이 없으면 Optional.empty 가 입력된다.
예제를 봐보자.
//호출 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
System.out.println("setNoBean1 = " + member);
}
//null 호출
@Autowired
public void setNoBean2(@Nullable Member member) {
System.out.println("setNoBean2 = " + member);
}
//Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
System.out.println("setNoBean3 = " + member);
}
출력 결과
setNoBean2 = null
setNoBean3 = Optional.empty
과거에는 수정자 주입과 필드 주입을 많이 사용했지만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다.
그 까닭은 다음과 같다
- 대부분의 의존관계 주입은 한 번 일어나면 애플리케이션 종료시점 까지 변경될 일이 없다.
- 수정자 주입을 사용하면, setXXX 메서드를 public 으로 열어야 한다.
- 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방식이 아니다.
프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우에
다음과 같이 수정자 의존관계인 경우
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
//...
}
이렇게 테스트를 수행하면 실행은 된다.
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.createOrder(1L, "itemA", 10000);
}
그런데 막상 실행결과는 NPE(Null Point Exception)이 발생하는데, memberRepository 와 discountPolicy 모두 의존관계 주입이 누락 됐기 때문이다.
생성자 주입을 사용하면 주입 데이터를 누락하면 컴파일 오류 가 발생한다. 그리고 IDE 에서 어떤 값을 주입해야 하는지 알 수 있다.
fianl 키워드
생성자 주입을 사용하면 final 키워드를 사용할 수 있다. 그래서 혹시라도 생성자에서 값이 설정되지 않은 경우 컴파일 시점에서 오류를 발생시킨다.
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
}
//...
}
discountPolicy에 값을 설정해야하는데 그 부분이 누락- java 에서는
java : variable discountPolicy might not have been intialized라는 오류를 발생시킨다.- 컴파일 오류는 가장 빠르고 고치기 쉬운 오류이다.
- 생성자 주입을 선택하는 이유는 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이다.
- 기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에 수정자 주입을 동시에 사용할 수 있다.
- 항상 생성자 주입을 선택하는 것이 좋다.
결국 거의 대부분의 상황에서 생성자 주입을 쓴다면 조금 더 코드를 간결하게 작성할 수는 없을까?
그래서 롬복이라는 것이 나왔다. 다음 코드를 봐보자
기본코드
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
롬복 라이브러리가 제공하는 @RequiredArgsConstructor 를 사용하면 다음과 같아진다.
**최종결과 코드
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
롬복 라이브러리 적용 방법
build.gradle 에 라이브러리 및 환경 추가
plugins {
id 'org.springframework.boot' version '2.3.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
//lombok 설정 추가 시작
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}//lombok 설정 추가 끝
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
//lombok 라이브러리 추가 시작
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
//lombok 라이브러리 추가 끝
testImplementation('org.springframework.boot:spring-boot-starter-test')
{
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
<출처 : 스프링 핵심 원리 - 기본편 by 김영한>