이 게시글은 인프런의 유료 강의 스프링 핵심 원리 - 기본편을 수강하며 공부한 내용을 담고있습니다.
저작권에 의해 강의내용에 해당하는 전체 코드는 공개할 수 없습니다.
저작권에 의해 모든 내용을 담을 수 없습니다. 중복 및 반복되는 내용은 생략했습니다.
스프링 컨테이너
스프링 컨테이너란 Spring 에서 자바 객체들을 관리하면서 다양한 기능들을 제공하기 위해 존재한다.
생성
1
2
// ApplicationContext 는 interface 이다.
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
위 코드는 스프링 컨테이너를 생성하는 코드고 ApplicationContext 가 스프링 컨테이너 역할을 한다.
ApplicationContext 는 인터페이스이기 때문에 구현체를 만들어야한다.
AnnotationConfigApplicationContext 클래스는 이름에서 알 수 있듯이 @Configuration 이 붙은 클래스를 가지고 ApplicationContext를 생성한다.
그리고 AppConfig 클래스를 기반으로 스프링 컨테이너로 만들것이기 때문에 생성자의 매개변수로 AppConfig.class 를 준것이다.

스프링 컨테이너를 생성하게되면 구성정보(여기서는 AppConfig)를 바탕으로 Spring Bean을 생성한다.
- Bean 이름을 지정해줄 수 있지만 지정하지 않으면 기본적으로 메서드명을 이름으로 사용한다.
- Bean 이름을 중복으로 지으면 오류가 나거나 나지 않더라도 이름이 중복된 Bean을 호출하기 어렵기 때문에 중복하지 않는게 중요하다.
그리고 이렇게 의존관계가 설정이된다. 사실 코드를 보면 이런 순서로 생성되는 것이 아님을 알 수 있다.
1
2
3
4
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
memberService 빈이 생성될 때 이미 memberRepository 빈을 생성하기 떄문이다.
(이 부분은 이해를 위해 일부러 이렇게 설명했다고 함. 추후 싱글톤 강의에서 제대로된 설명을 다시 함.)

결과적으로 Spring 컨테이너에는 그림과 같은 정보가 담겨지게된다.
Bean 조회 - 연습
만들어본 Bean을 조회해본다.
- ac 에 저장된 Bean 이름들을 모두 가져온다.
- 가져온 이름들로 Bean에 저장된 객체들을 가져온다.
- 콘솔 창에 출력한다.

출력 결과물을 보면 내가 작성하지 않은 Bean 도 보인다. Spring 자체에서 사용하는 Bean 이므로 넘어가면 된다.
눈여겨 보아야 할 것은 AppConfig 도 Bean 으로 등록되어 있다는 점과 각 Bean 에 어떤 객체가 저장되어 있는지이다.
Bean 조회 - 기본
- getBean(이름, 타입)
- getBean(타입)
기본적인 bean 조회 방법은 두 가지 방법이있다.
memberService 라는 이름을 가진 Bean 이 ac 에 있는지 조회했다.
가져온 값을 콘솔에 출력해서 눈으로 테스트 하는 방법도 있지만 수백개의 테스트를 눈으로 확인할 수 없으니, Assertions 를 이용해 테스트 로직을 작성한다.
1
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
memberService 가 MemberServiceImpl 클래스인지 검사한다. 맞으므로 오류가 발생하지 않고 콘솔에도 녹색 체크표시가 나온다.
타입으로 조회 하는 방법은 이름으로 조회하는 방법과 동일 하므로 생략한다.

test case 는 실패 case 도 있어야 한다.
실패한 경우 사진과 같이 NoSuchBeanDefinitionException 예외가 발생한다.
실패한경우가 의도적으로 동작하는지 체크하기 위해 NoSuchBeanDefinitionException 예외를 검증하는 테스트 코드를 작성한다.<br
1
2
3
4
5
6
7
8
@Test
@DisplayName("이름으로 빈 조회 실패")
void findBeanByName_fail() {
//ac.getBean("asdf", MemberService.class);
// 실패한 경우를 테스트할 때는 JUnit 의 Assertions 를 사용한다.(위에서 사용한 Assertions 랑 이름만 같고 다른 클래스임.)
org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("asdf", MemberService.class));
}
assertThrows 메서드를 사용하여 ac.getBean(“asdf”, MemberService.class) 메서드가 호출 될 때 NoSuchBeanDefinitionException 예외가 발생하면 정상으로 처리되게 한다.
테스트를 실행하면 녹색 체크표시를 볼 수 있다.
Bean 타입으로 조회 - 중복이 있는 경우
- getBean(타입)
- getBeansOfType(타입) : 해당 타입의 모든 Bean 들을 조회
getBean(타입) 으로 조회 시 여러 Bean이 동일한 타입을 가지고 있는 경우가 있다. 이 경우 어떤 Bean을 리턴해줘야할지 모르기 때문에 예외가 발생한다.
이런 경우 getBeansOfType(타입) 메서드를 사용하여 중복되는 빈을 전부 가져온다.

중복된 빈을 테스트 하기 위해 임시로 @Configuration 클래스를 하나 만들어준다.
그리고 해당 클래스로 스프링 컨테이너를 만든다.

