스프링 핵심원리-기본편 (김영한님) 수업을 듣고 정리한 글입니다.
의존관계 주입
- 생성자 주입
- 수정자 주입(setter)
- 필드 주입
- 일반 메서드 주입
1. 스프링 빈 생성 후 => 2. 의존관계가 주입된다(스프링 컨테이너가 관리하는 스프링 빈이어야 동작).
생성자 주입
생성자를 통해서 의존관계를 주입(@Autowired)받는 방법
=> 생성자 호출 시점에 딱 1번만 호출된다.
불변, 필수 의존관계에 사용(배우가 바뀌지 않는것처럼)
생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입된다.(스프링 빈에만 해당)
수정자 주입
setter라고 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입(@Autowired)
=> 선택, 변경 가능성이 있는 의존관계에 사용된다. (변수의 final 속성은 지우자.)
실행해 보면 값이 잘 들어오는 것을 알 수 있다.(잘 호출되었다.)
* 수정자 주입에 @Autowired를 붙이지 않으면 스프링이 초기에 함수를 실행시키지 않기 때문에,
함수를 따로 호출하지 않는 한 출력값도 없다.
* 생성자 주입은 주입대상이 없을 때 오류를 발생시킨다. (파라미터 값이 없을 때)
* 수정자주입은 파라미터가 빈에 등록되지 않아 주입할 대상이 없을 때도 가능하다(선택적). => @Autowired(required=false)를 붙여주면 주입대상이 없을 때도 동작한다.
생성자 주입과 수정자 주입의 호출 순서
스프링 라이프 사이클은 크게 두 단계인데, 빈 등록 => 의존관계 주입이다.
빈 등록 시 생성자가 호출되기 때문에 생성자 주입이 먼저 호출이 되고 => 그 후에 수정자 주입이 호출된다(의존관계 주입 단계)
결과
생성자 주입과 수정자 주입을 함께 쓸 필요는 없다. 둘 다 의존관계를 주입하는 같은 결과를 가져오므로
생성자 주입이나 수정자 주입 둘 중 하나만 쓰자. 😁
필드 주입
필드에 바로 주입하는 방법.
MemberRepository가 잘 주입되었는지 확인해 보자.(memberRepository 값 출력되는지)
결과
필드 주입은 사용이 아주 간단하지만 외부에서 변경이 불가능해서 테스트하기 힘들다...
또한 DI 프레임워크가 없으면 아무것도 할 수 없다.(setter 같은 거)
그래서 사용하지 않는 것을 추천한다! (테스트 코드(@SpringBootTest)나 @Configuration 같은 곳에서만 특별 용도로 사용하기)
예를 들어 테스트 시 위의 OrderServiceImpl에서 사용하는 MemberRepository를 더미 데이터등으로 바꿔서 테스트하고 싶다고 가정하자.
하지만 필드 주입은 외부에서 변경이 어려우므로 바꿀 수가 없다. (생성자를 호출할 수도 없고, setter도 없기 때문)
필드 주입 - 테스트 코드
순수 자바코드로 테스트를 만든다고 가정하자.(스프링 띄우지않고)
테스트를 실행해 보면 nullpointException이 뜬다.
그 이유는 여기 OrderServiceImpl의 createOrder 함수에서 memberRepository와 discountPolicy가 사용되는데,
이 테스트코드는 스프링 없이 순수하게 돌리므로 new OrderServiceImpl() 객체 생성시에 의존관계가 아무것도 주입되지않는다.
그러므로 memberRepository와 discountPolicy를 주입해야 하는데, 주입할 방법이 없다.
주입하려면 새로 setter를 만들어주어야 한다.
setter 만들기
그 후 주입이 가능하다.
이렇게 필드 주입을 하면 setter를 따로 만들어줘야 하는 상황이 발생한다..
그럴 거면 수정자 주입(setter)을 처음부터 만드는 게...
그러니까 필드 주입은 쓰지 말자!
일반 메서드 주입
일반 메서드를 통해 주입받기
한 번에 여러 필드를 주입받을 수 있다.
그러나 일반적으로 잘 사용하지 않는다.
옵션 처리
주입대상(주입할 스프링 빈)이 없어도 동작해야 할 때가 있다.
@Autowired는 기본이 required=true이므로 주입대상이 없으면 오류가 난다.
주입 대상이 없을 때 옵션으로 처리하는 방법
- @Autowired(required=false) : 주입대상이 없으면 메서드가 호출이 안된다.
- org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.
- Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력된다.
결과
생성자 주입시 특정 필드에 @Nullable과 Optional 사용가능하다.
생성자 주입을 선택해야하는 이유
- 불변
- 대부분 의존관계 주입 후 변경할 일은 없기 때문에 생성자 주입을 사용하여 수정될 수 있는 가능성을 없애고 불변하게 설계할 수 있다.
- 누락
- set을 이용한 수정자 주입의 경우에 의존관계 주입을 누락할 수 있다. 테스트시 코드를 까서 일일히 어떤 의존관계를 주입해야하는지 확인해야한다.
- 생성자 주입은 생성시에 의존관계를 주입하므로 IDE에서 바로 어떤값을 주입해야하는지 알 수 있다.(누락 가능성 적음) 또한 누락했을때 컴파일 오류가 발생한다.
- final 키워드
- 필드에 final 키워드 사용시 필드에서 값을 할당하거나 생성자로만 값을 할당할 수 있다.
- 생성자 주입할때 의존관계를 누락하는 실수를 방지할 수 있다.(빨간줄로 컴파일오류)
생성자 주입을 항상 선택하고, 가끔 옵션이 필요하면 수정자 주입을 선택하자. 필드 주입은 선택X
롬복 적용방법
build.gradle
롬복 사용예시 - @Getter, @Setter
lombok을 사용하면 따로 getter,setter 만들어줄 필요없이 lombok 불러와서 써주면 된다.(helloLombok.getName, helloLombok.setName)
롬복 사용예시 - @RequiredArgsConstructor
롬복 사용X
롬복 - @RequiredArgsConstructor 사용
@RequiredArgsConstructor는 final이 붙은 필드에 대해 생성자를 통해 의존관계를 주입해준다. (자동으로 생성자 만들고 의존관계 주입 !! 우리가 코드 작성할 필요X)
command+f12를 이용하면 함수의 메소드와 필드 리스트를 볼 수 있는데,
의존관계가 주입된 생성자가 lombok에 의해 자동으로 만들어진 것을 볼 수 있다.
즉,
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
이 코드를 작성하지 않아도 된다.
(참고: 생성자가 하나있으면 @Autowired 생략해도 자동주입된다.)
필드에 final 붙이고 애노테이션 하나(@RequiredArgsConstructor)만 쓰면 끝!!
조회시 해당 빈이 2개 이상일때
부모로 빈을 조회시 동시에 두개 이상의 빈이 등록되어있을때
ex ) DiscountPolicy 조회시 FixDiscountPolicy와 RateDiscountPolicy 둘 다 Component로 등록되어있다면
=> 오류를 발생시킨다.
둘 다 컴포넌트로 등록되어있다면,
ac.getBean(OrderServiceImpl.class) 에 대해 여러 빈이 조회되므로 하나를 선택할 수 없어 오류를 발생시킨다.
하위 타입으로 빈을 지정해도 되지만 그렇게 되면 DIP를 위배하여 유연성이 떨어진다. 그리고 이름만 다르고 완전히 똑같은 타입의 스프링 빈이 2개 있을때 해결되지않는다.
해결방법
- @Autowired 필드명
- @Qualifier
- @Primary
@Autowired 필드명 매칭
@Autowired는 빈 조회시 1. 타입 매칭을 시도하고, 여러 빈이 조회되면 2. 필드, 파라미터 이름을 추가 매칭하여 결과를 찾는다.
* 필드 주입일때는 한줄로 작성하면 된다.
@Qualifier
@Qualifier는 추가 구분자를 붙여준다. @Qualifier("추가별명")
생성자 자동 주입 예시
수정자 자동 주입 예시
@Qualifier("mainDiscountPolicy")에서 @Qualifier가 mainDiscountPolicy를 못찾는다면 mainDiscountPolicy라는 이름의 빈을 찾는다. (그 이후로도 못찾으면 NoSuchBeanDefinitionException 발생한다.)
하지만 @Qualifier는 추가 구분자를 찾는 용도로만 쓰이는게 헷갈리지않고 좋다.
빈 등록시 @Qualifier
@Primary 사용
우선순위를 정하는 방법이다. 여러 빈이 매칭되었을때 @Primary가 우선권을 가진다.
@Primary vs @Qualifier
@Primary는 한번 지정해두면 따로 명시하지않아도 특정 빈으로만 조회되지만, @Qualifier는 주입시마다 @Qualifier를 붙여주어야한다.
활용
메인 데이터베이스와 서브 데이터베이스가 존재할때, @Primary는 메인 데이터베이스로 설정하여 편리하게 조회하고, 서브 데이터베이스는 @Qualifier를 설정하여 조회하도록 한다.
우선순위
@Primary는 기본값처럼 동작하고 @Qualifier는 상세하게 동작한다. 스프링은 자동보다는 수동이, 넓은 범위보다는 좁은 범위의 선택권이 우선순위가 높기때문에 @Primary보다 @Qualifier가 우선권이 높다.
애노테이션 직접 만들기
@Qualifier("mainDiscountPolicy")를 사용하면 내가 @Qualifier("mainnDiscountPolicy")처럼 오타를 입력해도 컴파일타임에 오류가 확인되지 않는다.(런타임에 오류 확인 가능- 작동해봐야 안다.)
그러므로 애노테이션을 직접 만들면 오타를 내도 컴파일 타임에 바로 알 수 있다.
@Qualifier의 Annotation을 따라 붙인다.
command+o: 파일 찾기
@MainDiscountPolicy 가져다 쓰기
이렇게 애노테이션으로 쓰면 오타의 경우 바로 빨간색으로 뜨기때문에 금방 오류를 잡을 수 있다.
스프링은 여러 애노테이션을 조합하여 사용할 수 있도록 한다. 무분별한 애노테이션 제작은 유지보수에 혼란을 줄 수 있다. @Qualifier 이름쓸때 등등 적절한 상황에서만 쓰자.
조회한 빈이 모두 필요할 때 List, Map
아래의 코드는 policyMap과 policies를 잘 가져올까?
수행결과
잘 수행되지 않는 이유는 스프링이 DiscountPolicy를 가져오지 않았기 때문이다. @ComponentScan을 통해 우선 빈들을 등록해야한다.
@ComponentScan이 존재하는 클래스를 스프링 빈으로 등록한다. =>AutoAppConfig.java
AutoAppConfig를 빈으로 추가 등록
수행결과
잘 가져온다.
DiscountPolicy에 RateDiscountPolicy와 FixDiscountPolicy가 있을때,
사용자가 비율(rateDiscountPolicy)로 할지 고정할인(fixDiscountPolicy)으로 할지 고를 수 있게 하고싶다면?
1. DiscountService를 만든다. DiscountPolicy를 주입한다.
2. discount함수에서 discountCode(할인정책 이름)에 따라 할인 정책을 가져와 할인값을 반환한다.
=> Map<String, DiscountPolicy> policyMap : 스프링 빈 이름 - 이름으로 조회된 빈
=> List<DiscountPolicy> : DiscountPolicy 타입으로 조회한 모든 스프링 빈 담기
3. assertThat으로 올바르게 할인되었는지 확인한다.
스프링 컨테이너를 생성하면서 스프링 빈 등록하기
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
스프링 컨테이너는 생성자에 클래스 정보를 받는데, 해당 클래스들은 스프링 빈으로 자동 등록된다.
1. new AnnotationConfigApplicationContext로 스프링 컨테이너를 생성한다.
2. AutoAppConfig.class, DiscountService.class를 스프링 빈으로 등록한다.
자동, 수동의 올바른 실무 운영 기준
컴포넌트 스캔, 자동 주입 vs 설정정보, 수동 주입
@Component만 넣어주고 컴포넌트 스캔 vs @Configuration 설정파일에서 @Bean적고 객체를 생성하여 주입 대상 적기
자동 빈 등록 기능은 편리하기때문에 기본으로 사용하자. - OCP,DIP를 지킬 수 있다.(@Primary만 붙여주면 됨)
수동 빈 등록은
- 기술 지원 빈(업무 로직 빈과 달리 기술적인 문제나 공통 관심사를 처리할때 사용되며 하부기술이나 공통기술이다. 널리 쓰인다.)을 만들때 의존 관계를 명확하게 파악하기 위해 사용하자. (문제 발생 원인을 찾기 쉽도록)
- 다형성을 활용할때 (fixDiscountPolicy, rateDiscountPolicy) 빠르게 관계를 파악하기위해 설정파일을 만들어놓으면 좋다.
'Spring > 스프링 핵심 원리' 카테고리의 다른 글
스프링 : 빈 스코프 (0) | 2023.03.24 |
---|---|
빈 생명주기 콜백 (0) | 2023.02.28 |
컴포넌트 스캔 (2) | 2023.02.23 |
싱글톤 컨테이너 (0) | 2023.02.13 |
스프링으로 전환하기, 스프링 컨테이너와 스프링 빈 (0) | 2023.02.13 |