-
Notifications
You must be signed in to change notification settings - Fork 0
스프링 핵심편 Section7
의존관계 주입 방법
- 생성자 주입
- 수정자 주입(setter 주입)
- 필드 주입
- 일반 메서드 주입
-
이름 그대로 생성자를 통해서 DI
-
우리가 지금까지 진행한 방법
-
특징
- 생성자 호출시점에 딱 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가 없어도 괜찮다.
@Autowired
public void setMemberRepository(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
- 여기서는 final이 들어가면 안된다.
- 특징
- 선택, 변경 가능성 있는 의존관계에 사용
자바빈 프로퍼티 규약
→ 필드 값을 직접 변경하는 것이 아닌 getXxx, setXxx를 이용하여 값을 읽거나 수정하자.
class Data {
private int age;
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
- age가 필드값이니 setAge, getAge
@Component
public class OrderServiceImpl implements OrderService {
@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;
}
- 필드에 바로 주입하는 방법
- 특징
- 코드가 간결하지만, 외부에서 변경이 불가능하여 테스트하기 어렵다.
- 결국 테스트하려면 setter를 추가로 생성..그럴 바엔 setter주입이..
- DI 프레임워크가 없으면 아무 것도 못한다.
- 테스트 코드에서는 써도 된다.
- 코드가 간결하지만, 외부에서 변경이 불가능하여 테스트하기 어렵다.
@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;
}
}
- 아무 메서드에 autowired주입 할 수 있다
- 수정자 주입이랑 다른게 거의 없다
- 특징
- 한번에 여러 필드를 주입
- 잘 사용 안됨
DI는 스프링 컨테이너가 관리하는 spring bean에서만 동작 빈이 아닌 곳에서 autowired코드 적용해도 아무 기능 안함
주입할 스프링 빈이 없어도 동작해야할 때가 있다.
@Autowired(required = false)
@Nullable
Optional
과거에는 setter주입, 필드 주입을 많이 사용했지만 최근에는 생성자 주입을 권장
- “불변”
- 대부분의 DI는 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경하지 않는다. → 오히려 애플리케이션 종료시점까지 변하면 안된다.
- setter주입을 사용하면 setXxx메소드를 public으로 열어두기에 누군가 실수로 변경할 수 있다.
- 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. → 불변
- “누락”
- 프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우
@Test
void createOrder(){
MemoryMemberRepository memberRepository = new MemoryMemberRepository();
memberRepository.save(new Member(1L, "name", Grade.VIP));
OrderServiceImpl orderService = new OrderServiceImpl(memberRepository, new FixDiscountPolicy());
Order order = orderService.createOrder(1L, "itemA", 10000);
assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
생성자 주입을 사용하면 final 사용 가능 → 값이 설정되지 않는 오류를 컴파일 시점에서 막아줌
컴파일 오류는 세상에서 가장 빠르고 좋은 오류
💡 항상 생성자 주입을 선택, 필요하면 가끔 setter 주입, 필드주입은 사용하지 말자개발할 때는 대부분 불변, 생성자에 final 사용
그런데 필드 주입처럼 편리하게 못 하나?
@Component
@RequiredArgsConstructor
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;
// }
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
//OrderService 입장에선 할인에 대해서는 난 몰라. discountPolicy 너가 알아서 해줘
return new Order(memberId, itemName, itemPrice, discountPrice);
}
//test 용도
public MemberRepository getMemberRepository(){
return memberRepository;
}
}
- @RequiredArgsConstructor를 추가하면 class 내의 final인자에 대한 생성자를 자동으로 만들어줌
- 컴파일 시점에서 생성자 코드를 자동으로 생성
아래의 방법으로 해결할 수 있다.
- @Autowired 필드 명 매징
- @Qualifier → @Qualifier끼리 매칭 → 빈 이름 매칭
- @Primary 사용
Autowired는 type 매칭 시도하고 여러 빈이 있으면 필드, 파라미터 이름으로 빈 이름을 추가 매칭
@Autowired 매칭 정리
- 타입 매칭
- 타입 매칭의 결과가 2개 이상일 때 필드 명, 파라미터 명으로 빈 이름 매칭
주입시 추가적인 방법을 제공하는 것, 빈 이름 변경은 아님
@Qualifier
로 주입할 때 @Qualifier("mainDiscountPolicy")
를 못찾으면?
→ 그러면 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다. 하지만 경험상 @Qualifier
는 @Qualifier
를 찾는 용도로만 사용하는게 명확하고 좋다.
여러개가 있을 때 Primary가 있으면 우선 순위 지정
Primary와 Qualifier의 우선순위 → Qualifier가 더 높다.
@Qualifier(”mainDiscountPolicy) → 여기서 문자는 컴파일 타임에서 체크가 되지 않는다.
Primary로 해결이 되면 Primary를 사용하면 되지만 그것이 아니라면 직접 만드는 것도 좋다.
- 애노테이션은 상속이라는 개념 x
- 여러 애노테이션 모아서 사용하는 기능 → 스프링
- @Qualifier 뿐만 아니라 다른 애노테이션들도 함께 조합해서 사용
- Autowired도 재정의 가능하나 뚜렷한 목적 없이 스프링이 제공하는 기능을 무분별하게 재정의하는 것은 유지 보수에 더 혼란을 준다.
의도적으로 해당 타입의 bean이 다 필요한 경우도 있음
소비자가 rate, fix를 선택할 수 있다면 spring의 전략패턴을 사용
편리한 자동 기능을 기본으로 사용하자
- 자동을 선호하는 추세이다.
- Component만 넣으면 되는 것을 Cofiguration설정 정보에 가서 Bean을 적는 과정은 번거롭다.
- 자동 빈 등록을 사용해도 OCP, DIP 지킬 수 있다.
수동 빈 등록은 언제?
-
업무 로직 빈
- 웹 지원 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 리포지토리 등
- 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경
-
기술 지원 빈
- 기술적인 문제나 공통 관심사(AOP)처리에 주로 사용
-
업무로직은 숫자가 많고, controller, service, repository 등 유사한 패턴이 있어 자동 기능 사용이 좋다.
-
기술 지원 로직은 업무 로직과 비교해 수가 적고, app 전반에 걸쳐 광범위한 영향
- 적용이 잘 되는지도 파악하기 어렵기에 이 경우 수동 빈 등록 사용이 좋다.
app 광범위하게 영향을 미치는 기술 지원 객체 → 수동 빈
설정 정보에 바로 나타나게 하는 것이 유지보수에 좋다.
비즈니스 로직 중 다형성을 적극 사용하는 경우
DiscountPolicy의 경우 여러 파일로 나뉘어 있어 하나 하나 찾아봐야 하지만, DiscountPolicyConfig로 묶여 있으면 바로 파악할 수 있다 → 특정 패키지에 같이 묶어 두는 것이 좋다. 딱 보고 이해가 되어야 한다!
@Configuration
public class DiscountPolicyConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
- 한 눈에 빈의 이름, 어떤 빈들이 주입될지 파악 가능
스프링, 스프링 부트가 자동 등록하는 빈들은 예외
스프링 부트가 아닌 내가 직접 기술 지원 객체를 bean으로 등록한다면 수동으로 등록해 명확하게 들어내는 것이 좋다.