06. Spring Basic - 의존관계 자동 주입 (1)

MoonJaeGyeong·2023년 9월 14일

Spring

목록 보기
6/10
post-thumbnail

1. 다양한 의존관계 주입


의존 관계 주입의 종류는 4가지가 있다.

  • 생성자 주입
  • 수정자 주입
  • 필드 주입
  • 일반 메서드 주입

1. 생성자 주입

  • 생성자를 통해서 의존 관계를 주입 하는 방법
  • 생성자 호출 시 딱 1번만 호출되는 것을 보장
  • 불변, 필수 의존관계에 사용
    @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 가 없어도 된다.

2. 수정자 주입(setter 주입)

  • setter 라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법
  • 선택, 변경 가능성이 있는 의존관계에 사용
  • 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법
@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;
 	}
}

3. 필드 주입

  • 필드에 그대로 바로 주입하는 방법
  • 코드가 간결하지만 외부에서 변경이 불가능해서 테스트하기 힘들다.
  • DI 프레임 워크가 없으면 아무것도 할 수 없다.
  • 사용하지 말자
@Component
public class OrderServiceImpl implements OrderService {
	@Autowired
 	private MemberRepository memberRepository;
 	@Autowired
 	private DiscountPolicy discountPolicy;
}

4. 일반 메서드 주입

  • 일반 메서드를 통해서 주입하는 방법
  • 한번에 여러 필드를 주입 받을 수 있다.
  • 일반적으로 잘 사용하지 않는다.
@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;
	 }
}

5. 옵션 처리

주입할 스프링 빈이 없더라도 동작해야 할 경우가 있는데
일반적으로 @Autowired 를 쓴다면 requiredtrue 로 설정되어 있기에 오류가 날 수 있다.
이 때 오류를 처리할 수 있는 방법은 다음이 있다.

  • @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

2. 대부분 생성자 주입을 쓰는 이유


과거에는 수정자 주입과 필드 주입을 많이 사용했지만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다.
그 까닭은 다음과 같다

1. 불변

  • 대부분의 의존관계 주입은 한 번 일어나면 애플리케이션 종료시점 까지 변경될 일이 없다.
  • 수정자 주입을 사용하면, setXXX 메서드를 public 으로 열어야 한다.
  • 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방식이 아니다.

2. 누락

프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우에
다음과 같이 수정자 의존관계인 경우

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 라는 오류를 발생시킨다.
  • 컴파일 오류는 가장 빠르고 고치기 쉬운 오류이다.

3. 결론

  • 생성자 주입을 선택하는 이유는 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이다.
  • 기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에 수정자 주입을 동시에 사용할 수 있다.
  • 항상 생성자 주입을 선택하는 것이 좋다.

3. 롬복


결국 거의 대부분의 상황에서 생성자 주입을 쓴다면 조금 더 코드를 간결하게 작성할 수는 없을까?
그래서 롬복이라는 것이 나왔다. 다음 코드를 봐보자

기본코드

@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()
}
  1. Preferences(윈도우 File Settings) plugin lombok 검색 설치 실행 (재시작)
  2. Preferences Annotation Processors 검색 Enable annotation processing 체크 (재시작)
  3. 임의의 테스트 클래스를 만들고 @Getter, @Setter 확인

<출처 : 스프링 핵심 원리 - 기본편 by 김영한>

profile
내 맘대로 끄적이는 개발 블로그

0개의 댓글