1. 다양한 의존 관계 주입
의존 관계 주입에는 크게 4가지 방법이 있다.
- 생성자 주입
- 수정자 주입 (setter 주입)
- 필드 주입
- 메서드 주입
1.1. 생성자 주입
먼저 생성자 주입 방식을 살펴보자. 생성자 주입은 아래와 같이 생성자 위에 @Autowired를 붙여 자동으로 주입하는 방식이다. 예시는 아래와 같다.
@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;
}
...
}
생성자 주입은 생성자 호출 시점에만 호출되는 것이 보장된다. 즉, 딱 한 번만 호출되기 때문에 코드만 잘짜면 이후에 이 값을 setting하지 못하게 막을 수 있다. 따라서 불변하고, 필수적인 의존관계에 사용한다.
- 불변이란 setter를 통해 이후에도 수정되지 않을 친구들을 말한다.
- 필수란 무조건 필요한 값을 의미한다.
단, 만약 생성자가 한 개이고 클래스에 @Component, @Bean과 같이 컨테이너가 관리해야하는 빈으로 인식된다면 @Autowired를 사용하지 않아도 @Autowired가 붙은 것과 동일한 효과를 부여한다. 만약 생성자가 두 개 이상이라면 무조건 @Autowired를 사용해야 한다.
1.2. 수정자 주입 (setter 주입)
수정자 주입은 필드값을 변경하는 수정자 메서드인 setter를 통해 의존관계를 주입하는 방식이다. 방식은 아래와 같다.
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;
}
...
}
위와 같이 setter 메서드에 @Autowired를 붙여주면 컨테이너가 알아서 의존성 주입을 해준다. 만약 @Autowired를 빼면 동작하지 않는다. 하지만 위와 같은 메서드는 (만들어둔 생성자 주입 방식이 있다면) 생성자 주입도 발생할 수 있다. 위 클래스(OrderServiceImpl)을 스프링 빈으로 등록하기 위해서는 생성자가 필요하고 결국 컨테이너는 생성자 주입을 먼저 수행한다. 이렇게 생성자 주입이 먼저 발생하면 이후 수정자 주입이 발생한다. 수정자 주입은 "선택, 변경" 가능성이 있는 의존 관계에 사용한다.
- MemberRepository가 스프링 빈에 등록되어 있지 않다면 생성자 주입에서는 에러가 발생하겠지만 수정자 주입의 경우 에러가 발생하지 않는다. 만약 이와 같이 선택적으로 주입하려면 @Autowired(required=false)를 사용하면 된다. (@Autowired의 경우 주입할 대상이 없으면 오류가 발생함.)
- 만약 중간에 인스턴스를 바꾸고 싶다면 외부에서 setter 메서드를 사용하면 될 것이다. 물론 그럴 일은 거의 없다. (있을 수도 있음)
- 최근에는 거의 생성자 주입을 사용하고 수정자 주입도 가끔 선택적이거나 변경 가능할 때 사용한다.
1.3. 필드 주입
필드 주입은 말 그대로 필드에서 바로 주입하는 방식이다. 방식은 아래와 같다.
public class OrderServiceImpl implements OrderService{
@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;
...
}
코드가 간결하여 좋을 것 같지만 외부에서 변경이 불가능하기 때문에 테스트하기 힘들다는 단점이 있다. 즉, 필드값을 변경할 수 없기 때문에 스프링 컨테이너가 아닌 다른 테스트 코드로 테스트하기는 힘들다. 따라서 결국 값 변경을 위해 setter를 만들어서 해결해야 한다. 이렇게 코드를 추가하는 것보단 차라리 수정자 주입이 더 나을 것이다.
- 따라서 필드 주입은 거의 사용하지 않는 것이 좋다.
- 단, 애플리케이션의 실제 코드와는 관계 없는 테스트 코드 등 특정 상황에서는 사용하는 것이 편할 때도 있다.
1.4. 일반 메서드 주입
말 그대로 아무 메서드에서나 @Autowired를 사용해 의존성을 주입할 수 있다.
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;
}
...
}
- 일반 메서드 주입은 수정자 주입과 비슷한 시기에 주입된다. 사실 수정자 주입과 동일한 대신, 여러 필드를 동시에 주입할 수 있다는 특징이 있다. 하지만 보통 잘 사용하진 않는다. 생성자 주입이나 수정자 주입을 보통 많이 사용하기 때문이다.
2. 옵션 처리
주입할 스프링 빈이 없어도 동작해야 할 때가 있는데 @Autowired만 사용하면 required 옵션의 default가 true이기 때문에 오류가 발생한다. 자동 주입 대상을 옵션으로 처리하는 방법은 다음과 같다.
- @Autowired(required=false): 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않는다.
- org.springframework.lang.@Nullable: 자동 주입할 대상이 없으면 null이 입력된다.
- Optional<>: 자동 주입 대상이 없으면 Optional.empty가 입력된다.
예시 코드는 아래와 같다.
public class AutowiredTest {
@Test
void AutowiredOption() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
}
static class TestBean {
@Autowired(required=false)
public void setNoBean1(Member noBean1) {
System.out.println("noBean1 = " + noBean1);
}
@Autowired
public void setNoBean2(@Nullable Member noBean2) {
System.out.println("noBean1 = " + noBean2);
}
@Autowired
public void setNoBean3(Optional<Member> noBean3) {
System.out.println("noBean3 = " + noBean3);
}
}
}
Member는 스프링 빈이 아니므로 해당 코드의 결과는 아래와 같다.
위와 같이 setNoBean1과 같이 @Autowired(reqired=false)를 사용하면 자동 주입할 빈이 없다면 메서드가 호출조차 되지 않는다. 대신 setNoBean2는 호출은 되고, null이 들어가는 것을 확인할 수 있고, setNoBean3역시 Optional.empty가 들어있는 것을 확인할 수 있다.
3. 생성자 주입을 선택해라!
과거에는 수정자 주입과 필드 주입을 많이 사용했지만 최근에는 스프링을 포함한 DI 프레임워크 대부분 생성자 주입을 권장한다. 생성자 주입을 많이 사용하는 이유는 다음과 같다.
먼저 "불변"과 관련된 이유다. 대부분의 의존 관계 주입은 처음 어플리케이션을 조립할 때 이미 정해진다. 즉, 한 번 의존관계 주입이 발생하면 종료시점까진 의존관계를 변경할 일이 거의 없기 때문에 생성자 주입을 사용하는 것이 좋다. (실제로 대부분의 의존관계는 애플리케션 종료전까지 변하면 안된다.) 또한 생성자 주입을 사용하지 않고 수정자 주입을 사용한다면 setter 메서드를 public으로 열어두어야 한다는 단점이 있다. 이렇게 되면 누군가 실수로 의존관계를 변경할 수도 있으며 변경하면 안되는 메서드를 열어두는 것은 좋은 설계법이 아니다.
- 또한 필드에 final을 붙일 수 있으므로 이후 바뀌지 않는 것을 보장할 수 있다는 장점이 있다. 또한 생성자에서 값이 설정되지 않는 오류를 컴파일 시점에서 잡아준다. final 키워드는 오직 생성자 주입 방식만 사용할 수 있다.
또 다른 이유는 테스트 상의 문제다. 프레임워크 없이 순수 자바 코드를 단위 테스트할 때 수정자 의존 관계인 경우를 생각해보자. 예시로 아래와 같이 수정자 의존 관계로 구현 클래스가 있다.
@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;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
그리고 아래와 같은 테스트 코드를 실행해보자.
class OrderServiceImplTest {
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.createOrder(1L, "item", 10000);
}
}
그러면 아래와 같이 NullPointerException이 발생한다.
왜냐하면 memberRepository와 discountPolicy의 값이 세팅되지 않았기 때문이다. 그러면 (더미라도) 가짜 memoryMemberRepository라도 만들어서 넣어줘야 한다. 이런 실수를 하는 이유는 무엇일까? 테스트 코드를 작성할 때 의존 관계가 전혀 보이지 않기 때문이다. 위에서 OrderServiceImpl 인스턴스를 생성할 때 new OrderServiceImpl()로 생성할 텐데 해당 코드를 보면 전혀 의존 관계를 알 수 없다. 따라서 OrderServiceImpl이 작성된 파일로 가서 의존 관계를 알아내야 한다. 하지만 생성자 주입의 경우 다르다. 에당초에 new OrderServiceImpl()를 사용하면 컴파일 오류가 발생하고 코드를 new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy())로 바꿔주면 테스트 코드는 잘 동작한다. 이와 같이 순수한 자바 코드로 테스트 할 때에는 생성자 주입을 사용하는 것이 더 좋다. 정리하자면 생성자 주입이 좋은 이유는 다음과 같다.
- 프레임워크에 의존하지 않고 순수한 자바 언어의 특징을 잘 살릴 수 있다.
- 기본으로 생성자 주입을 사용하고 필수 값이 아닌 경우만 수정자 주입 방식 옵션으로 부여하면 된다. 생성자 주입과 수정자 주입은 동시에 사용할 수 있다. 단, 필드 주입은 사용하지 않는 것이 좋다.
4. 롬복과 최신 트랜드
개발을 하다보면 거의 대부분이 불변이기 때문에 final 키워드를 사용한다. 그런데 final을 사용하게 되면 생성자와 주입 받은 값을 대입하는 코드도 만들어야 한다. 이런 반복적인 귀찮은 코드들을 최적화하는 법을 살펴보자. 코드를 최적화 하기 전에 먼저 롬복을 사용하기 위해 설정을 해보자. 먼저 build.gradle에 아래와 같이 관련 설정을 추가해주자.
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.2'
id 'io.spring.dependency-management' version '1.1.2'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
// 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'
}
tasks.named('test') {
useJUnitPlatform()
}
그리고 [File]→[Settings]에 들어가서 Plugins에 Lombok을 검색하고 설치를 진행하자. 해당 롬복은 아래와 같다.
그리고 동일하게 [File]→[Settings]에서 annotation processor를 검색하고 "Enable annotation processing"에 체크표시를 해주자.
lombok을 사용하게 되면 아래와 같이 @Getter와 @Setter 같은 것들을 통해 좀 더 편하게 코드를 작성할 수 있다.
@Getter
@Setter
public class HelloLombok {
private String name;
private int age;
public static void main(String[] args){
HelloLombok helloLombok = new HelloLombok();
helloLombok.setName("코딩마루");
String name = helloLombok.getName();
System.out.println("name = " + name);
}
}
위처럼 lombok을 사용하면 getter, setter 같은 함수를 직접 만들지 않고 @Getter, @Setter을 사용함으로써 대체할 수 있다. lombok의 자세한 사용법은 좀 더 찾아보도록 하자. 실무에서 굉장히 많이 사용한다. 이제 아래 기본 코드를 최적화 해보자.
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
- @RequiredARgsConstructor: final이 붙어 있는 필드들은 필수값이므로 해당 필드들을 파라미터로 받는 생성자를 만들어준다. 롬복은 자바의 어노테이션 프로세서 기능을 사용해 컴파일 시점에서 생성자 코드를 자동으로 생성해주는 것이다. 해당 어노테이션을 사용하면 위 코드에서 생성자 관련 코드들은 모두 삭제할 수 있다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
...
}
위와 같이 코드가 굉장히 줄어든 것을 알 수 있다. lombok은 꼭 따로 공부하도록 하자.
- 롬복은 자바의 어노테이션 프로세서 기능을 사용해 컴파일 시점에서 생성자 코드를 자동으로 생성해준다.
5. 조회 빈이 2개 이상 - 문제
@Autowired는 타입으로 조회한다. 그래서 만약 필드에 private DiscountPolicy discountPolicy가 선언되어 있고 해당 필드를 자동 주입한다면 ac.getBean(DiscountPolicy.class)와 동일한 방식으로 동작한다. 문제는 만약 DiscountPolicy(인터페이스)의 구현체가 두 개이고, 해당 구현체들이 모두 빈으로 등록되어 있는 경우다. 이런 경우 FixDiscountPolicy, RateDiscountPolicy 모두 조회될 것이다. 이런 경우 아래와 같은 에러가 발생한다.
- NoUniqueBeanDefinitionException: No qualifying bean of type "com.example.projectEx1.discount.DiscountPolicy'
이런 문제를 해결하기 위해 하위 타입으로 빈을 조회할 수도 있다. 하지만 이런 방법은 DIO를 위반하고 유연성이 떨어진다. 또한 이름만 다른 똑같은 타임의 빈이 두 개 있다면 해결할 수 없다. 또한 스프링 빈을 수동 등록하여 문제를 해결할 수도 있다. 하지만 해당 문제를 해결하기 위해서는 여러 방법이 존재한다.
6. @Autowired 필드명, @Qualifier, @Primary
자동 주입을 위해 조회된 빈이 두 개 이상인 경우 아래와 같은 해결 방법이 있다.
- @Autowired 필드 명 매칭
- @Qualifier → @Qualifier끼리 매칭 → 빈 이름 매칭
- @Primary 사용
6.1. @Autowired 필드 명 매칭
@Autowired는 처음에 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드명, 파라미터 이름으로 빈 이름을 추가 매칭한다. 즉, 만약 "@Autowired private DiscountPolicy discountPolicy"라고 하면 먼저 DiscountPolicy라는 타입 매칭을 통해 자동 주입을 시도한다. 만약 이때 두 개 이상의 빈이 조회된다면 discountPolicy라는 파라미터 명 혹은 필드 명을 통해 해당 이름의 빈을 찾아온다. 생성자 주입의 경우 파라미터 명을 통해 가져온다. 즉, 필드명을 "@Autowired private DiscountPolicy rateDiscountPolicy"로 수정하거나 생성자의 파라미터를 DiscountPolicy rateDiscountPolicy로 변경한다면 올바르게 동작하게 된다.
6.2. @Qualifier 사용
아래와 같이 빈 등록시 @Qualifier를 붙여줄 수 있다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
이런 경우 생성자에 다음과 같이 매개변수 옆에 @Qualifier를 붙여주면 문제를 방지할 수 있다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
생성자 주입이 아니라 수정자 주입의 경우 아래와 같이 작성할 수 있다.
@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
return discountPolicy;
}
또한 아래와 같이 수동 빈 등록의 경우도 @Qualifier를 사용할 수 있다.
@Bean
@Qualifier("mainDiscountPolicy")
public DiscountPolicy discountPolicy() {
return new ...
}
단, 만약 mainDiscountPolicy를 못 찾는 경우 mainDiscountPolicy라는 이름의 스프링 빈을 찾는다. 하지만 @Qualifier는 @Qualifier를 찾는 용도로만 사용하는 것이 좋다. (만약 해당 이름의 스프링 빈까지 없다면 "NoSuchBeanDefinitionException" 예외가 발생한다.
6.3. @Primary 사용
@Primary는 우선순위를 정해주는 방법으로 편하고, 자주 사용하는 방법이다. 만약 자동 주입때 여러 빈이 매칭되면 @Primary이 붙은 빈이 우선권을 가진다. 즉, 아래의 경우 RateDiscountPolicy가 우선권을 가지므로, 해당 빈이 주입된다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
- 보통 주로 사용하는 것은 @Primary를 사용하고 보조적인 빈들은 @Qualifier을 통해 주입해준다.
7. 어노테이션 직접 만들기
이전에 @Qualifier를 다음과 같이 사용했다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
위와 같이 사용하면 "mainDiscountPolicy"가 문자열이기 때문에 오타가 나도 컴파일 단계에서 발견하기 어렵다. 따라서 아래와 같이 이러한 문제를 미리 방지하기 위해 어노테이션을 만들 수 있다.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
- Qualifier위에 있는 것들은 @Qualifier안에 있는 어노테이션을 모두 가져온 것이다. 해당 어노테이션들이 없다면 문제가 발생한다. 이때 @Qualifier를 사용해도 Qualifer의 어노테이션들이 사용되지 않는 이유(위처럼 추가해줘야 하는 이유)는 어노테이션에 상속이라는 개념이 없기 때문이다.
8. 조회한 빈이 모두 필요할 때 (Map, List)
의도적으로 정말 해당 타입의 스프링 빈이 다 필요한 경우도 있다. 예를 들어, 할인 서비스를 제공하는데 클라이언트가 할인 종류를 선택할 수 있을 수 있다. 이런 경우 스프링을 사용하면 쉽게 구현할 수 있다. 아래는 Map과 List를 통해 해당 타입의 전체 스프링 빈을 가져온 코드 예시다.
public class AllBeanTest {
@Test
void findAllBean() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
}
@Configuration
static class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
/*
Map, List 모두 자동 주입 됨. 즉, DiscountPolicy의 구현체이며 스프링 빈으로 등록된
RateDiscountPolicy와 FixDiscountPolicy 모두 Map과 List 필드에 들어가게 됨.
*/
@Autowired
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);
}
}
}
- Autowired는 위와 같이 해당하는 타입의 모든 빈을 Map과 List 필드에 넣어준다.
위 코드의 테스트 결과는 아래와 같다.
이와 같은 방식을 사용하면 아래와 같이 사용자가 특정 할인 정책을 선택할 때 유동적으로 할인 가격을 조정하는 메서드도 만들 수 있다.
static class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
@Autowired
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);
}
// discountCode는 스프링 빈 이름이다. 원하는 할인 정책을 선택하면 해당 할인 정책을 적용할 수 있다.
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
System.out.println("discountCode = " + discountCode);
System.out.println("discountPolicy = " + discountPolicy);
return discountPolicy.discount(member, price);
}
}
9. 자동, 수동의 올바른 실무 운영 기준
- 보통 자동 의존 관계 주입을 기본으로 사용하는 것이 가장 좋다. 스프링은 @Component, @Controller, @Service, @Repository와 같이 계층에 맞추어 일반적인 어플리케이션 로직을 자동으로 스캔하도록 지원한다. 또한 스프링 부트는 컴포넌트 스캔을 기본으로 사용하며, 스프링 부트의 다양한 스프링 빈들도 조건이 맞으면 자동으로 등록된다. 그러다 보니 시간이 지날수록 자동을 선호하는 편이다.
- 어플리케이션은 업무 로직과 기술 지원 로직으로 나눌 수 있다. "업무 로직 빈"이란 웹을 지원하는 컨트롤러, 핵심 비지니스 로직이 있는 서비스, 데이터 계층 로직을 처리하는 레포지토리 등을 포함한다. 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경된다. "기술 지원 빈"의 경우 기술적 문제나 공통 관심사(AOP)를 처리할 때 주로 사용한다. 데이터베이스 연결이나 공통 로그 처리와 같이 업무 로직을 지원하기 위한 하부 기술이나 공통 기술을 말한다. 이때 업무 로직의 경우 숫자도 많고 한 번 개발하면 유사한 패턴이 있고, 이런 경우 자동 기능을 사용하는 것이 좋다. 반면 기술 지원 로직은 업부 로직에 비해 숫자가 적고, 애플리케이션 전반에 걸쳐서 광범위한 영향을 미친다. 그리고 업무 로직은 문제가 발생했을 때 어디가 문제인지 명확히 들어나지만 기술 지원 로직은 적용이 잘 되고 있는지 조차 파악하기 어렵다. 따라서 기술 지원 로직은 수동 빈 등록을 사용하는 것이 좋다.
- 단, 다형성을 많이 활용하는 비지니스 로직에서도 수동 주입을 사용하는 것이 좋을 때도 있다. 이전에 DiscountService 인터페이스에 두 구현체인 rate와 fix가 있었고 해당 구현체들은 스프링 빈에 등록되었다. 만약 이 구현체 말고도 다른 구현체들이 많거나, 다른 개발자와 함께 코딩을 하여 다른 구현체들은 모른다면 코드를 뒤져봐야 한다. 따라서 이런 경우 DiscountPolicyConfig와 같이 따로 설정 정보를 만드는 것이 좋다. 이때 해당 설정 정보에는 DiscountPolicy와 관련된 빈들만 등록되어야 한다. 이렇게 등록하면 한 눈에 관련 빈들을 볼 수 있다는 장점이 있다. 만약 수동 주입을 사용하고 싶지 않다면 DiscountPolicy관련 클래스만 따로 모아놓은 패키지를 만드는 것이 좋다.
'BackEnd > Spring' 카테고리의 다른 글
[Spring] 빈 스코프 (0) | 2023.09.02 |
---|---|
[Spring] 빈 생명주기 콜백 (0) | 2023.08.31 |
[Spring] 컴포넌트 스캔 (0) | 2023.08.28 |
[Spring] 싱글톤 컨테이너 (0) | 2023.08.23 |
[Spring] 스프링 컨테이너와 스프링 빈 (0) | 2023.08.20 |