일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- 컴포넌트 스캔
- RunWith
- Controller
- jdbc
- 테스트 코드
- 스프링 데이터 JPA
- 좋은 객체지향 설계 원칙
- ResponseEntity
- 의존성 주입
- 406 NOT_ACCEPTABLE
- SpringBootTest
- 빈 스코프
- entity
- 스프링
- Java Reflection API
- testresttemplate
- 405 METHOD_NOT_ALLOWED
- 키움
- 통합 테스트
- restTemplate
- 기본 생성자
- Not Acceptable
- 가을야구
- 스프링 컨테이너
- jdbc template
- 랜덤 포트
- 의존관계 자동 주입
- JPA
- 정적 컨텐츠
- 스프링 IoC 컨테이너
- Today
- Total
코드네임 JY
[스프링 핵심] 의존관계 자동 주입 본문
미리보기
✅ 의존관계 자동 주입 방식들을 찾아보자!
✅ 생성자 주입을 강력 추천한다!
✅ 코드를 더 줄여볼 수는 없을까! (with 롬복)
✅ 동일한 빈을 구분하는 방법에 대해 알아보자!
🍭 의존관계 자동 주입
이제는 설정 정보 파일을 따로 작성하지 않고도, 의존관계를 자동으로 주입할 수 있다.
의존관계 자동 주입에는 크게 3가지 방식이 있다! 코드를 통해 알아보자.
1️⃣ 생성자 주입
@Component
public class ReservationServiceImpl implements ReservationService {
private final TicketReserve ticketReserve; // 핵심 : final 키워드
private final MemberRepository memberRepository;
@Autowired // 핵심 : 생성자
public ReservationServiceImpl(TicketReserve ticketReserve, MemberRepository memberRepository) {
this.ticketReserve = ticketReserve;
this.memberRepository = memberRepository;
}
}
생성자를 통한 주입 방식이다. 생성자 부분에 @Autowired 어노테이션을 붙여서 구현할 수 있다.
필드(변수) 부분에 final 키워드를 사용하였다. 핵심이다! 밑에서 더 구체적으로 설명하겠다.
2️⃣ Setter 주입
@Component
public class ReservationServiceImpl implements ReservationService {
private TicketReserve ticketReserve;
private MemberRepository memberRepository;
@Autowired
public setReservationServiceImpl(TicketReserve ticketReserve) {
this.ticketReserve = ticketReserve;
}
@Autowired
public setJPAMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
'JavaBeans' 라는 표준(Standard)에는 Getter / Setter 에 대한 작성 표준에 따라 위와 같은 방식으로 작성한다.
이는 변경에 대한 가능성이 있는 의존관계일 경우 사용한다!
참고로 Setter 주입에서 @Autowired 어노테이션에 (required = false) 옵션을 붙여주었을 때,
자동 주입할 대상(빈)이 없으면 Setter 메서드 자체가 실행되지 않는다.
@Autowired(required = false)
public void setTest(Object object) {
System.out.println("이 메서드는 실행되지 않아요");
}
만약 파라미터인 object 변수가 스프링 빈이 아니라면, 해당 메서드는 실행되지 않는다는 것이다!
3️⃣ 필드 주입
@Component
public class ReservationServiceImpl implements ReservationService {
@Autowired private TicketReserve ticketReserve;
@Autowired private MemberRepository memberRepository;
}
(뭐가 이렇게 간단해? 🐶 꿀 아닌가?) 생성자나 Setter를 따로 쓰지 않고, 필드 부분에 @Autowired만 명시하면 된다.
하지만 필드 주입은 비추한다.. 실제로 인텔리제이에서도 필드 주입을 지양한다!
어떤 의존관계를 주입 받는지 명확히 볼 수 없고, final 키워드를 사용하지 않아 state safe 하지 않고,
필드 주입을 통해 주입한 객체를 테스트하려면 스프링 컨테이너를 통으로 올려야 한다, 즉 단위 테스트가 힘들어진다.
(왜냐면 @Autowired는 스프링 컨테이너가 필요한데, 생성자 같은 경우는 자바에서 단위 테스트 동작이 가능하기 때문)
🎂 생성자 주입의 이점
결국, 생성자 주입 방식이 가장 깔끔하고 멋있는 방법이라고 생각한다! 생성자 주입 방식은 다음과 같은 이점들이 있다.
첫 번째는 '생성자'를 통해 구현하기 때문에 얻는 이점이다.
생성자 주입은 객체를 생성할 때 딱 1번만 호출하기 때문에, 의존관계를 불변하게 설계할 수 있다!
물론 의존관계를 실행 중간에 바꾸는 경우가 필요할 수도 있지만, 별로 좋은 경우는 아니라고 생각한다.
두 번째는 'final 키워드'를 사용해서 얻는 이점이다.
자바에서 final 키워드를 사용하면 해당 변수는 더 이상 수정할 수 없고, 값을 무조건 할당해야하는 특징이 있다.
이는 필드에 final 키워드를 사용할 수 있기 때문에, 값이 설정되지 않는 오류를 컴파일 시점에 방지할 수 있다!
(🚫 Setter 주입이나 필드 주입은 생성 시점 이후에 실행하는 방식이기 때문에 final 키워드를 사용할 수 없다)
이점도 많고, 깔끔하다! 생성자 주입 방식을 주로 사용하는 것을 추천한다!
🍦 코드 간단화, 그리고 롬복
생성자 주입은 @Autowired 어노테이션을 통해 구현할 수 있는데, 생성자 주입에는 다음과 같은 특징이 있다.
@Component
public class ReservationServiceImpl implements ReservationService {
private final TicketReserve ticketReserve;
private final MemberRepository memberRepository;
// @Autowired
public ReservationServiceImpl(TicketReserve ticketReserve, MemberRepository memberRepository) {
this.ticketReserve = ticketReserve;
this.memberRepository = memberRepository;
}
}
만약 생성자가 1개 밖에 없다면? @Autowired 어노테이션을 생략할 수 있다!
단, 생성자가 2개 이상이라면 반드시 @Autowired를 명시해주어야 한다.
'롬복(Lombok)'을 이용해서 코드를 더 간단하게 줄일 수 있다. (but 코드를 줄인다고 만사가 아니다. 알아보기 편해야지!!)
롬복은 '반복되는 메서드 작성 코드를 줄여주는 자바 코드 다이어트 라이브러리' 라고 한다.
Getter나 Setter, 그리고 여기서는 생성자까지! 코드를 작성하다보면 반복되는 것들이 꽤나 있는데, 이들을 줄여준다.
@Component
@RequiredArgsConstructor // Lombok 라이브러리 기능
public class ReservationServiceImpl implements ReservationService {
private final TicketReserve ticketReserve;
private final MemberRepository memberRepository;
}
롬복 라이브러리에는 @RequiredArgsConstructor 어노테이션이 있는데, final이 붙은 필드들의 생성자를 자동으로 생성해준다.
어노테이션 하나로 생성자 코드를 대체한다! 또한 @Getter, @Setter를 사용하면 Getter, Setter를 작성하지 않고 구현 가능하다!
🥛 동일한 빈을 구분하는 방법
@Autowired의 원리는, 타입으로 빈을 조회하는 것이다. 하지만 같은 타입의 빈이 2개 이상 있다면..? 😱😱
다행히도 스프링은 타입이 동일한 빈에 대해 구분하는 방법을 알고 있다. 알아보도록 합시다요!
(MemberRepository 인터페이스를 구현하는 JPAMemberRepository, JDBCMemberRepository가 있다고 해보자!)
@Repository
public class JPAMemberRepository implements MemberRepository {}
@Repository
public class JDBCMemberRepository implements MemberRepository {}
1️⃣ @Qualifier, 구분자 이름 작성해주기
@Qualifier 어노테이션은 '구분자' 인데, 어노테이션을 구분하는 구분자를 만들어주면 된다!
예를 들어, JPA가 메인 리포지토리이고, JDBCMemberRepository가 서브 리포지토리라고 해보자.
@Repository
@Qualifier("MainRepository")
public class JPAMemberRepository implements MemberRepository {}
@Repository
@Qualifier("SubRepository")
public class JDBCMemberRepository implements MemberRepository {}
위 코드처럼 @Qualifier 구분자를 사용하여 구분하는 것은 완료했다.
하지만 이렇게 둔다고 잘 실행되는 것은 아니고, 실제로 사용하는 코드는 생성자 주입에서 볼 수 있다.
@Autowired
public ReservationServiceImpl(TicketReserve ticketReserve, @Qualifier("MainRepository") MemberRepository memberRepository) {
this.ticketReserve = ticketReserve;
this.memberRepository = memberRepository;
}
생성자 주입 부분에서 파라미터 앞에 @Qualifier("구분자 이름") 와 같이 사용하면 된다.
저 말은 "MainRepository 구분자로 써있는 것을 가져와야 해!" 라는 뜻이다!
음? 근데 이렇게 되면 빈 이름이 바뀌는 것인가? 그건 아니다!
빈 이름은 바뀌지 않고, 단순히 동일한 타입의 빈에 대해서 구분자를 통해 어떤 빈을 써야하는지 개발자가 지정해주는 것이다!
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'autoAppConfiguration'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'JDBCMemberRepository'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'JPAMemberRepository'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'reservationServiceImpl'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'ticketReserveImpl'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'reservationServiceImpl' via constructor to bean named 'ticketReserveImpl'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'reservationServiceImpl' via constructor to bean named 'JPAMemberRepository'
실제 찍힌 로그를 보면, 빈 이름은 바뀌지 않는 것을 볼 수 있다. 단순히 구분자 역할일 뿐이다!
2️⃣ @Primary, 우선순위 정하기
@Primary 어노테이션은 어떤 것을 우선적으로 사용해야하는지 알려주는 역할을 한다.
말 그대로 어떤 것이 더 Primary 하냐는 뜻이다! 실제로 구현되는 코드를 보자.
@Repository
public class JPAMemberRepository implements MemberRepository {}
@Repository
@Primary // 만약 JDBC를 사용하고 싶다면
public class JDBCMemberRepository implements MemberRepository {}
이건 단순히 이렇게만 써주면 된다! 생성자 부분에 특별히 추가할 코드는 없다.
그러면 타입이 겹치는 빈에 대해 스프링이 알아서 @Primary 붙은 클래스에 접근해 해당 클래스를 우선적으로 가져온다.
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'autoAppConfiguration'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'JDBCMemberRepository'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'JPAMemberRepository'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'reservationServiceImpl'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'ticketReserveImpl'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'reservationServiceImpl' via constructor to bean named 'ticketReserveImpl'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'reservationServiceImpl' via constructor to bean named 'JDBCMemberRepository'
실제로 찍힌 로그를 보면, 우선적으로 JDBCMemberRepository를 제대로 가져온 것을 볼 수 있다.
'백엔드 공부' 카테고리의 다른 글
[CRUD 연습] 프로젝트 구상 (0) | 2023.01.08 |
---|---|
[스프링 핵심] 빈 스코프 (0) | 2023.01.06 |
[스프링 핵심] 컴포넌트 스캔 (0) | 2023.01.05 |
[스프링 핵심] 싱글톤 패턴 (0) | 2023.01.04 |
[스프링 핵심] 스프링 컨테이너 (0) | 2023.01.03 |