스프링 컨테이너 생성 과정
보통 Main 매서드 안에서 ApplicationContext로 스프링 컨테이너를 생성한다.
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
Java
복사
•
ApplicationContext를 스프링 컨테이너라고 한다.
•
ApplicationContext는 인터페이스!!
•
ApplicationContext는 인터페이스이기 때문에 XML 기반, 어노테이션 기반 자바 설정 클래스로도 만들 수 있다.
•
위 코드는 어노테이션 기반 자바 설정 클래스로 스프링 컨테이너를 만드는 것으로, ApplicationContext 의 구현체는 AppConfig.class가 된다.
1. 스프링 컨테이너 생성
3. 스프링 빈 의존관계 설정 - 준비
2. 스프링 빈 등록
4. 스프링 빈 의존관계 설정 - 완료
1.
스프링 컨테이너 생성
→ new AnnotationConfigApplicationContext(AppConfig.class);
→ AnnotationConfigApplicationContext를 사용하면 스프링 컨테이너가 생성되고 그 안에는, 스프링 빈 저장소가 존재한다.
2.
구성정보 활용
→ AnnotationConfigApplicationContext 를 호출할 때 파라미터로 넣어준 AppConfig.class를 구성정보로써 활용하여, 스프링 빈 저장소에 Key : Value = 빈 이름 : 빈 객체 형식으로 Map을 이용하여 저장한다.
→ 보통 빈 이름은 매서드 명을 이용하지만 @Bean(name=”스프링 빈 이름”)을 통해 이름을 직접 부여할 수 있다.
빈 이름은 항상 다른 이름을 부여해야 한다.
→ 같은 이름이 부여되면, 다른 빈이 무시되거나, 기존 빈을 덮어써버리는 경우가 존재한다.
→ 현재는 컴파일 오류를 내기 때문에 다른 이름을 부여하자!!
3.
스프링 빈 의존관계 주입
→ 스프링 컨테이너는 등록된 스프링 빈과, 설정 정보(AppConfig.class)를 참고하여 의존 관계(DI)를 주입한다.
여기서는 스프링 빈과, 의존관계를 주입하는 과정을 나누어서 살펴보지만, 자바 코드에서 스프링 빈을 등록하면, 생성자를 호출하면서 의존관계 주입도 함께 이루어진다. → 자세한 내용은 Dependency Injection(DI) 참고
스프링 빈 조회
1. 전체 조회
전체 스프링 빈 조회
•
스프링에 등록된 모든 빈 정보를 출력
•
ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회
•
ac.getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회
@Test
@DisplayName("모든 빈 출력하기")
void findAllBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean );
}
}
Java
복사
애플리케이션 빈 조회
•
Spring 내부에서 사용하는 Bean 제외하고 내가 등록한 Bean만 조회
•
내부에서 사용하는 빈은 getRole() 로 구분
◦
ROLE_APPLICATION : 사용자가(내가) 등록한 Bean
◦
ROLE_INFRASTRUCTURE : 스프링 내부에서 사용하는 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("name = " + beanDefinitionName + " object = " + bean );
}
}
}
Java
복사
2. 스프링 빈 조회 - 기본
빈 이름으로 조회
•
ac.getBean(빈이름, 타입)
@Test
@DisplayName("빈 이름으로 조회")
void findBeanbyName(){
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
Java
복사
예외 - 조회 대상 스프링 빈이 없을 경우
•
조회 대상 스프링 빈이 없다면 예외 발생
→ NoSuchBeanDefinitionException: No bean named 'xxxxx' available
@Test
@DisplayName("빈 이름으로 조히 X")
void findBeanByNameX(){
assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("xxxx", MemberService.class));
}
Java
복사
이름 없이 타입으로만 조회
•
ac.getBean(타입)
@Test
@DisplayName("이름 없이 타입으로만 조회")
void findBeanByType(){
MemberService memberService = ac.getBean( MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
Java
복사
구체 타입으로 조회
•
ac.getBean(구현체이름)
•
구체 타입으로 조회할 수 있지만, 이는 추상화에 의존하는게 아닌 구현에 의존하는게 되기 때문에 별로 좋지 않음
@Test
@DisplayName("구체 타입으로 조회")
// 구체 타입으로 조회할 수 있지만, 이는 추상화에 의존하는게 아닌 구현에 의존하는게 되기 때문에 별로 좋지 않음
void findBeanbyDetailType(){
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
Java
복사
3. 스프링 빈 조회 - 동일한 타입이 둘 이상
타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생 → 빈 이름 지정
같은 타입 둘 이상일 경우 - 중복오류 발생
•
NoUniqueBeanDefinitionException 예외 발생
@Test
@DisplayName("타입으로 조히시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanbyDuplicate(){
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
Java
복사
같은 타입 둘 이상일 경우 - 빈 이름 지정
•
ac.getBean(빈이름, 타입)
@Test
@DisplayName("타입으로 조히시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
void findBeanbyName(){
MemberRepository memberRepository1 = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository1).isInstanceOf(MemberRepository.class);
}
Java
복사
특정 타입 모두 조회
•
ac.getBeansOfType()
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType(){
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value" + beansOfType.get(key));
}
System.out.println("beansOfType = " + beansOfType);
assertThat(beansOfType.size()).isEqualTo(2);
}
Java
복사
4. 스프링 빈 조회 - 상속
•
부모타입 조회 시, 자식 타입도 전부 조회됨
•
모든 자바 객체는 extends Object 가 생략되어 있는 상태로 Object가 최고 부모이다. 따라서 Object를 조회하면 모든 스프링 빈이 조회된다.
부모 타입으로 모두 조회 - 최고 부모 Object
@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));
}
}
Java
복사
부모 타입으로 조회시, 자식 둘 이상일 경우 - 중복 오류 발생
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생")
void findBeanByParentTypeDuplicate() {
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(DiscountPolicy.class));
}
Java
복사
특정 하위타입으로 조회
@Test
@DisplayName("특정 하위타입으로 조회")
// 좋지 않음
void findBeanBySubType() {
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
Java
복사
부모 타입으로 조회시, 자식 둘 이상일 경우 - 빈 이름 지정
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, Bean 이름을 지정하면 된다.")
void findBeanByParentTypeBeanName() {
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
Java
복사
부모 타입으로 조회
@Test
@DisplayName("부모 타입으로 조회")
void findAllByParentType() {
Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
assertThat(beansOfType.size()).isEqualTo(2);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
}
}
Java
복사
BeanFactory와 ApplicationContext
BeanFactory
•
스프링 컨테이너의 최상위 인터페이스
•
스프링 빈을 관리하고 조회하는 역할을 담당
•
getBean()을 제공
ApplicationContext
•
BeanFactory 기능을 모두 상속받아서 제공
BeanFactory vs ApplicationContext
⇒ 애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요하기 때문에 ApplicationContext 사용
ApplicationContext가 제공하는 부가 기능
ApplicationContext는 BeanFactory 외의 여러 인터페이스를 상속 받아 BeanFactory의 빈 관리 기능 외의 여러 부가 기능을 제공한다.
•
MessageSource : 메시지 소스를 활용한 국제화 기능
→ 예를 들어, 한국에서 들어오면 한국에서, 영어권에서 들어오면 영어로
•
EnvironmentCapable : 환경변수
→ 실제 회사에서 개발한다면, 로컬, 개발, 운영, 스테이징(운영과 가장 비슷한 환경)으로 보통 구분을 하는데 이러한 환경을 관리할 수 있도록 해줌
•
ApplicationEventPublisher
→ 이벤트를 발행하고 구독하는 모델을 편리하게 지원
•
ResourceLoader
→ 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
정리
•
보통 BeanFactory를 사용하기 보다는 부가기능이 포함된 ApplicationContext를 사용
•
BeanFactory나 ApplicationContext를 스프링 컨테이너라고 한다.
다양한 설정 형식 - JAVA, XML
스프링 컨테이너는 인터페이스를 이용하여 다양한 형식의 설정 형식을 지원할 수 있도록 설계되어 있다.
어노테이션 기반 JAVA 코드 설정 사용
•
new AnnotationConfigApplicationContext(AppConfig.class)
•
AnnotationConfigApplicationContext 클래스를 사용하면서 자바 코드로된 설정 정보를 넘기면 된다.
XML 설정 사용(과거)
•
요즘은 잘 사용하지 않지만 아직 많은 레거시 프로젝트들이 XML로 되어 있고, 컴파일 없이 빈 설정 정보를 바꿀 수 있다는 장점이 있어, 한번쯤 배워보면 좋다.
•
GenericXmlApplicationContext 를 사용해 xml 설정 파일을 넘기면 된다.
appConfig.xml
•
<bean></bean>으로 스프링 빈을 생성
•
<constructor-arg/>로 생상자로써 파라미터를 넘겨줌
<?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.OrederServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository"/>
<constructor-arg name="discountPolicy" ref="discountPolicy"/>
</bean>
<bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy"/>
</beans>
Java
복사
XML 스프링 조회
@Test
void xmlAppContext() {
GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
Java
복사
스프링 빈 설정 메타 정보 - BeanDefinition
스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까?
→ 그 중심에는 BeanDefinition 이라는 추상화가 존재
1. BeanDefinition이란? == 빈 설정 메타정보
•
쉽게 이야기해서 역할과 구현을 개념적으로 나눈 것이다!
•
XML을 읽어서 BeanDefinition을 만들면 된다.
→ <bean>당 하나의 메타 정보 생성
•
자바 코드를 읽어서 BeanDefinition을 만들면 된다.
→ @Bean당 하나의 메타 정보 생성
•
스프링 컨테이너는 자바 코드인지, XML인지 몰라도 된다. 오직 BeanDefinition만 알면 된다.
스프링 컨테이너는 이 BeanDefinition이라 불리는 메타 정보를 가지고 스프링 빈을 생성한다.
클래스 다이어그램
객체 다이어그램
•
AnnotationConfigApplicationContext 는 AnnotatedBeanDefinitionReader 를 사용해서 AppConfig.class 를 읽고 BeanDefinition 을 생성
•
GenericXmlApplicationContext 는 XmlBeanDefinitionReader 를 사용해서 appConfig.xml 설정 정보를 읽고 BeanDefinition 을 생성
•
새로운 형식의 설정 정보가 추가되면, XxxBeanDefinitionReader를 만들어서 BeanDefinition 을 생성
2. BeanDefinition 확인하기
•
BeanDefinition 정보
◦
BeanClassName : 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
◦
factoryBeanName : 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
◦
factoryMethodName : 빈을 생성할 팩토리 메서드 지정, 예) memberService
◦
Scope : 싱글톤(기본값)
◦
abstract : 상속 관계
◦
autowireMode : 자동으로 종속성을 주입할 때 이용
◦
dependencyCheck : 새로 생성되고 구성된 개체의 개체 참조에 대한 Spring 종속성 검사를 활성화
→ true일경우 Spring은 구성 후 모든 속성(기본 또는 컬렉션이 아님)이 설정되었는지 확인
◦
autowireCandidate : autowireCandidateResolver를 통해 autowire 후보를 결정
◦
primary : 여러 bean이 단일 값 종속성에 autowired 후보인 경우 특정 bean에 우선권을 부여해야 함을 나타냄
◦
lazyInit : 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연처리 하는지 여부
◦
InitMethodName : 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
◦
DestroyMethodName : 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
◦
Constructor arguments, Properties : 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
◦
defined in : 어디에서 해당 빈이 정의되었는지에 대한 정보
어노테이션 기반 JAVA 코드 설정일 경우
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);
}
}
}
Java
복사
XML 설정일 경우
GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
@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);
}
}
}
Java
복사
정리
•
BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수 도 있다. 하지만 실무에서 BeanDefinition을 직접 정의하거나 사용할 일은 거의 없다.
•
BeanDefinition에 대해서는 너무 깊이있게 이해하기 보다는, 스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용하는 것 정도만 이해
•
가끔 오픈 소스에서 BeanDefinition을 직접 생성해 사용하는 경우가 있기 때문에, 알아는 두자!