스프링 컨테이너가 생성되는 과정을 알아보자.
ApplicaionContext applicationContext =
new AnnotationConfigApplicaionContext(AppConfig.class);
ApplicationContext
를 스프링 컨테이너라 한다.ApplicationContext
는 인터페이스이다. → AnnotationConfigApplicaionContext
가 구현AppConfig
를 사용했던 방식이 애노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만든 것이다.ApplicationContext
)를 만들어보자.new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig
class를 넣어준다.참고 더 정확히는 스프링 컨테이너를 부를 때 BeanFactory
, ApplicationContext
로 구분해서 이야기 한다. BeanFactory
를 직접 사용하는 경우는 거의 없으므로 일반적으로 ApplicationContext
를 스프링 컨테이너라 한다.
스프링 컨테이너 생성
new AnnotationConfigApplicationContext(AppConfig.class)
AppConfig
클래스를 파라미터로)AppConfig.class
를 구성 정보로 지정했다.스프링 빈 등록
빈 이름
@Bean(name="memberService2")
주의 빈 이름은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.
스프링 빈 의존관계 설정 - 준비
스프링 빈 의존관계 설정 - 완료
- 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다. - 단순히 자바 코드를 호출하는 것 같지만, 차이가 있다.참고
스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다. 그런데 이렇게 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다.
정리
스프링 컨테이너를 생성하고, 설정(구성) 정보를 참고해서 스프링 빈도 등록하고, 의존관계도 설정했다.
이제 스프링 컨테이너에서 데이터를 조회해보자.
스프링 컨테이너에 실제 스프링 빈들이 잘 등록 되었는지 확인해보자.
public class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("모든 빈 출력하기")
void findAllBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("beanDefinitionName = " + beanDefinitionName + ", object = " + bean);
}
}
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("beanDefinitionName = " + beanDefinitionName + ", object = " + bean);
}
}
}
}
참고 리스트나 배열이 있을 경우 iter
+ tab
하면 for each문이 자동 완성 됨.
ac.getBeanDefinitionNames()
: 스프링에 등록된 모든 빈 이름을 조회한다.ac.getBean()
: 빈 이름으로 빈 객체(인스턴스)를 조회한다.getRole()
로 구분할 수 있다.ROLE_APPLICATION
: 일반적으로 사용자가 정의한 빈ROLE_INFRASTRUCTURE
: 스프링이 내부에서 사용하는 빈
ac.getBean(빈이름, 타입)
ac.getBean(타입)
NoSuchBeanDefinitionException: No bean named 'xxxxx' available
예제 코드
public 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("빈 이름으로 조회X")
void findBeanByNameX() {
assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean("xxxxx", MemberService.class));
}
}
ac.getBeansOfType()
을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회시 같은 타입이 두 개 이상 있으면, 중복 오류가 발생한다")
void findBeanByTypeDuplicate() {
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
@Test
@DisplayName("타입으로 조회시 같은 타입이 두 개 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByName() {
MemberRepository memberRepository1 = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository1).isInstanceOf(MemberRepository.class);
}
@Test
@DisplayName("특정 타입의 빈 모두 조회 하기")
void findAllBeanByType() {
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String beanName : beansOfType.keySet()) {
System.out.println("key = " + beanName + ", value = " + beansOfType.get(beanName));
}
assertThat(beansOfType.size()).isEqualTo(2);
}
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
public class ApplicationContextExtendsFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanByParentTypeDuplicate() {
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
void findBeanByParentTypeBeanName() {
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(DiscountPolicy.class);
}
@Test
@DisplayName("특정 하위 타입으로 조회")
void findBeanBySubtype() {
RateDiscountPolicy rateDiscountPolicy = ac.getBean(RateDiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(DiscountPolicy.class);
}
@Test
@DisplayName("부모 타입으로 모두 조회하기")
void findAllBeanByParentType() {
Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + ", value = " + beansOfType.get(key));
}
assertThat(beansOfType.size()).isEqualTo(2);
}
@Test
@DisplayName("부모 타입으로 모두 조회하기 - Object")
void findAllBeanByObjectType() {
Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + ", value = " + beansOfType.get(key));
}
}
@Configuration
static class TestConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
}
beanFactory와 ApplicationContext에 대해서 알아보자.
getBean()
을 제공한다.예를 들어서 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
로컬, 개발, 운영등을 구분해서 처리
이벤트를 발행하고 구독하는 모델을 편리하게 지원
파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
정리
new AnnotationConfigApplicationContext(AppConfig.class)
AnnotationConfigApplicationContext
클래스를 사용하면서 자바 코드로된 설정 정보를 넘기면 된다.GenericXmlApplictionContext
를 사용하면서 xml 설정 파일을 넘기면 된다.xml 기반의 스프링 빈 설정 정보
src/main/resources/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>
테스트 코드
public class XmlAppContext {
@Test
void xmlAppContext() {
GenericXmlApplicationContext xmlApplicationContext = new GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = xmlApplicationContext.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
}
}
BeanDefinition
이라는 추상화가 있다.BeanDefinition
을 만들면 된다.BeanDefinition
을 만들면 된다.BeanDefinition
만 알면 된다.BeanDefinition
을 빈 설정 메타정보라 한다.@Bean
, <bean>
당 각각 하나씩 메타 정보가 생성된다.코드레벨의 BeanDifinition
AnnotationConfigApplicationContext
는 AnnotatedBeanDefinitionReader
를 사용해서 AppConfig.class
를 읽고 BeanDefinition
을 생성한다.GenericXmlApplicationContext
는 XmlBeanDefinitionReader
를 사용해서 appConfig.xml
설정 정보를 읽고 BeanDefinition
을 생성한다.XxxBeanDefinitionReader
를 만들어서 BeanDefinition
을 생성하면 된다.public class BeanDefinitionTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 설정 메타정보 확인")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
System.out.println("beanDefinitionName = " + beanDefinitionName + "beanDefinition = " + beanDefinition);
}
}
}
}
정리
BeanDefinition
을 직접 생성해서 스프링 컨테이너에 등록할 수 도 있다. 하지만 실무에서 BeanDefinition
을 직접 정의하거나 사용할 일은 거의 없다. → 어려우면 그냥 넘어가면 된다^^!BeanDefinition
에 대해서는 너무 깊이있게 이해하기 보다는, 스프링이 다양한 형태의 설정 정보를 BeanDefinition
으로 추상화해서 사용하는 것 정도만 이해하면 된다.
안녕하세요, 이정원님.
인프런 운영팀입니다.
인프런을 이용해 주셔서 대단히 감사드립니다. 저작권과 관련해 말씀 드릴 부분이 있어 댓글로 찾아뵙게 되었어요. 현재 공부 목적으로 블로그를 운영 중인 것으로 확인됩니다.
인프런 강의내용을 공부 목적으로 정리하며 본인의 사견을 덧붙이는 형식의 포스팅은 얼마든지 가능합니다. 다만, 강의 자료(교안) 일부를 캡쳐하고 소스코드 등을 public 상태로 공유하시는 것은 저작권에 위배되는 행위임을 알려드립니다.
이에 아래 2가지 요청을 드립니다. 확인 부탁드립니다.
1. 블로그에서 public 상태로 글을 게재할 경우, 개별 게시글마다 출처를 모두 기재하셔야 합니다. (예시. "인프런 - 강의명" & 강의 링크)
2. 강의에서 제공된 강의자료 캡쳐, 소스코드는 전체 게시글에서 삭제 요청드립니다.
이는 지식공유자의 소중한 저작권임을 다시 한번 안내드립니다.
확인 후 빠른 조치 부탁드리겠습니다.
관련 문의는 언제든지 info@inflearn.com 으로 연락주시면 감사하겠습니다.
인프런 드림