의존관계 주입하는 다양한 방법이 있다
생성자 주입, setter 주입, 필드 주입, 일반 메서드 주입 - 거의 생성자 주입을쓰고, setter도 가끔 쓴다.
생성자 주입
참고로 생성자 주입은 지금껏 우리가 한 행위다.
생성자 호출 시점에 딱 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;
}
}
불변으로 만들어둬야, 어차피 바꿀 일 없는 것 임의로 바꿀 수 없게끔 지정해둬야 한다.
생성자가 만약 두개 이상 있으면 무조건 @Autowired로 지정을 해줘야하지만, 하나만 있으면 생략 가능하다. 그러나 어지간하면 지정을 해주는 버릇을 가지자.
수정자 주입
getter와 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;
}
}
* 자바빈 프로퍼티 - 자바의 규정(?) - 에 의해 필드의 값을 직접 변경하는 대신 set, get 메서드를 활용한다.
생성자 주입을 써야 하는 이유
- 불변: 한마디로, 대부분의 주입은 한번 일어나면 앱 종료할 때까지 의존관계를 변경할 일이 없다고 한다. 오히려 아예 변하면 안되는 경우가 대부분이다. 그렇기에 생성자 주입을 써서 아예 변화를 할 수 없게 하는 게 좋다.
- 누락: 생성자 주입의 경우 생성자를 만들 때에 파라미터를 넘겨주는 방식으로 진행하기 때문에, 필요한 객체를 누락할 가능성이 없다.
- final 키워드: JAVA에서 쓰는 final로 설정을 하면, 정의 시에 = 뭐시기 해주거나 아니면 생성자에서만 수정이 가능하다. 그래서 만약 누락이 되거나 변경이 되는 경우, compile 오류가 뜨면서 일침을 가해준다. 강사님은 compile 오류가 세상에서 가장 빠르고 좋은 오류라고 강조하신다.
옵션 처리
@Autowired 는 디폴트가 required=true로 되어 있어서 자동 주입할 대상이 없으면 오류가 발생한다.
그때는 required=false로 붙여주면 된다. 그렇게하면 만약 주입 대상 없으면 아예 안 부른다.
그게 아니면 null 혹은 Optional.empty 호출해도 된다.
//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);
}
그치만 실무에서 어떤 경우에 이걸 쓸지는 아직 잘 모르겠다.
롬복, 최신 트렌드
생성자 주입이 기본이 되어야 하지만, 생각보다 주입 시에 쓸 코드가 많다. 그래서 이걸 간소화하는 걸 또 만들어줬다. 그 이름이 바로 "롬복"이라는 라이브러리다. initializr 쓸 때 추가해줬으면 되는데, 우리는 안 해줬으니 build.gradle에 아래의 코드들을 통해 추가해줘야 한다.
추가로, Preferences -> Annotation Processors 검색 후 Enable annotation processing 체크되게 해야한다.
마지막으로 코끼리 눌러줘야 하고.
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
//lombok 설정 추가 시작
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
//lombok 설정 추가 끝
repositories {
mavenCentral()
}
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'
}
}
test {
useJUnitPlatform()
}
예를 들어 HelloLombok이라는 클래스를 만들고, 내부에 name과 age라는 변수를 두면, 저렇게 위에 @Getter, @Setter 애노테이션만 추가해줘도, 알아서 생성자에 딸린 프로퍼티에 대한 getter와 setter를 만들어준다.
package hello.core.order;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class HelloLombok {
private String name;
private int age;
public static void main(String[] args) {
HelloLombok helloLombok = new HelloLombok();
helloLombok.setName("king");
System.out.println(helloLombok.getName());
}
}
예를 들어 @RequiredArgsContstructor 같은 애노테이션은, 기존의 생성자 만드는 코드를 대체해준다 그러니까,
@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;
}
}
// 기존의 코드가 아래처럼 변한다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
구현체의 생성자 자체를 만들 필요가 없어지는 것이다. 프로퍼티들을 저장해두면, 그걸 필수로 파라미터로 포함하는 생성자를 자체적으로 만들어줘버린다,,,
킹-토와이어드보다도 더 깔끔한 갓-복이다. 실무에서는 이걸 쓴다고 한다. 진짜 어쩌다 생성자가 필요한 경우 킹-토와이어드를 쓰면 되고.
조회할 빈이 2개 이상인 경우
기본적으로 Autowired든, 그 기저의 getBean이든, 타입을 기준으로 빈 조회를 한다. 그러니까 같은 타입의 빈이 여러개인 경우 문제가 발생한다. 이때,
1 하위타입으로 지정해주면 문제는 해결되지만 그렇게 하면 구현체를 부르는 것이므로 DIP를 위반한다.
2 만약 수동으로 빈 만들어주면 문제가 해결되겠지만, 더 좋은 다른 방법들이 있다.
- @Autowired 필드명
타입 매칭을 시도하고 나서 여러개가 있는 경우 추가로 매칭하는 기능이 Autowired에게는 존재한다!!
그러니까 예를 들어, 다음과 같다.
- @Qualifier
추가 구분자. 추가적인 이름을 붙일 수 있다. 약간 subname 같은 느낌쓰.
- @Primary
이게 편해서 자주 사용한다고 한다. 여러 빈이 매칭되는 경우 @Primary가 우선권을 가진다.
ex) 메인 DB는 9할, 보조 DB가 1할이면 메인을 쓴다고 봐도 되므로 @Primary 써버리면 된다.
애노테이션을 내가 만들어주기
이런 새로운 annotation을 만들어줄 수 있다. 그러면 기존의 @Qualifier("mainDiscountPolicy") 이걸 깔쌈하게
이렇게 된다.
특정한 빈 뿐 아니라 모든 빈이 다 필요할 때 - List, Map
와,,, 진짜 신기하다. 그러니까 Map에다가 불러주면, 저절로 key는 이름, 값은 해당 빈이 들어간다.
아, Spring은 자동으로 키값에 해당 빈의 이름, 값에 해당 빈을 넣어준다.
값에 빈이 들어간다는 게 핵심이다. 그러면 이제 키로 이름을 불러주면 해당 빈을 갖다가 쓸 수 있다는 것.
자동과 수동 빈 - 자동을 기본으로 쓰자, 그러나 수동도 필요하다
'백엔드 잡학사전' 카테고리의 다른 글
[스프링 핵심] 빈 스코프 & 프로토타입 빈 (0) | 2024.08.20 |
---|---|
[스프링 핵심] 빈 생명주기 콜백 (0) | 2024.08.20 |
[스프링 핵심] 컴포넌트 스캔 & 의존관계 자동 주입 (0) | 2024.08.18 |
[스프링 핵심] 싱글톤 컨테이너 (0) | 2024.08.18 |
[스프링 핵심] 스프링 컨테이너와 스프링 빈 (0) | 2024.08.16 |