의존관계 주입 방법
- 생성자 주입
- setter 주입
- 필드 주입
- 일반 메서드 주입
* 주의할 점: 스프링 컨테이너가 관리하는 스프링 빈이어야 @Autowired 어노테이션을 통해 의존관계가 자동 주입됨
생성자 주입 방법 (Constructor Injection)
- 생성자를 통해서 의존 관계를 주입받는 방법
- 생성자 호출 시점에 딱 한 번만 호출되는 것이 보장
- 불변, 필수 의존관계에 사용 (대부분 불편, 필수 의존관계)
- 따라서, 웬만하면 생성자 주입 방법을 사용할 것을 권장 (하단에 부연설명 추가)
- 생성자가 하나만 정의되어 있고 스프링 빈이라면 @Autowired 어노테이션 생략 가능
* 생성자 주입 방법에만 의존관계 주입받을 필드에 final 키워드 사용 가능
생성자 주입 방법 1
생성자 주입 방법 2 (Lombok 이용)
Setter 주입 방법 (Setter Injection)
- 필드 값을 변경하는 setter 메서드를 통해 의존관계를 주입받는 방법
- 선택, 변경 가능성 있는 의존관계에 사용 (특수한 상황)
- Java Bean 프로퍼티 규약의 Setter 메서드 방식을 사용
필드 주입 방법 (Field Injection)
- 필드에 바로 주입하는 방법
- 코드가 간결해진다는 장점이 있지만,
- 외부에서 변경이 불가하여 테스트하기 힘들다는 단점 존재
- DI 프레임워크 없이 독단적으로 사용 불가능하기 때문에 가급적 사용 자제
- 여담이지만 인턴 생활을 할 때도 멘토님들이 필드 주입 방법은 사용하지 말라고 권고하셨음
- 사용해도 괜찮은 경우
- 애플리케이션의 실제 코드에 영향을 주지 않는 테스트 코드
일반 메서드 주입 방법 (Method Injection)
- 일반 메서드를 통해 주입받는 방법
- 한 번에 여러 필드를 주입받을 수 있음
- 사용하는 경우 거의 없음
옵션 처리
- 주입할 빈이 없더라도 동작해야 하는 경우 존재
- @Autowired 어노테이션은 required 키워드의 디폴트 값이 true이므로 자동 주입 대상이 없을 경우 오류 발생시킴
- 아래와 같이 세 가지 방법으로 오류 방지 가능
- @Autowired(required=false): 자동 주입할 대상이 없으면 setter 메서드 호출 X
- org.springframework.lang.@Nullable 어노테이션: 자동 주입할 대상이 없으면 NULL 입력
- Java8 Optional <>: 자동 주입할 대상이 없으면 Optional.empty 입력
* 1번을 제외한 2번, 3번의 경우 생성자 자동 주입 내 특정 필드에서도 사용 가능
생성자 주입 방법 (Constructor Injection)을 사용해야 하는 이유
- 대부분의 의존관계 주입은 한번 일어날 경우 애플리케이션 종료시점까지 의존관계를 변경할 일이 없음
- 생성자 주입은 객체를 생성할 때 딱 한 번만 호출되므로 불변하게 설계 가능
- Setter 주입을 사용할 경우, setter 메서드를 public으로 둬야 하는데, 이럴 경우 누군가 실수로 변경할 수 있기 때문에 오류에 취약함
- 프레임워크 없이 순수 자바 코드를 통해 단위 테스트를 하는 경우 생성자 주입 방법은 의존관계가 누락되었을 경우 바로 컴파일 에러를 통해 바로 캐치할 수 있지만 setter 주입 방법은 실행 후 NullPointerException이 발생함에 따라 누락됐음을 확인 가능
- 또한, 생성자 주입을 사용할 경우 필드에 final 키워드를 사용함으로써 생성자에서 값이 설정되지 않을 경우 컴파일 에러를 통해 바로 확인 가능
* 정리하자면, 프레임워크에 의존하지 않고, 순수 자바 언어의 특징을 제일 잘 살리는 방법이 생성자 주입 방법
의존관계 주입할 때 조회되는 빈이 두 개 이상일 경우?
- @Autowired 어노테이션은 Type으로 조회
- 즉, 상위 Type을 상속받는 하위 Type 클래스가 여러 개 있는 상태에서 상위 Type으로 조회할 경우 NoUniqureBeanDefinitionException 예외를 발생시킴
- 해당 문제를 하위 Type으로 조회하면서 해결할 수도 있지만 이는 SOLID 원칙 중 DIP를 위반
- NoUniqueBeanDefinitionException 해결 방법은 아래와 같이 크게 세 가지
- @Autowired 필드명 매칭
- @Qualifier 어노테이션 사용
- @Primary 어노테이션 사용
@Autowired 필드명 매칭
- @Autowired 어노테이션은 Type 매칭을 시도하고, 여러 Bean이 존재할 경우 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭
- 정리하자면, 1순위로 Type 매칭을 시도하고 여기서 같은 Type이 여러 개라면 필드 명, 파라미터 명으로 매칭 시도
@Qualifier 어노테이션 사용
- 추가 구분자를 붙여주는 방법 (Bean 이름을 변경하는 것은 아님)
- 주입 시에 @Qualifier를 붙여주고 이름 등록
- @Qualifier 이름을 통해 찾지 못할 경우 해당 이름의 스프링 빈을 추가로 찾음 (예를 들자면, @Qualifier("mainRepository")를 못 찾을 경우 MainRepository라는 스프링 빈을 찾음)
- 그럼에도 찾지 못할 경우 NoSuchBeanDefinitionException 예외 발생
@Primary 사용
- 우선순위를 정하는 방법
- @Autowired 어노테이션을 통해 여러 빈을 찾을 경우 @Primary 어노테이션이 추가된 빈이 우선권을 가짐
@Qualifier vs @Primary
- @Qualifier 어노테이션의 경우 사용하기 위해서는 모든 코드에 @Qualifier를 추가해줘야 하는 단점 존재
- @Primary 어노테이션의 경우 그러한 단점 X
- 우선순위는 @Qualifier가 @Primary 보다 높음
@Qualifier, @Primary 활용 예시
- 위 예시 코드처럼 애플리케이션에서 메인으로 사용하는 데이터베이스의 커넥션을 획득하는 스프링 빈(JpaRepository)이 있고, 테스트용으로 사용하는 데이터베이스의 커넥션을 획득하는 스프링 빈(MyBatisRepository)이 있다고 가정
- JpaRepository를 주로 사용하므로 @Primary 어노테이션을 적용해 조회하는 곳에서 @Qualifier 어노테이션 없이 편리하게 사용
- MyBatisRepository는 가끔 사용하므로 @Qualifier를 지정하여 명시적으로 획득하며 사용
참고
인프런 스프링 핵심 원리 - 기본편 (김영한 강사님)
반응형
'Spring' 카테고리의 다른 글
클라이언트에서 서버로 HTTP 요청 메시지 보내는 방법 (0) | 2021.06.02 |
---|---|
빈 스코프 (Bean Scope) (0) | 2021.05.26 |
스프링 빈 생명주기 (Spring Bean Life Cycle) (0) | 2021.05.25 |
@ComponentScan - 컴포넌트 스캔 (0) | 2021.05.17 |
Spring Boot Intellij IDEA MySQL 연동 에러 발생할 경우 (2) | 2019.10.03 |