의존관계 주입 방법
- 생성자 주입
- setter 주입
- 필드 주입
- 일반 메서드 주입
* 주의할 점: 스프링 컨테이너가 관리하는 스프링 빈이어야 @Autowired 어노테이션을 통해 의존관계가 자동 주입됨
생성자 주입 방법 (Constructor Injection)
- 생성자를 통해서 의존 관계를 주입받는 방법
- 생성자 호출 시점에 딱 한 번만 호출되는 것이 보장
- 불변, 필수 의존관계에 사용 (대부분 불편, 필수 의존관계)
- 따라서, 웬만하면 생성자 주입 방법을 사용할 것을 권장 (하단에 부연설명 추가)
- 생성자가 하나만 정의되어 있고 스프링 빈이라면 @Autowired 어노테이션 생략 가능
* 생성자 주입 방법에만 의존관계 주입받을 필드에 final 키워드 사용 가능
생성자 주입 방법 1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component | |
public class ExampleServiceImpl implements ExampleService { | |
private final ExampleRepository exampleRespoistory; | |
@Autowired | |
public OrderServiceImpl(ExampleRespository exampleRepository) { | |
this.exampleResository = exampleRepository | |
} | |
} |
생성자 주입 방법 2 (Lombok 이용)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component | |
@RequiredArgsConstructor | |
// @RequiredArgsConstructor 어노테이션을 통해 생성자 생략 가능 | |
public class ExampleServiceImpl implements ExampleService { | |
private final ExampleRepository exampleRepository; | |
} |
Setter 주입 방법 (Setter Injection)
- 필드 값을 변경하는 setter 메서드를 통해 의존관계를 주입받는 방법
- 선택, 변경 가능성 있는 의존관계에 사용 (특수한 상황)
- Java Bean 프로퍼티 규약의 Setter 메서드 방식을 사용
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component | |
public class ExampleServiceImpl implements ExampleService { | |
private ExampleRepository exampleRespoistory; | |
@Autowired | |
public void setExampleRepository(ExampleRepository exampleRepository) { | |
this.exampleRepository = exampleRepository; | |
} | |
} |
필드 주입 방법 (Field Injection)
- 필드에 바로 주입하는 방법
- 코드가 간결해진다는 장점이 있지만,
- 외부에서 변경이 불가하여 테스트하기 힘들다는 단점 존재
- DI 프레임워크 없이 독단적으로 사용 불가능하기 때문에 가급적 사용 자제
- 여담이지만 인턴 생활을 할 때도 멘토님들이 필드 주입 방법은 사용하지 말라고 권고하셨음
- 사용해도 괜찮은 경우
- 애플리케이션의 실제 코드에 영향을 주지 않는 테스트 코드
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component | |
public class ExampleServiceImpl implements ExampleService { | |
@Autowired | |
private ExampleRepository exampleRespoistory; | |
} |
일반 메서드 주입 방법 (Method Injection)
- 일반 메서드를 통해 주입받는 방법
- 한 번에 여러 필드를 주입받을 수 있음
- 사용하는 경우 거의 없음
옵션 처리
- 주입할 빈이 없더라도 동작해야 하는 경우 존재
- @Autowired 어노테이션은 required 키워드의 디폴트 값이 true이므로 자동 주입 대상이 없을 경우 오류 발생시킴
- 아래와 같이 세 가지 방법으로 오류 방지 가능
- @Autowired(required=false): 자동 주입할 대상이 없으면 setter 메서드 호출 X
- org.springframework.lang.@Nullable 어노테이션: 자동 주입할 대상이 없으면 NULL 입력
- Java8 Optional <>: 자동 주입할 대상이 없으면 Optional.empty 입력
* 1번을 제외한 2번, 3번의 경우 생성자 자동 주입 내 특정 필드에서도 사용 가능
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Autowired(required = false) | |
public void setObjectThatIsNotBean(NotBean notBean) { | |
// 호출안됨 | |
} | |
@Autowired | |
public void setObjectThatIsNotBean2(@Nullable NotBean notBean) { | |
// notBean = null | |
} | |
@Autowired(required = false) | |
public void setObjectThatIsNotBean3(Optional<NotBean> notBean) { | |
// notBean = Optional.empty | |
} |
생성자 주입 방법 (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이 여러 개라면 필드 명, 파라미터 명으로 매칭 시도
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component | |
public class JpaRepository extends ExampleRepository {} | |
@Component | |
public class MyBatisRepository extends ExampleRepository {} | |
@Service | |
public class ExampleServiceImpl extends ExampleService { | |
@Autowired | |
private ExampleRepository jpaRepository; // JpaRepository 정상 주입 | |
@Autowired | |
private ExampleRepository myBatisRepository; // MyBatisRepository 정상 주입 | |
} |
@Qualifier 어노테이션 사용
- 추가 구분자를 붙여주는 방법 (Bean 이름을 변경하는 것은 아님)
- 주입 시에 @Qualifier를 붙여주고 이름 등록
- @Qualifier 이름을 통해 찾지 못할 경우 해당 이름의 스프링 빈을 추가로 찾음 (예를 들자면, @Qualifier("mainRepository")를 못 찾을 경우 MainRepository라는 스프링 빈을 찾음)
- 그럼에도 찾지 못할 경우 NoSuchBeanDefinitionException 예외 발생
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component | |
@Qualifier("mainRepository") | |
public class JpaRepository extends ExampleRepository {} | |
@Component | |
@Qualifier("subRepository") | |
public class MyBatisRepository extends ExampleRepository {} | |
@Service | |
public class ExampleServiceImpl extends ExampleService { | |
private final ExampleRepository exampleRepository; | |
public ExampleServiceImpl(@Qualifier("mainRepository") ExampleRepository exampleRepository) { | |
this.exampleRepository = exampleRepository; | |
} | |
} |
@Primary 사용
- 우선순위를 정하는 방법
- @Autowired 어노테이션을 통해 여러 빈을 찾을 경우 @Primary 어노테이션이 추가된 빈이 우선권을 가짐
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component | |
@Primary | |
public class JpaRepository extends ExampleRepository {} | |
@Component | |
public class MyBatisRepository extends ExampleRepository {} | |
@Service | |
public class ExampleServiceImpl extends ExampleService { | |
private final ExampleRepository exampleRepository; | |
public ExampleServiceImpl(ExampleRepository exampleRepository) { | |
this.exampleRepository = exampleRepository; // JpaRepository 주입 받음 | |
} | |
} |
@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 |