스프링 컨테이너가 생성되는 과정을 알아보자.
// 스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplication(AppConfig.class);
ApplicationContext
: 인터페이스. 스프링 컨테이너new AnnotationConfigApplication(AppConfig.class);
: ApplicationContext의 구현체.참고
더 정확히는 스프링 컨테이너를 부를 때, BeanFactory, ApplicationContext로 구분해서 이야기함. BeanFactory를 직접 사용하는 경우는 거의 없으므로 일반적으로 ApplicationContext를 이야기한다.
new AnnotationConfigApplicationContext(AppConfig.class)
AppConfig.class
를 구성 정보로 지정했다.빈 이름
- 빈 이름: 메서드 이름 사용.
- 빈 이름 직접 부여 가능
@Bean(name="memberService2")
주의: 빈 이름은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.
애매해 보이고 잘 모르는겠는건 무조건 하지마라.
참고
스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다. 그런데 이렇게 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다. 여기서는 이해를 돕기 위해 개념적으로 나누어 설명했다. 자세한 내용은 의존관계 자동 주입에서 다시 설명하겠다.
정리
스프링 컨테이너를 생성하고, 설정(구성) 정보를 참고해서 스프링 빈도 등록하고, 의존관게도 설정했다.
이제 스프링 컨테이너에서 데이터를 조회해보자!
스프링 컨테이너에 실제 스프링 빈들이 잘 등록 되었는지 확인해보자
package hello.core.beanfind;
import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
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);
}
}
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
// Bean의 Definitation에 대한 메타 정보
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
// Role ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
// Role ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean);
}
}
}
}
ac.getBeanDefinitionNames()
: 스프링에 등록된 모든 빈 이름을 조회한다.ac.getBean()
: 빈 이름으로 빈 객체(인스턴스)를 조회한다.name = org.springframework.context.annotation.internalConfigurationAnnotationProcessor object = org.springframework.context.annotation.ConfigurationClassPostProcessor@5a755cc1
name = org.springframework.context.annotation.internalAutowiredAnnotationProcessor object = org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@7ae42ce3
name = org.springframework.context.annotation.internalCommonAnnotationProcessor object = org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@4f5991f6
name = org.springframework.context.event.internalEventListenerProcessor object = org.springframework.context.event.EventListenerMethodProcessor@484094a5
name = org.springframework.context.event.internalEventListenerFactory object = org.springframework.context.event.DefaultEventListenerFactory@38234a38
name = appConfig object = hello.core.AppConfig$$SpringCGLIB$$0@63fbfaeb
name = memberService object = hello.core.member.MemberServiceImpl@602e0143
name = memberRepository object = hello.core.member.MemoryMemberRepository@2c07545f
name = orderService object = hello.core.order.OrderServiceImpl@e57b96d
name = discountPolicy object = hello.core.discount.RateDiscountPolicy@32c726ee
getRole()
로 구분할 수 있다.Role ROLE_APPLICATION
: 일반적으로 사용자가 정의한 빈Role ROLE_INFRASTRUCTURE
: 스프링이 내부에서 사용하는 빈name = appConfig object = hello.core.AppConfig$$SpringCGLIB$$0@5a755cc1
name = memberService object = hello.core.member.MemberServiceImpl@7ae42ce3
name = memberRepository object = hello.core.member.MemoryMemberRepository@4f5991f6
name = orderService object = hello.core.order.OrderServiceImpl@484094a5
name = discountPolicy object = hello.core.discount.RateDiscountPolicy@38234a38
ac.getBean(빈이름, 타입)
ac.getBean(타입)
NoSuchBeanDefinitionException: No bean named 'xxxx' availabe
package hello.core.beanfind;
import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.*;
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("구체 타입으로 조회")
// 가능 여부를 확인하기 위함. 구체화에 의존 시 유연성이 떨어진다.
void findBeanByName2() {
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("빈 이름으로 조회 X")
void findBeanByNameX() {
// ac.getBean("xxxxx", MemberService.class);
org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("xxxxx", MemberServiceImpl.class));
}
}
ac.getBeansOfType()
을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.@Autowired
같은데 다 적용이 됨package hello.core.beanfind;
import hello.core.AppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByTypeDuplication() {
Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
()-> ac.getBean(MemberRepository.class));
}
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
void findBeanByName() {
MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
/**
* 이렇게 한번에 조회하는 로직 같은게, 나중에 Autowired 실행할 때 다 적용이 된다.
*/
@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);
}
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
Object
타입으로 조회하면, 모든 스프링 빈을 조회한다.package hello.core.beanfind;
import hello.core.AppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.boot.test.autoconfigure.webservices.server.WebServiceServerTest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
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(RateDiscountPolicy.class);
}
@Test
@DisplayName("특정 하위 타입으로 조회")
// 구체화에 의존하므로 권장하지 않음.
void findBeanBySubType() {
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("부모 타입으로 모두 조회하기")
void findAllBeanByParentType() {
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));
}
}
@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();
}
}
}
💬컨테이너가 자동으로 의존관계 주입해주는거 사용하지, 개발자가 직접 이 코드들을 사용할 일 거의 없음. 보통 개발자들이 개발하게 될
OrderServiceImpl
같은 코드에는 의존관계 주입에 대한 것들을 아예 명시 안함. bean을 주입하는 코드도 없음. 실제 개발 시 bean을 주입할 코드를 사용할 일이 거의 없음.
✔️ 근데 왜 설명할까?
많이 쓰이는 기본기능이라서. + 가끔 순수한 자바 애플리케이션에서 스프링 컨테이너를 생성해서 쓸 일이 있음. 그럴 때 알아두면 좋음. 부모타입 조회 시 자식이 어디까지 조회되냐, 이런 것도 알아두면 좋음.
getBean()
을 제공한다.정리
1. ApplicationContext: BeanFactory의 기능을 상속받는다.
2. ApplicationContext: 빈 관리 기능 + 편리한 부가 기능 제공
3. BeanFactory를 직접 사용할일 거의 없다. 부가 기능이 포함된 ApplicationContext 주로 사용
4. BeanFactory, ApplicationContext를 스프링 컨테이너라 한다.
스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 유연하게 설계되어 있다. (자바코드, xml, groovy등)
다양한 정보의 설정 정보를 스프링이 유연하게 수용할 수 있음에 초점!
과거에는 xml로 설정 정보를 설정했으나, 요즘엔 스프링 부트에서 자바코드만으로 설정이 가능해지면서 거의 사용하지 않음. 아직도 해당 파일을 빈번히 볼 수 있으므로 사용법 참조 정도로 확인할 것!
new AnnotationConfgApplicationContext(AppConfig.class)
GenericXmlApplicaionContext
를 사용하면서 xml
설정 파일 넘기기XmlAppConfig 사용 자바 코드(테스트)
package hello.core.xml;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class XmlAppContext {
@Test
void xmlAppContext() {
ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = ac.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
}
}
xml 기반의 스프링 빈 설정 정보
src/main/resources/appConfg.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>
BeanDefinition
이라는 추상화가 있다.BeanDefinition
을 빈 설정 메타정보라 한다.@Bean
, <bean>
당 각각 하나씩 메타 정보가 생성된다. 코드 레벨로 더 깊이 들여다보자
AnnotationConfigApplicationContext
는 AnnotatedBeanDefinitionReader
를 사용해서 AppConfig.class
를읽고 BeanDefinition
을 생성한다.GenericXmlApplicationContext
는 XmlBeanDefinitionReader
를 사용해서 appConfig.xml
설정 정보를읽고 BeanDefinition
을 생성한다.XxxBeanDefinitionReader
를 만들어서 BeanDefinition
을 생성하면 된다.BeanDefinition 정보
package hello.core.beandefinition;
import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
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);
}
}
}
}
직접 빈을 등록 (xml 이용)
class명이 직접 명시되어 있음
FactoryMethod이용
(자바코드를 이용하는 경우. 직접 메서드를 호출해서 생성되는 방식.)
factoryBeanName=appConfg; factoryMethodName=orderService;
로 등록되어 있음.정리
- 스프링은
BeanDefinition
을 통해 스프링에 설정 메타정보를 추상화한다.- 스프링 빈을 만드는 두가지 방법: 직접 등록, 팩토리빈을 통해 등록
(자바 Config는 후자 방식)
- 메타 정보를 추상화한다 정도만 알면 됨. 가끔 외부 라이브러리에서
BeanDefinition
이라는 것이 보일 때가 있는데, 이때 이러한 매커니즘을 떠올릴 수 있으면 된다.