스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
본 포스팅은 <스프링 핵심 원리 - 기본편> 강의 내용을 복습하기 위해 간략히 작성된 글이므로 전체 내용은 위 강의에서 확인 가능합니다.
2. 객체지향의 5가지 원칙(SOLID), 관심사의 분리
7. @Autowired 필드명 매칭, @Qualifier, @Primary
스프링 컨테이너 생성
스프링 컨테이너는 설정 정보를 참고해서 스프링 빈을 등록하고 의존관계를 설정해줍니다.
먼저 스프링 컨테이너를 생성하는 코드를 작성해 보겠습니다.
ApplicationContext ac
= new AnnotationConfigApplicationContext(AppConfig.class);
스프링 컨테이너는 XML 기반이나 애노테이션 기반 자바 설정 클래스로 만들 수 있는데요. 여기에서는 ApplicationContext(인터페이스)의 구현체로 AnnotationConfigApplicationContext(XX.class) 즉, 애노테이션 기반 자바 설정 클래스를 선택해서 스프링 컨테이너를 생성했습니다.
XML 기반 사용 코드
public class XmlAppContext {
@Test
void xmlAppContext() {
ApplicationContext ac = new
GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = ac.getBean("memberService",
MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
}
src/main/resource/appConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://
www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="memberService" class="hello.core.member.MemberServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository" />
</bean>
<bean id="memberRepository"
class="hello.core.member.MemoryMemberRepository" />
<bean id="orderService" class="hello.core.order.OrderServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository" />
<constructor-arg name="discountPolicy" ref="discountPolicy" />
</bean>
<bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy" />
</beans>
스프링 컨테이너를 생성하는 코드를 작성했으니 동작 과정에 대해 살펴보겠습니다.
1. 스프링 컨테이너를 생성할 때는 구성 정보를 지정해줘야 하는데 여기서 아래 AppConfig.class를 지정해줬습니다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository();
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
2. 스프링 컨테이너는 파라미터로 넘어온 설정 클래스의 정보를 사용해서 스프링 빈을 등록합니다.
- memberService
- memberRepository
- orderService
- discountPolicy
3. 그리고 아래의 그림과 같이 의존관계 설정을 완료합니다.
BeanFactory와 ApplicationContext
BeanFactory는 스프링 컨테이너의 최상위 인터페이스로 스프링 빈을 관리하고 조회합니다. ApplicationContext는 이 BeanFactory의 기능을 모두 상속받아서 제공을 하는데요. ApplicationContext에는 빈을 관리하고 조회하는 기능 외에도 여러 부가 기능에 있습니다. 그래서 우리가 직접 BeanFactory를 사용할 일은 거의 없습니다.
ApplicationContext만이 가진 부가 기능은 다음과 같습니다.
- MessageSource(메시지소스를 활용한 국제화 기능)
- EnvironmentCapable(환경변수) : 로컬, 개발, 운영등을 구분해서 처리
- ApplicationEventPublisher(애플리케이션 이벤트) : 이벤트를 발행하고, 구독하는 모델을 편리하게 지원
- ResourceLoader(편리한 리소스 조회) : 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
스프링 빈 조회
스프링 빈이란 개념을 많이 들어봤을 텐데 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 합니다. 스프링 빈을 찾으려면 ac.getBean()메서드를 사용하면 됩니다.
ApplicationContext ac
= new AnnotationConfigApplicationContext(AppConfig.class);
MemberSerivice memberService =
ac.getBean("memberService", MemberService.class);
테스트 코드
class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService =
ac.getBean("memberService", memberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름없이 타입으로만 조회")
void findBeanByType() {
MemberService memberService =
ac.getBean(memberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName() {
MemberService memberService =
ac.getBean("memberService", memberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
인터페이스에만 의존하는 규칙을 지키기 위해서는 구체 타입으로 조회하지 않는 것이 좋습니다.
만약 빈이 없다면 ‘NoSuchBeanDefinitionException’ 에러를 만날 수 있습니다. 이럴 때는 테스트 코드를 어떻게 작성해야 할까요?
@Test
@DisplayName("빈 이름으로 조회X")
void findBeanByName() {
MemberService xxxx =
ac.getBean("xxxx", memberService.class);
assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("xxxx", memberService.class));
}
assertThrows를 이용해서 오른쪽 로직을 실행했을 때 ‘NoSuchBeanDefinitionException’ 에러가 떠야 테스트가 성공할 수 있게 만든 코드입니다.
스프링 빈에서 중요한 것은 중복되지 않게 해야 한다는 것입니다. 빈이 2개 이상으로 찾아지면 ‘NoUniqueBeanDefinitionException’에러를 만날 수 있는데 아래와 같이 테스트 코드를 작성할 수 있습니다.
@Test
@DisplyName("부모 타입으로 조회 시 자식이 둘 이상 있으면 중복 오류가 발생한다.")
void findBeanByParentTypeDuplicate() {
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(DiscountPolicy.class));
}
참고로 BeanDefinition이란 빈 설정 메타정보라고 하는데 @Bean, <bean>당 각각 하나씩 메타 정보를 생성됩니다. 스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성하는 것이죠.
그런데 이렇게 스프링 빈이 2개 이상이어서 에러가 날 때는 어떻게 해결해야 할까요?
[Spring] @Autowired 필드명 매칭, @Qualifier, @Primar
스프링 핵심 원리 - 기본편 - 인프런 | 강의 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런... www.inflearn.
justsora.tistory.com
만약 조회한 빈이 모두 필요할 때는 어떻게 해야 할까요?
예를 들어서 할인 서비스를 제공하는데 클라이언트가 할인의 종류를 선택할 수 있다고 가정해 봅시다. 스프링을 사용하면 전략 패턴을 간단하게 구현할 수 있습니다.
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 DiscountSerivice {
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);
System.out.println("discountCode = " + discountCode);
System.out.println("discountPolicy = " + discountPolicy);
return discountPolicy.discount(member, price);
}
}
}
DiscountService는 Map으로 DiscountPolicy를 주입받는데 이때 두 개의 구현 객체를 주입받습니다. discount() 메서드에 “fixDiscountPolicy”가 넘어오면 map에서 fixDiscountPolicy 스프링 빈을 찾아서 실행하는 것이죠.
'Spring' 카테고리의 다른 글
[Spring] 컴포넌트 스캔 (0) | 2022.09.17 |
---|---|
[Spring] 싱글톤 패턴과 싱글톤 컨테이너 (0) | 2022.09.17 |
[Spring] 제어의 역전(IoC)과 의존관계 주입(DI) (0) | 2022.09.14 |
[Spring] 객체지향의 5가지 원칙(SOLID), 관심사의 분리 (0) | 2022.09.14 |
[Spring] 스프링이란 (0) | 2022.09.14 |