스프링 컨테이너 & 스프링 빈

D A S H B O A R D
D E V E L O P
S E C U R I T Y
 스프링 컨테이너 생성 과정
 스프링 빈 조회
  BeanFactory와 ApplicationContext
 다양한 설정 형식 - JAVA, XML
 스프링 빈 설정 메타 정보 - BeanDefinition
Reference

 스프링 컨테이너 생성 과정

보통 Main 매서드 안에서 ApplicationContext로 스프링 컨테이너를 생성한다.
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
Java
복사
ApplicationContext를 스프링 컨테이너라고 한다.
ApplicationContext는 인터페이스!!
ApplicationContext는 인터페이스이기 때문에 XML 기반, 어노테이션 기반 자바 설정 클래스로도 만들 수 있다.
위 코드는 어노테이션 기반 자바 설정 클래스로 스프링 컨테이너를 만드는 것으로, ApplicationContext 의 구현체는 AppConfig.class가 된다.
좀 더 깊은 내용은 3번BeanFactory와 ApplciationContext 확인!!

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이라 불리는 메타 정보를 가지고 스프링 빈을 생성한다.

클래스 다이어그램

객체 다이어그램

AnnotationConfigApplicationContextAnnotatedBeanDefinitionReader 를 사용해서 AppConfig.class 를 읽고 BeanDefinition 을 생성
GenericXmlApplicationContextXmlBeanDefinitionReader 를 사용해서 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을 직접 생성해 사용하는 경우가 있기 때문에, 알아는 두자!