해당 포스트는 스프링 핵심 원리 - 기본편 강의 내용을 참고하였습니다.
1. @ComponentScan 를 설정 정보에 추가
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type= FilterType.ANNOTATION, classes=Configuration.class)
)
public class AutoAppConfig {
//이전과는 달리 @Bean 등록 없음!
}
2. @Component 를 컴포넌트 스캔의 대상인 클래스들에 추가
@Component
public class MemberServiceImpl implements MemberService{
...
}
3. @Autowired 로 의존관계 자동 주입하기
@Component
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
....
}
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
.....
}
지정하지 않을 시엔, @ComponentScan 이 붙은 설정 정보 클래스의 패키지가 시작 위치로 지정된다.
설정 정보 클래스의 위치를 프로젝트 최상단에 두고, 패키지 위치 지정 코드를 생략하는 것이 스프링에서 권장하는 방법이다(@SpringBootApplication).
@Configuration
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes =MyIncludeComponent.class),
excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes =MyExcludeComponent.class), }
)
static class ComponentFilterAppConfig {
}
[FilterType.]
1. 자동 빈 등록 vs 자동 빈 등록
ConflictingBeanDefinitionException 스프링 오류가 발생한다.
2. 수동 빈 등록 vs 자동 빈 등록 :
수동 빈 등록이 우선권을 가진다.
순수 자바 코드로 실행 시에는 오류가 발생하지 않고 아래 로그를 확인할 수 있다.
Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing
스프링 부트를 통해 실행하면(CoreApplication) 오류가 발생한다.
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
오버라이딩 하게 두려고 한다면, resource/application.propperties 파일에 위 코드를 추가한다.
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
만약 주입할 스프링 빈이 없는 상태에서 @Autowired 를 사용하고 싶다면?
@Autowired(required=false)
public void setNoBean1(Member nobean1) {
System.out.println("setNoBean1 = " + nobean1);
}
@Autowired
public void setNoBean2(@Nullable Member nobean2) {
System.out.println("setNoBean2 = " + nobean2);
}
@Autowired
public void setNoBean3(Optional<Member> nobean3) {
System.out.println("setNoBean3 = " + nobean3);
}
위 이유들로, 기본적으로는 항상 생성자 주입을 선택하자. 필수 값이 아닌 경우엔 수정자 주입 방식을 추가하자.
생성자 주입 방식을 사용하면 결국 생성자 코드를 작성해야 하는데 코드 작성이 귀찮다면? Lombok을 써보자!
생성자 주입 방식을 사용할 때 final 키워드를 필드에 붙였는데, lombok은 이 final 키워드가 붙은 필드들을 인식하여 자동으로 생성자를 만들어준다.
변경 전 코드
@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 //lombok
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
@Component
public class FixDiscountPolicy implements DiscountPolicy {
...
}
@Component
public class RateDiscountPolicy implements DiscountPolicy{
...
}
@Autowired로 연결 가능한 빈이 2개 이상인 경우, 오류가 발생한다.
NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = rateDiscountPolicy;
}
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
주의: @Primary보다 @Qualifier의 우선순위가 더 높음!
1. 애노테이션 생성
package hello.core.annotataion;
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)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
2. 애노테이션 지정
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {
....
}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,@MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
public class AllBeanTest {
@Test
void findAllBean() {
ApplicationContext 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;
//생성자 하나여서 @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);
}
//스프링 빈 실행
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);
}
}
}
Spring 1
EDITOR: 로자
[스프링 1] 3장.스프링 DI (0) | 2022.10.27 |
---|---|
[스프링 1] 2장.스프링 시작하기 (0) | 2022.10.27 |
[스프링1] 빈 생명주기 콜백, 빈 스코프 (0) | 2022.10.21 |
[스프링 1] AppConfig, 스프링 컨테이너, 싱글톤 (0) | 2022.10.06 |
[스프링1] 객체 지향 설계와 스프링, DI (1) | 2022.09.29 |