getBeansOfType 메서드를 사용해 조회하면 Map 자료구조로 모든 Bean 들을 리턴해준다.
하나씩 꺼내서 print 로 직접 확인해보면 위에서 임시로 만든 Bean들이 담겨져 있는 것을 볼 수 있다.
Bean 조회 - 상속 관계
Bean 을 조회할 때 부모 클래스로 조회하면 자식 클래스까지 모두 조회된다.
코드를 보면 쉽게 이해가 된다.
1
2
3
4
5
6
7
8
9
@Test
@DisplayName("타입으로 모든 빈 조회")
void findAllBeansByType() {
// getBeansOfType 의 매개변수를 Object 클래스로 수정
//Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
for (String key : beansOfType.keySet()) {
System.out.println("key : " + key + " value : " + beansOfType.get(key));
}
모든 클래스는 Object 클래스를 상속받는다.
Object 클래스로 타입을 지정해서 모든 Bean 을 조회하면 Object 클래스뿐만 아니라 상속받는 모든 클래스가 출력되게 된다.
BeanFactory와 ApplicationContext

AnnotationConfigApplicationContext 는 ApplicationContext 를 상속받고(구현하고) ApplicationContext는 BeanFactory를 상속받는다.
- BeanFactory
- Spring 컨테이너 최상위 인터페이스
- 스프링 Bean 을 관리 및 조회 (getBean 등을 제공)
- ApplicationContext
- 여태 사용한 기능은 전부 BeanFactory 의 기능이다.
- ApplicationContext 를 사용하는 이유는 BeanFactory 뿐만 아니라 필요한 다른 여러 인터페이스를 상속받기 때문이다.


EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource 등을 상속받은 것을 볼 수 있다.
- MessageSource
- 메세지 소스를 활용한 국제화 기능 지원
- EnvironmentCapable
- 환경변수를 통해 로컬, 개발, 운영 등을 분리해서 처리
- ApplicationEnvironmentPublisher
- 이벤트를 발행하고 구독하는 모델을 편리하게 지원
- ResourceLoader
- 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
정리
BeanFactory, ApplicationContext는 스프링 컨테이너다.
ApplicationContext는 빈 관리기능(BeanFactory) + 편리한 부가기능을 제공한다.
다양한 설정 형식 지정(자바코드, XML)

스프링 컨테이너는 @Configuration 말고 다른 방법으로도 만들 수 있다. 그 중 하나는 XML이다. 아래는 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>
src > main > resources 경로에 appConfig.xml 파일을 생성하고 위 코드를 작성한다.
기본적으로 XML 문법을 알고있으면 기존에 작성한 AppConfig.java 코드와 비교하면서 보면 바로 이해된다.
1
ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
그리고 new GenericXmlApplicationContext 를 이용해 스프링 컨테이너를 만든다.
XML 로 작성하게 되면 컴파일 없이 Bean 설정 정보를 변경할 수 있다는 장점이 있다.
만약 위 코드를 이해하기 어렵다면 최근에는 사용하지 않으니 패스하고 지나가도 된다.
스프링 빈 설정 메타정보

@Configuration 이 붙은 클래스, XML 이외에 다양한 방법으로 Bean 을 설정할 수 있다.
어떻게 이게 가능할까?
그 중심에는 BeanDefinition 이라는 추상화가 있다.
XML 또는 자바코드를 읽어서 BeanDefinition 을 만들고,
스프링 컨테이너는 BeanDefinition 을 바탕으로 Bean을 만든다.
스프링 컨테이너는 Bean 의 메타정보가 XML에서 온건지 자바코드에서 온건지 전혀 몰라도된다.

그림의 빨간색 영역을 보면 AnnotationConfigApplicationContext 의 AnnotatedBeanDefinitionReader 가 AppConfig.class 를 읽고 BeanDefinition 을 생성하는 모습을 확인할 수 있다.
나머지 그림도 마찬가지의 내용이 담겨져있다.

실제 코드에 AnnotatedBeanDefinitionReader 가 존재하는 모습을 확인할 수 있다. 다음은 생성된 BeanDefinition 을 직접 확인한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
@Test
void findApplicationBean() {
// 일단 정의된 Bean들의 이름을 가져온다.
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
// 반복문으로 Bean 이름을 하나씩 가져온다.
for (String beanDefinitionName : beanDefinitionNames) {
// get BeanDefinition 으로 BeanDefinition 을 꺼내온다.
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
// 내용을 확인하기에 앞서 모든 Bean을 출력하면 내용이 너무 길어지니 if문으로 우리가 정의한 Bean만 출력한다.
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
System.out.println("beanDefinitionName = " + beanDefinitionName +
"\n beanDefinition = " + beanDefinition);
}
}
}
1
2
beanDefinitionName = memberService
beanDefinition = Root bean: class=null; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=appConfig; factoryMethodName=memberService; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in hello.core.AppConfig
테스트 코드를 작성해 BeanDefinition 을 콘솔창에 출력하면 위와 같은 결과를 얻을 수 있다. 전부 출력하면 지저분해서 memberService 만 가져왔다.
- beanDefinition = Root bean
- class = null;
생성할 빈의 클래스 명(여기서는 팩토리 역할의 빈을 사용해서 null이다.) - scope = ;
싱글톤(기본값) - abstract = false;
- lazyInit = null;
스프링 컨테이너를 생설할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 떄 까지 최대한 생성을 지연처리 하는지 여부 - autowireMode = 3;
- dependencyCheck = 0;
- autowireCandidate = true;
- primary = false;
- factoryBeanName = appConfig;
팩토리 역할의 빈을 사용할 경우에 출력된다. - factoryMethodName = memberService; <br 빈을 생성할 팩토리 메서드의 이름.
- initMethodNames = null;
빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명 - destoryMethodNames = [(inferred)];
빈의 생명주기가 끝나서 제거하기 직전 호출되는 메서드 명 - defined in hello.core.AppConfig
이전 포스팅 : 기본 예제에 스프링 기술 적용하기
다음 포스팅 : 스프링과 싱글톤 기초
