티스토리 뷰
[SpringBoot] 스프링 핵심 원리 섹션 4 | 스프링 컨테이너와 스프링 | 스프링 빈 생성 과정 | BeanFactory
YouJungJang 2023. 11. 21. 17:47본 포스팅은 인프런 김영한 강사님의 스프링 부트 핵심 원리 강의 섹션 4를 수강하고 배운 점을 정리했습니다.
스프링 컨테이너 생성 과정
1. 스프링 컨테이너 생성
- 스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다. 아래와 같이 AppConfig.class를 구성 정보로 지정했다.
new AnnotationConfigApplicationContext(AppConfig.class)
2. 스프링 빈 등록
AppConfig.class
@Configuration
public class AppConfig {
@Bean
public static MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
return new RateDiscountPolicy();
// return new FixDiscountPolicy();
}
}
스프링 컨테이너는 AppConfig 정보를 사용해서 스프링 컨테이너의 스프링 빈 저장소에 객체를 등록한다.
스프링 컨테이너의 스프링 빈 저장소
빈 이름 | 빈 객체 |
memberService | MemberServiceImpl@x01 |
orderService | OrderServiceImpl@x02 |
memberRepository | MemoryMemberRepository@x03 |
discountPolicy | RateDiscountPolicy@x04 |
- 스프링 컨테이너는 설정 정보를 참고해서 의존 관계를 주입(DI)한다.
⚠️ 빈 이름은 항상 다른 이름을 부여해야 한다.
컨테이너에 등록된 모든 빈 조회
1. 스프링 컨테이너에 등록된 '모든' 빈 정보 출력 테스트
package hello.core.beanfind;
import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
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("name = " + beanDefinitionName + " object= " + bean);
}
}
}
- 실행하면 스프링에 등록된 모든 빈 정보를 출력할 수 있다.
- ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
- ac.getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회한다.
팁: 위와 같은 for문 작성 시 'iter' 단축키 선택하면 자동으로 for문이 생성된다.
결과 - 스프링 내부에서 등록한 빈도 함께 출력됨.
name = org.springframework.context.annotation.internalConfigurationAnnotationProcessor object= org.springframework.context.annotation.ConfigurationClassPostProcessor@6475472c
name = org.springframework.context.annotation.internalAutowiredAnnotationProcessor object= org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@3f07b12c
name = org.springframework.context.annotation.internalCommonAnnotationProcessor object= org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@4bd1f8dd
name = org.springframework.context.event.internalEventListenerProcessor object= org.springframework.context.event.EventListenerMethodProcessor@7096b474
name = org.springframework.context.event.internalEventListenerFactory object= org.springframework.context.event.DefaultEventListenerFactory@3e14c16d
name = appConfig object= hello.core.AppConfig$$EnhancerBySpringCGLIB$$f65d639a@3c989952
name = memberRepository object= hello.core.member.MemoryMemberRepository@784b990c
name = memberService object= hello.core.member.MemberServiceImpl@3d3ba765
name = orderService object= hello.core.Order.OrderServiceImpl@25bc0606
name = discountPolicy object= hello.core.discount.RateDiscountPolicy@5d1659ea
Process finished with exit code 0
2. 내가 만든 것만 보고 싶다면 '애플리케이션 빈' 출력하기
@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);
}
}
}
스프링이 내부에서 사용하는 빈은 getRole()로 구분할 수 있다.
ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
BeanDefinition.ROLE_APPLICATION
출력 결과
name = appConfig object= hello.core.AppConfig$$EnhancerBySpringCGLIB$$f65d639a@6475472c
name = memberRepository object= hello.core.member.MemoryMemberRepository@3f07b12c
name = memberService object= hello.core.member.MemberServiceImpl@4bd1f8dd
name = orderService object= hello.core.Order.OrderServiceImpl@7096b474
name = discountPolicy object= hello.core.discount.RateDiscountPolicy@3e14c16d
스프링 빈 조회 - 동일한 타입이 둘 이상
1. 같은 타입 두 개 이상 사용
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
void findByTypeDuplicate(){
MemberRepository bean = ac.getBean(MemberRepository.class);
}
@Configuration
static class SameBeanConfig{
@Bean
public MemberRepository memberRepository1(){
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2(){
return new MemoryMemberRepository();
}
}
}
기존에 Config.class 파일 대신 밑에서 작성해 준 새로운 Config 클래스를 정보로 넘겨준다.
-> 빈으로 등록되는 것은 같은 타입의 빈 객체 두 개.
여기서 findByType실행 시 오류가 발생한다.
오류 메시지 출력
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.member.MemberRepository' available: expected single matching bean but found 2: memberRepository1,memberRepository2
같은 타입 key, value로 분리해서 출력
@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);
org.assertj.core.api.Assertions.assertThat(beansOfType.size()).isEqualTo(2);
}
결과
key = memberRepository1value = hello.core.member.MemoryMemberRepository@5a85c92
key = memberRepository2value = hello.core.member.MemoryMemberRepository@32811494
beansOfType = {memberRepository1=hello.core.member.MemoryMemberRepository@5a85c92, memberRepository2=hello.core.member.MemoryMemberRepository@32811494}
스프링 빈 조회 - 상속 관계
부모 타입으로 조회하면, 자식 타입도 함께 조회한다.
가장 밑 단에 있는 4, 5, 6, 7은 자식 클래스가 없으므로 호출했을 때 자기 자신만 호출된다.
그런데 1번은 2, 3 뿐만 아니라 4, 5, 6, 7도 자식으로 갖는 최고 부모이므로 호출 시 나머지 클래스가 모두 호출된다.
같은 원리로 모든 자바 객체의 최고 부모인 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));
}
}
위 코드를 실행하면 모든 스프링 빈의 정보가 콘솔에 출력된다.
BeanFactory와 ApplicationContext
BeanFactory
- 스프링 컨테이너의 최상위 인터페이스다.
- 스프링 빈을 관리하고 조회하는 역할을 담당한다.
- getBean()을 제공한다.
- 지금까지 우리가 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능이다.
ApplicationContext
- BeanFactory 기능을 모두 상속받아서 제공한다.
- 빈을 관리하고 검색하는 기능을 BeanFactory가 제공해 주는데, 그러면 둘의 차이가 뭘까?
- 애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수많은 부가기능이 필요하다.
ApplicatonContext가 제공하는 부가기능
- 메시지소스를 활용한 국제화 기능: 예) 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
- 환경변수: 로컬, 개발, 운영 등을 구분해서 처리
- 애플리케이션 이벤트: 이벤트를 발행하고 구독하는 모델을 편리하게 지원
- 편리한 리소스 조회: 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
다양한 설정 형식 지원 - 자바 코드, XML
스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있게 유연하게 설계되어 있다
Xml 기반의 스프링 빈 설정 정보: AppConfig와 같은 설정 정보
<?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>