인프런 강의 中
김영한 강사님의 '스프링 핵심 원리' 내용을 정리했습니다.
이전 시간에는 '스프링이 제공해주는 Component Scan이라는 기능 덕분에 개발자들이 코드 작성을 한 결 편하게 할 수 있게 됐다' 라는 내용의 공부를 했다. 이번 시간에는 우리가 DIP, OCP를 준수하기 위해 사용했던 의존성 주입의 종류에 대해서 김영한 강사님의 강의를 통해 학습해보겠다! 참고로 우리는 지금까지 생성자 주입만을 사용했다는 점 참고바란다.
목차
- 다양한 의존관계 주입 방법
- 생성자 주입을 선택해야하는 이유
1. 다양한 의존관계 주입 방법
의존관계 주입은 크게 3가지 방법이 있다.
- 생성자 주입
- 수정자 주입(setter 주입)
- 필드 주입
생성자 주입
위에서 언급한대로 우리가 지금까지 사용했던 방법이다. 이름 그대로 생성자를 통해서 의존 관계를 주입 받는 방법이다.
생성자 주입은 다음과 같은 특징을 가진다.
- 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
- 불변, 필수 의존관계에 사용된다.
불변? 필수? 갑자기 이런 특징을 왜 가지지? 라는 생각이 들 수 있다. 이 특징을 가질 수 있었던 이유는 필드 선언부에 있다.
위 이미지처럼 필드 변수가 final로 선언되어 있으면 해당 변수에는 무조건 값이 있어야 된다는 뜻이자 한 번 선언된 값으로 고정된다는 뜻이다. 무조건 값이 있어야 되기 때문에 필수인 것이고 값이 고정되니까 불변인 것이다.(뒤에서 나오겠지만 수정자 주입 같은 경우는 final 사용 못함!)
discountPolicy를 생성자를 통해서 의존성 주입하고자 final로 선언했는데 생성자에서 discountPolicy를 깜빡하고 주입을 시키지 않았다? 그럼 다음 코드의 모습이 되고 다음과 같은 컴파일 에러가 발생한다.
위와 같이 바로 컴파일 에러가 발생하기 때문에 실수로 의존성 주입을 빼먹을 일이 없다! (물론 의존성 주입을 안하고 싶거나 다른 이유 등으로 final을 붙이지 않으면 생성자에서 해당 필드를 주입시키지 않아도 에러가 발생하진 않음). 즉, 컴파일 에러만 잡아주면 잘 됨!! 테스트 코드로 확인해보겠다!
중요! 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입 된다. 물론 스프링 빈에만 해당된다.
수정자 주입(setter 주입)
setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.
참고: @Autowired의 기본 동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게 하려면 @Autowired(required=false)로 지정하면 된다.
수정자 주입은 다음과 같은 특징을 가진다.
- 특징 1: 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다.
참고: 자바빈 프로퍼티, 자바에서는 과거부터 필드의 값을 직접 변경하지 않고, setXxx, getXxx라는 메서드를 통해서 값을 읽거나 수정하는 규칙을 만들었는데, 그것이 자바빈 프로퍼티 규약이다.
자바빈 프로퍼티 규약 예시
class Data {
private int age;
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
- 특징 2: 선택, 변경 가능성이 있는 의존관계에 사용
위에서 잠깐 언급 했듯이 수정자 의존성 주입은 필드부에 fianl을 사용하지 않는다. 그 이유가 바로 수정자 의존성 주입의 특징인 선택, 변경 때문이다. 필드 값을 변경해야할 때는 수정자 주입이 유리할 수 있다.
하지만 수정자 주입은 NullPointerException을 가져다 줄 수 있다. 순수 자바 코드의 테스트를 통해서 확인해보겠다.
위와 같이 관계 주입을 빼먹은 경우에는 필드에 주입된 값이 없으니 바로 NullPointException이 발생한다! 수정자 주입은 생성자 주입처럼 컴파일 에러를 띄워주지 않기 때문에 이렇게 빼먹고 다른 에러를 발생시킬 수 있다. 수정자 주입은 다음과 같이 세팅을 해줘야 한다.
필드 주입
이름 그대로 필드에 바로 주입하는 방법이다. 필자는 이 방법을 편해서 많이 사용했는데, 사실 단점이 많은 주입 방법이였다....ㅠ 특징을 살펴보자ㅠ
- 코드가 간졀해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명적인 단점이 있다. => 필자는 개인 프로젝트에서 사용했기 때문에 외부에서 테스트를 하지 않아서 해당 단점을 파악하지 못했다..
- DI 프레임워크가 없으면 아무것도 할 수 없다. => 순수 자바 코드로는 아무것도 할 수가 없음
- 애플리케이션의 실제 코드와 관계 없는 테스트 코드, 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용하고 나머진 사용하지 말자!
다른 주입 방식보다 훨씬 간편하긴 하다...ㅎ 필자는 혹해서 사용했지만 필자도..! 이제는 사용을 지양할 것이다!
순수 자바 코드로 필드 주입을 했을 때 발생하는 에러 사항을 확인해보자.
위 처럼 테스트 코드를 작성하고 실행하면 NullPointerException이 발생한다. 순수 자바 코드에서는 스프링이 자동으로 의존성 주입을 시켜주지 않기 때문에 당연히 위 에러가 발생한다. 그럼 필드 주입은 순수 자바로 테스트 할 방법이 없는가? 그건 아니다. setter를 만들어주고 setter로 접근해서 필드 값을 주입 후 테스트를 하면 되긴 한다. 근데 말만 들어도 좋은 방법은 아니란 걸 직감할 것이다. 고로 필드 주입은 테스트에서 이런 단점이 있음!!
2. 생성자 주입을 선택해야 하는 이유
사실 위 글만 읽어도 생성자 주입을 사용해야겠다는 생각이 들 것이다. 필자가 생성자 주입을 편애하는 글을 썼다. 하지만! 팩트인걸 어떡하나!!! 해당 목차에서는 간단하게 위 내용들을 요약하는 시간을 가져보겠다.
과거에는 수정자 주입과 필드 주입을 많이 사용했지만, 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다. 이유는 다음과 같다.
불변
- 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)
- 사실 의존성 주입은 드라마 배역과 같다. 드라마에서 배역이 정해지고 바뀌는 걸 본 적이 있나? 거의 없을 것이다. 이와 마찬가지로 의존성 주입도 정해지고 프로그램이 돌아가기 때문에 중간에 바뀔 일이 거의 없다. 그러므로 불변하는 특성을 가지는 것이 유리하다.
- 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 한다.
- 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.
- 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.
누락
- 수정자 주입에서 테스트 할 때처럼 의존관계 주입을 누락시키는 경우 NullPointeException이 발생한다.
- 생성자 주입은 컴파일 주입 데이터를 누락 했을 때 컴파일 오류가 바로 발생하기 때문에 누락시킬 수 없다!
final 키워드
- 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에서 막아준다. 위 생성자 주입 글에서 테스트 코드 확인해보자!
참고: 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.
이렇게 의존성 주입 방법과 각 방법은 장단점들을 알아보았다. 다음 시간에는 스프링 빈에 등록되지 않은 인스턴스를 자동 주입 하고싶을 땐 어떻게 해야하지?!에 대한 궁금증을 해소하는 시간과 lombok이라는 매우 친절한 친구에 대해서 공부해보겠다. 스포하자면.. lombok 덕분에 생성자 주입이 필드 주입보다 더 간단해진다! 이상!
!!김영한 강사님 강의
'스프링 > SpringBasicCore' 카테고리의 다른 글
Section7(3편) 의존관계 자동 주입 시 문제 상황 및 대처 방법 (0) | 2024.03.11 |
---|---|
Section7(2편) 의존관계 자동 주입(옵션 및 lombok 활용) (4) | 2024.03.08 |
Section6 Component Scan (0) | 2024.02.26 |
Section5 Singleton (0) | 2024.02.21 |
Section4(3편) BeanDefinition (1) | 2024.02.20 |