// 스프링 컨테이너 생성
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ApplicationContext
를 스프링 컨테이너라고 하며 이는 인터페이스이다.
스프링 컨테이너는 XML을 기반으로도, 애노테이션 기반의 자바 설정 클래스로도 만들 수 있다.
📌 참고
더 정확히는 스프링 컨테이너를 부를 때BeanFactory
,ApplicationContext
로 구분해서 이야기한다.BeanFactory
를 직접 사용하는 경우는 거의 없으므로 일반적으로ApplicationContext
를 스프링 컨테이너라 한다.
new AnnotationConfigApplicationContext(AppConfig.class)
스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다. 여기서는 AppConfig.class
로 지정함.
스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.
빈 이름은 메서드 이름을 사용하는데 다음과 같이 직접 부여할 수 도 있다.
@Bean(name="memberService2")
다만, 빈 이름은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.
기존 AppConfig
에는 MemberService
와 OrderService
가 있는데 이 두 클래스를 생성하기 위해서는 각각 할인정책, 회원 리포지토리에 의존관계를 주입해줘야 한다. 이런 정보가 AppConfig
라는 구성정보 클래스에 담겨있기 때문에 이를 베이스로 스프링 빈 의존관계를 설정해준다.
스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다. 스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있는데 이처럼 자바 코드를 통해 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다.
package hello.core.beanfind;
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;
import static org.assertj.core.api.Assertions.assertThat;
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()
: 빈 이름으로 빈 객체(인스턴스)를 조회@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
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);
}
}
}
getRole()
로 구분할 수 있다.ROLE_APPLICATION
: 직접 등록한 애플리케이션 빈ROLE_INFRASTRUCTURE
: 스프링이 내부에서 사용하는 빈스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법은 다음과 같다.
ac.getBean(빈이름, 타입)
ac.getBean(타입)
조회 대상 스프링 빈이 없으면 NoSuchBeanDefinitionException: No bean named 'xxxxx' available
예외가 발생한다.
getBean()
을 통해 타입으로 빈을 조회할 수 있는데, 해당 타입의 빈이 하나가 아니라 둘 이상이라면 어떻게 될까?
타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이라면 오류가 발생한다. 이때는 빈 이름을 지정해야 한다.
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("동일한 타입이 둘 이상인 스프링 빈을 타입으로 조회할 경우 에러 발생")
void findBeanByTypeDuplicate() {
assertThatThrownBy(() -> {
DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
}).isInstanceOf(NoUniqueBeanDefinitionException.class);
}
@Configuration
static class SameBeanConfig {
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
}
}
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
void findBeanByName() {
MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
@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);
}
ac.getBeansOfType()
: 해당 타입의 모든 빈을 조회할 수 있다.부모 타입으로 조회하면, 자식 타입도 함께 조회한다. 모든 자바 객체의 최고 부모인 Object
타입으로 조회하면, 모든 스프링 빈을 조회하게 된다.
getBean()
을 제공함.다음과 같은 부가 기능을 제공한다.
스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있게 유연하게 설계되어 있다.
BeanDefinition
이라는 추상화를 통해 스프링은 다양한 설정 형식을 지원한다.
다시 말해, 역할과 구현을 개념적으로 나눈 것이다.
BeanDefinition
을 빈 설정 메타정보라고 한다. @Bean
, <bean>
당 각각 하나씩 메타 정보가 생성된다. 스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.
// 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용하는 것 정도만 이해하면 됨.