생성자 주입
수정자 주입(setter로 주입하는 것)
필드 주입
일반 메소드 주입
생성자 주입
수정자 주입
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
필드 주입
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
일반 메소드 주입
@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;
}
}
옵션 처리
생성자 주입 선택
과거에는 수정자 주입을 썼지만, 지금은 대부분이 생성자 주입을 쓴다. 그 이유는 뭘까
불변
첫 번째는 불변이다. 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시까지 의존관계 변경할 일이 없다. 즉 불변해야 한다.
수정자 주입을 사용하면, setxxx 메소드를 public으로 열어 두어야 한다.
고로 누군가 실수로 변경할 수도 있고, 변경하면 안되는 메소드를 열어두는 것은 좋은 설계 방법이 아니다.
생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후 호출될 일이 없다. 따라서 불변하다.
누락
두 번째는 누락이다.
만약 orderService를 생성자 주입이 아닌 수정자 주입으로 만들고 테스트하면
다음과 같이 NPE가 발생한다.
이유는 createOrder를 하려면 저장할 memberRepository, discountPolicy의 값이 필요한데 그 값이 들어오지 않았다. 조금 더 이해를 쉽게 하자면
만약 다시 이렇게 생성자 주입으로 변경하면
이렇게 new OrderServiceImpl(); 에 빨간줄이 뜬다.
생성자 주입은 무조건 memberRepository, discountPolicy가 들어와야만 구현체가 생성되도록 했기 때문이다. 그런데 수정자 주입을 사용하면 이 파라미터가 없어도 OrderServiceImpl 객체가 생성되므로 memberRepository, discountPolicy를 필요로 하는 createOrder가 작동할 수가 없다.
따라서 다음과 같이 구현체 값을 파라미터로 넣어주면 테스트가 성공한다.
중요한 것은 오직 생성자 주입을 선택해야지 final 키워드를 선택할 수 있다는 것이다. final로 정해지면 값이 절대 불변하기 때문에, 생성자에서만 값을 세팅하고 절대 바꿀 수 없다는 장점이 있다. 또 개발자가 실수로 discountpolicy나 memberrepository 값을 넣는 코드를 누락해도 컴파일 시점에 오류가 나서 막아줄 수 있다. (컴파일 오류가 가장 최고)
즉, 항상 생성자 주입을 선택하라. 가끔 옵션이 필요할 때만 수정자 주입 사용
롬복과 최신 트렌드
private final MemberRepository memberRepository;
//lombok 설정 추가 시작 configurations {
compileOnly {
extendsFrom annotationProcessor
} }
//lombok 설정 추가 끝
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
//lombok 라이브러리 추가 시작
compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' //lombok 라이브러리 추가 끝
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
} }
롬복이라는 것을 사용하자. 먼저 위의존관계 세팅을 build.gradle에 넣어줘야 한다.
또 세팅의 preference plugin에서 Lombok을 깔아야 한다. 나는 미리 깔아놨다.
또 plugins의 Annotation precessors에서 enable annotation processing를 꼭 켜줘야 동작한다.
lombok을 사용하면 @Getter,@Setter 어노테이션을 추가하기만 하면 자동으로 getter,setter를 쓸 수 있다.
또 생성자 관련 어노테이션도 지원한다.
이제 OrderServiceImpl을 롬복으로 수정해보자
따라서 생성자 주입 코드를 따로 짤 필요가 없다. 이 코드와 이전 코드는 완전히 동일하다. 실제 클래스를 열어보면 알 수 있다.
최근에는 생성자를 딱 한개 두고 @Autowired를 사용하는데, @RequiredArgsConstructor가 이 기능을 전부 제공한다.
조회 빈이 2개 이상이면 문제발생
@Autowired 필드 명, @Quilfier, @Primary
@Autowired 필드 명을 매칭한다
@Qualifier -> @Qualifier끼리 매칭한다 -> 빈 이름 매칭
@Primary 사용
@Autowired 필드 명을 매칭한다
예를 들어
@Autowired
private DiscountPolicy discountPolicy
@Autowired
private DiscountPolicy rateDiscountPolicy
@Autowired 정리
1. 타입 매칭
2. 타입 매칭의 결과가 2개 이상일 때 필드 명, 파라미터 명으로 빈 이름 매칭
@Qualifier -> @Qualifier끼리 매칭한다
@Bean
@Qualifier("mainDiscountPolicy")
public DiscountPolicy discountPolicy() {
return new ...
}
@Qualifier 정리
1. @Qualifier끼리 매칭
2. 그래도 안되면 빈 이름 매칭
3. 그래도 안되면 NoSuchBeanDefinitionException 예외 발생
@Primary 사용
애노테이션 직접 만들기
OrderServiceImpl에도 넣어주면 정상적으로 RateDiscountPolicy가 주입된다.
어노테이션은 상속 개념이 없고, 이를 모으는 것을 스프링이 지원하는 것이므로, 스프링에서 이처럼 어노테이션을 모아 사용할 수 있다.
조회한 빈이 모두 필요할 때 List,Map
로직 분석
Map<String, DiscountPolicy>
List<DiscountPolicy>
자동, 수동의 올바른 실무 운영 기준
업무 로직 빈: 웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리등이 모두 업무 로직이다. 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경된다.
-> 업무 요구사항이 들어오면 고쳐야 되는 로직들
기술 지원 빈: 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용된다. 데이터베이스 연결이나, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들이다.
업무 로직은 숫자도 많고 유사한 패턴이 많아 준비된 자동 기능을 적극 사용하는 것이 좋다.
기술 지원 로직은 업무 로직에 비해 그 수가 적고 애플리케이션 전체에 걸쳐 광범위하게 영향을 주므로 문제가 발생했을 때 어디가 문제인지 잘 모른다.
그래서 기술 지원 로직은 수동 빈 등록을 통해 설정 정보에 명확하게 들어내는 것이 유지보수에 좋음
또 비즈니스 로직 중에서 다형성을 적극 활용할 때, 예를 들어 DiscountPolicy에 무엇이 들어오는 지 확인하고 싶을 때, 어떤 빈들이 들어올 수 있는지 Appconfig 만들 때 처럼 수동으로 만들어주는게 한눈에 파악하기 좋다. 협업할때 남의 코드를 볼 때 어떤 빈들이 주입되는지 볼 수 있기 때문이다.
이런 경우 만약 자동으로 하면 특정 패키지에 주입 빈들을 같이 묶어 놓는게 좋다
참고로 스프링과 스프링 부트가 자동으로 등록하는 수많은 빈들은 예외다.