강의 링크
다양한 의존관계 주입 방법
- 생성자 주입
- 수정자 주입(setter 주입)
- 필드 주입
- 일반 메서드 주입
생성자 주입
생성자 자동 생성 단축키 : Alt+Insert (Windows/Linux)
- 생성자를 통해 의존관계를 주입받는 방법.
- 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
- “불변, 필수” 의존관계에 사용된다. (값 세팅을 고정할 수 있음)
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
- 만약 여러개의 생성자가 존재한다면, @Autowired를 지정해주어야한다.
- @Component가 지정되어있고 생성자가 하나라면, 하나뿐인 생성자에 @Autowired가 자동으로 지정된다.
- 좋은 개발 방법은 “제약”에 있다.
- 누구나 어디서든 수정할 수 있다면, 그 코드가 어디서 수정되었는지 찾기 쉽지않다.
- 공연 중간에 배우를 바꾸지 않도록 제약을 설정한다고 생각하자.
- 누구나 어디서든 수정할 수 있다면, 그 코드가 어디서 수정되었는지 찾기 쉽지않다.
private final MemberRepository memberRepository ;
private final DiscountPolicy discountPolicy;
- 변하지 않고 필수 사용하도록 final로 선언 후 의존관계를 주입한다.
수정자 주입
- setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법.
- “선택, 변경” 가능성이 있는 의존관계에 사용.
- 자바빈 프로퍼티 규약의 수정자 메서드를 사용하는 방식.
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
- setter에 @Autowired를 사용하면 의존관계를 주입할 수 있다. (@Autowired가 없으면 의존관계주입을 사용하는데 사용되지 않는 것으로 스프링 부트에서 인식함.)
- 스프링 컨테이너의 두 단계의 라이프 사이클
- 스프링 빈 등록 (전부 등록) → 등록 시 생성자 주입은 자동적으로 일어남.
2. @Autowired가 있는 경우 의존관계를 주입한다. → 수정자는 이 단계에서 주입이 일어남.
![Untitled 1](https://github.com/Jedo0224/Jedo0224.github.io/assets/90050514/a803cb7e-56d7-4f96-8bc0-5db2a5dfeb3a)
- 따라서 순서는 OrderServiceImpl → (setMemberRepository → setDiscountPolicy) 순으로 주입.
필드 주입
@Autowired private MemberRepository memberRepository ;
@Autowired private DiscountPolicy discountPolicy;
- 간결하다. 하지만.. → Field injection is not recommended
- 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명적인 단점이 존재한다.
- 의존성 주입을 숨김: 필드 주입은 클래스의 필드에 바로 의존성을 주입하기 때문에 의존성이 어디서 주입되는지 명확하게 드러나지 않는다.
- 의존성 주입을 변경할 수 없음: 필드 주입은 생성자 주입과 달리 의존성을 변경할 수 있는 방법을 제공하지 않는다
- 테스트를 위해 어쩔 수 없이 생성자 또는 setter를 추가해줘야하는 번거로움
- → 따라서 테스트 및 테스트 확장이 어려움
- 스프링 컨테이너가 아닌 순수한 자바로 테스트할 수 있는 방법이 없다..*
- 순수한 자바로 테스트하는 경우?
- 단위 테스트(unit test)를 의미한다. → 프레임워크나 컨테이너의 도움 없이 테스트 코드만으로 클래스의 기능을 검증한다.
- 사용 가능한 경우는? (하지만 가급적 쓰지 말자.)
- @SpringBootTest를 사용한 테스트를 진행할 때 (스프링 컨테이너에서 테스트할 때)
- @Configuation을 사용하는 경우(AppConfig 같이 의존성 주입시 최상위에 위에 위치하는 경우.. )
- 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명적인 단점이 존재한다.
옵션 처리
주입할 스프링 빈이 없어도 동작해야 할 때가 있다.
언제?
선택적 의존성: 어플리케이션이 특정 기능을 제공하는 빈을 필요로 하지만, 그 기능이 항상 활성화되어 있지 않을 경우.
- 어떤 기능이 특정 프로필이나 설정에 따라 사용되거나 사용되지 않는 경우.
- 다양한 유형의 결제 시스템을 지원하도록 설계되었을 때, 특정 국가나 시장에 따라 결제 시스템을 선택적으로 활성화.
환경별 설정: 개발 환경과 실제 운영 환경 사이에서 사용되는 빈이 다를 수 있다.
- 로컬 개발 환경에서는 특정 서비스의 가벼운 버전을 사용하고, 운영 환경에서는 전체 기능을 갖춘 서비스를 사용하는 경우.
자동 주입 대상을 옵션으로 처리하는 방법
- @Autowired(required = false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출이 안된다.
- org.springframework.lang.@Nullable : 자동 주입 대상이 없으면 null이 입력된다.
- Optional<> : 대상이 없으면 Optional.empty가 입력됨.
//호출 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
System.out.println("setNoBean1 = " + member);
}
//null 호출
@Autowired
public void setNoBean2(@Nullable Member member) {
System.out.println("setNoBean2 = " + member);
}
//Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
System.out.println("setNoBean3 = " + member);
}
- 결과 :setNoBean2 = null
setNoBean3 = Optional.empty - setNoBean1은 호출이 아예 안됨.
생성자 주입을 선택하라!
- 불변
- 대부분 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변!)
- 수정자 주입은 public으로 열어 놓기 때문에 변경이 가능하다.
- 누군가 실수로 변경할 수도 있고, 변경하면 안되는 메서드를 열어두는 것은 위험
- 생성자는 객체 생성 시 단 1번만 호출!
- 누락
- 프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우 해당 객체가 누락되어도(지금 실재로 존재하지 않아 가짜 객체를 사용하더라도) 해당 기능 및 동작을 테스트해볼 수 있다.
class OrderServiceImplTest {
@Test
void createOrder1() {
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.createOrder(1L,"itemA", 10000);
}
@Test
void createOrder2() {
MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository();
memoryMemberRepository.save(new Member(1L, "name", Grade.VIP));
OrderServiceImpl orderService = new OrderServiceImpl(memoryMemberRepository, new FixDiscountPolicy());
orderService.createOrder(1L,"itemA", 10000);
}
}
- 내가 단위 테스트를 진행하려는데 생성자 주입을 사용하지 않는다면.
- 어떤 의존성을 주입해야하는지 모를 수 있다
- 내가 새롭게 만든 더미클래스를 주입할 경우 → setter를 사용하는 건 위험 할 수 있다.
- 생성자 주입을 사용한다면.
개발자는 쉽게 의존 관계를 파악할 수 있으며, 라이브러리를 이용해 mock 객체를 만들며 쉽게 테스트가 가능하다.
```java
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository ;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
}
```
의존관계가 누락되었을 경우 → 개발자는 컴파일 시점에서 바로 이를 확인 할 수 있게 된다.
롬복 사용
@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;
// }
@RequiredArgsConstructor : arg가 요구되는 생성자를 자동으로 생성하고 의존성을 자동으로 주입함.
조회 빈이 2개 이상일 경우 해결 방법
- @Autowireed 필드 명 매칭
- @Qualifier → @Quilifier끼리 매칭 → 빈 이름 매칭
- @Primary 사용
@Autowired 필드 명 매칭
Autowired는 Type으로 매칭을 시도한다 → 여래 개라면 → 파라미터 이름으로 빈 이름을 추가로 매칭한다.
- MemberRepository memberRepository Or DiscountPolicy rateDiscountPolicy 로 구별
- DiscountPolicy rateDiscountPolicy : rateDiscountPolicy로 구별해서 의존성을 준다.
private final MemberRepository memberRepository ;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy ) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Qualifier를 사용
- 주입시 @Qualifier를 붙여주고 등록한 이름을 적어준다.
@Component
@Qualifier("mainDiscountPoilcy")
public class RateDiscountPolicy implements DiscountPolicy {
- 생성자에 해당하는 의존성을 @Qualifier로 주입한다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,@Qualifier("mainDiscountPoilcy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Primary 사용
메인데이터 베이스가 있고 보조 데이터 베이스가 있다고 가정한다면 → 메인데이터 베이스에 Primary를 부여하고 나머지에는 @Qualifier를 사용하도록 팀에서 룰을 지정할 수 있음.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
스프링은 제한이 있을 수록 권한을 더 준다. → 따라서 Primary보다 Qualifier가 더 우선순위가 있는데 이는 사용자가 수동으로 이름을 설정해 주입하는 것이기 때문에 더 제한이 있다고 판단하기 떄문이다.
어노테이션 직접 만들기
- Qualifier에 이름을 등록할 경우, 문자열이기 때문에 컴파일 시 Qualifier 이름을 잘못 적었더라도 알 수가 없다.
- 따라서 어노테이션을 직접 만들어서 사용할 수 있다.
- 어노테이션 생성
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Qualifier("mainDiscountPolicy") public @interface MainDiscountPolicy { }
- 어노테이션 사용
@Autowired public OrderServiceImpl(MemberRepository memberRepository,@MainDiscountPolicy DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; }
@Component @MainDiscountPolicy public class RateDiscountPolicy implements DiscountPolicy {
조회한 빈이 모두 필요할 때, List, Map
의도적으로 해당 타입의 스프링 빈이 다 필요한 경우가 어떤 경우일까?
- 사용자가 할인의 종류를 선택할 수 있다(rate, fix) → 할인 전략 패턴을 간단하게 구현할 수 있다.
@Test
void findAllBean() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class,DiscountService.class);
}
static class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
System.out.println("policyMap = " + policyMap);
System.out.println("policies = " + policies);
}
public int discount(Member member, int price , String discountCode){
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member,price);
}
}
- 모든 빈을 조회 할 수 있으며, 이제 필요한 bean을 사용할 수 있음
- 예시 (rate Or fix)
@Test void findAllBean() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class,DiscountService.class); //디스카운트 폴리시 생성 DiscountService discountService = ac.getBean(DiscountService.class); // 멤버에 값 넣기 Member member = new Member(1L, "userA" , Grade.VIP); //디스카운드 함수 만든것을 적용하기 int discountPrice = discountService.discount(member, 10000,"fixDiscountPolicy"); // 멤버에 값 넣기 Assertions.assertInstanceOf(DiscountService.class, discountService); Assertions.assertEquals(1000,discountPrice); int rateDiscountPrice = discountService.discount(member, 20000 , "rateDiscountPolicy"); Assertions.assertEquals(2000,rateDiscountPrice); }
- “로직 분석”*
- DiscountService은 discountPolicy를 주입받는다 (이때 rate, fixDiscountPolicty를 주입을 Map으로 주입받음)
- 이후 Arg에 따라 해당 가격에 rate 또는 fix를 부여한다.
스프링으로 모든 빈을 조회하고 다형성을 바탕으로 전략적으로 로직을 적용할 수 있다.
자동, 수동의 올바른 실무 운영 기준
편리한 자동 기능을 기본으로 사용하는 것을 추천함.
- 설정 정보가 커지면 설정 정보를 관리하는 것 자체가 부담이 된다.
- 결정적으로 자동 빈 등록을 사용해도 OCP, DIP를 지킬 수 있다.
@Controller , @Service , @Repository 처럼 계층에 맞추어 일반적인 애플리케이션 로직을 자동으로 스캔할 수 있도록 지원하고 있음.
그러면 수동 빈은 언제 사용할까?
- 애플리케이션을 업무로직 / 기술 지원 로직으로 나눈다면 → 기술 지원 로직일 경우 수동 빈 등록을 사용.
- 웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포 지토리등이 모두 업무 로직.
- 기술 지원 빈*:
- 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용된다. 데이터베이스 연결이나, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술을 뜻함.
- 프로젝트에서 라이브러리를 사용해서 로직에 적용하거나, 따로 CORS를 적용시켜주어야 했을 경우 수동 빈으로 처리했다.
- 기술 지원 로직은 적용 이 잘 되고 있는지 아닌지 조차 파악하기 어려운 경우가 많다. 그래서 수동 빈 등록을 사용해서 명확하게 드러내는 것이 좋다.
- 업무 로직 빈 :
- 다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민해보며 해당 로직과 관련된 클래스들은 패키지로 묶어 유지보수하기 편하게 만들고 싶을 때 사용한다.이 코드만 확인했을 경우 DiscountPolicy에 어떤 빈이 등록되어 있는지 확인하기 쉽지않다.
- 하지만 이처럼 수동으로 등록하여 클래스에 정리해 놓았을 경우. 개발자는 쉽게 빈 등록 정보를 알 수 있다. → 좋은 추상화가 가능함.
@Configuration public class DiscountPolicyConfig { @Bean public DiscountPolicy rateDiscountPolicy() { return new RateDiscountPolicy(); } @Bean public DiscountPolicy fixDiscountPolicy() { return new FixDiscountPolicy(); } }
static class DiscountService { private final Map<String, DiscountPolicy> policyMap; private final List<DiscountPolicy> policies; public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) { this.policyMap = policyMap; this.policies = policies; System.out.println("policyMap = " + policyMap); System.out.println("policies = " + policies); }
애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체, 비즈니스 로직 전략은 수동 빈으로 등록해 제대로 확인 할 수 있도록 !! 설정 정보에 바로 나타나게 하는 것이 유지보수 하기 좋다.