[Spring] 의존관계 자동 주입
의존관계 주입 방법 4가지
생성자 주입
수정자 주입 (setter 주입)
필드 주입
일반 메서드 주입
생성자 DI
- 생성자를 호출한 시점에서 한번만 호출되는걸 보장한다.
- 불변, 필수 의존관계에서 사용
수정자 DI
- setXXX 메서드를 작성한 후 @Autowired
- 선택, 변경 가능성이 있는 의존관계에 사용
- 선택 (required = false) - 없어도 되는 객체에 생성
필드 DI
- 생성자, 수정자 메서드를 만들지 않고 필드에서 그대로 @Autowired
- 코드가 간결하다는 장점이 있지만 외부에서 변경이 불가능해서 테스트가 불가능하다는 단점이 존재
- DI 프레임워크가 없으면 아무것도할 수 없다. 되도록이면 지향하는 것이 좋지만
- 애플리케이션과 관계 없는 테스트코드나 스프링 설정을 목적으로하는 @Configuration 같은 곳에서만 사용
일반 메서드 DI
- 생성자나 수정자 DI가 아닌 일반메서드에 자동주입하는 방법 ( 잘 사용하지 않음 )
옵션 처리
@Autowired(required = false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨
org.springframwork.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력됌
Optional<> : 자동 주입할 대상이 없으면 Optional.empty가 입력됌
생성자 주입을 선택해야 하는 이유
불변
- 대부분 의존 관계는 애플리케이션 종료 전까지 변하면 안된다.
- 수정자 주입을 사용하면, setXXX를 public으로 열어두어야 하기 때문에 누군가 실수로 변경할 수도 있고
- 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.
누락 방지
- 프레임워크 없이 순수한 자바 코드를 단위 테스트 하는 경우 수정자 주입으로 설계한 객체는 " 누락 "할 수도 있다.
- (생성자는 필수로 필요한 객체를 알려주지만, 수정자는 알려주지 않음) why? 그 객체를 생성할 당시에 필요해서!
final 키워드 사용가능 (final은 생성자나 인스턴스를 정의할 때 값을 정의해줘야함)
Lombok (라이브러리)
plugins -> Lombok 적용
Setting -> Annotation Processors -> Enble annotation processing 체크
@Setter setXXX를 자동으로 만들어줌
@Getter getXXX를 자동으로 만들어줌
@RequiredArgsConstructor 필수 생성자(final)를 만들어줌
@ToString
최신 트랜드
@Autowired는 생성자가 1개인 경우 생략이 가능하기 때문에 Lombok의 @RequiredArgsConstructor 와 함께 사용하면 간결한 코드 작성이 가능하다.
조회 빈이 2개 이상
@Autowired는 기본적으로 타입으로 조회한다.
ex) RateDiscountPolicy 와 FixDiscountPolicy 두개 다 @Component를 붙이면? 오류가난다.
이때 하위 타입으로 지정하는 것은 DIP를 위배하고 유연성이 떨어지기 때문에 다음과 같은 애너테이션을 추가로 사용한다.
@Autowired 필드 명 매칭
@Quilifier -> @Quilifier끼리 매칭 -> 빈 이름 매칭
@Primary 사용
@Autowired 필드 명 매칭
1. 타입 매칭
2. 같은 타입이 2개 이상 있을 때 필드 명, 파라미터 명으로 빈 이름 매칭
@Quilifier
1. @Quilifier("이름") 을 지정해두고 생성자에 이를 똑같이 쓰면 해당 @Quilifier끼리 매칭
2. 위에서 찾으면 필드 명, 파라미터 명으로 빈 이름 매칭
3. NoSuchBeanDefinitionException 발생
*참고* 직접 빈 등록시에도 동일하게 사용 가능
@Primary
우선 순위를 지정 하는 애너테이션
주 클래스에 지정하면 해당 구현 객체가 우선 순위를 갖는다.
@Quilifier, @Primary의 활용
메인 데이터베이스의 커넥션을 흭득하는 스프링빈이 있고, 서브 데이터베이스 커넥션의 스프링 빈이 있을때, 메인 데이터베이스 빈은 @Primary를 지정해서 편리하게 조회하고, 서브 데이터베이스 커넥션이 필요한 곳에 Quilifier을 지정하면 코드를 깔끔하게 유지 가능
우선순위 Qulifier -> Primary
애너테이션 만들기
@Quilifier ("문자열")을 하게되면 컴파일시 타입 체크가 안되는 경우가 발생한다. 이러한 경우를 방지하기 위한 방법으로 애너테이션을 만드는 방법이 있다.
package hello.core.annotation;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
예제와 같이만들면 @Quilifier 과 똑같이 동작시킨다
정의하는 애너테이션이 달라졌다고 보면된다.
자바 애너테이션에는 상속이라는 기능은 없고, 이는 스프링이 제공하는 기능인데 뚜렷한 목적 없이 이러한 기능을 무분별하게 사용하는 행동은 좋지않다.
같은 타입의 스프링 빈이 2개 이상 필요할 경우
클라이언트가 (rate, fix)를 선택할 수 있다면?
package hello.core.autowired;
import hello.core.AutoAppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Grade;
import hello.core.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
public class AllBeanTest {
@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");
assertThat(discountService).isInstanceOf(DiscountService.class);
assertThat(discountPrice).isEqualTo(1000);
}
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);
}
}
}
자동, 수동의 올바른 운영 기준
- 편리한 자동 기능을 기본으로 사용하는 것이 무난하지만 설정 정보를 기반으로 애플리케이션을 구성하는 부분과 실제 동작을 부분을 명확하게 나누는 것이 이상적이다.
수동 빈 등록을 사용하면 좋은 경우
- 애플리케이션은 크게 업무 로직과 기술 지원 로직을 나눌 수 있다.
- 업무 로직 빈 : 웹을 지원하는 컨트롤러, 핵심 비지니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리 등이 모두 업무 로직이다. 보통 비지니스 요구사항을 개발할 때 추가되거나 변경된다.
- 기술 지원 빈 : 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용된다. 데이터베이스 연결이나, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술이다.
- 업무로직은 문제가 발생해도 어떤 곳에서 문제가 발생했는지 명확하게 파악하기 쉽다. (자동)
- 기술 지원 로직은 수가 매우 적고, 애플리케이션 전반에 걸쳐서 광범위하게 영향을 미친다.
기술 지원 로직들은 가급적 수동 빈 등록을 사용해서 명확하게 들어내는 것이 좋다.
그럼 비지니스 로직(업무 로직)에서는 사용하면 안될까?
방금 예제와 같이 여러개를 받는 상황은 어떤 빈들이 주입될 지, 각 빈들의 이름은 무엇인지
정적 코드 분석에서 파악이 가능할까? (내가 작성한 코드가 아닐 경우)
이런 경우 수동 빈으로 등록하거나 특정 패키지에 같이 묶어두는 것이 좋을 수도 있다.
요약: 가독성을 신경쓰자!!!
출처: https://tan-sog.tistory.com/90
[Spring] 참고사항 - 출처
이 카테고리의 내용의 원본은 우아한형제들 최연소 기술이사 출신 김영한의 스프링 완전 정복 로드맵에 포함된 강의의 내용임을 알려드립니다. http://inflean.com/roadmaps/373
tan-sog.tistory.com