Spring ‐ 스프링 컨테이너와 스프링 빈 - dnwls16071/Backend_Study_TIL GitHub Wiki
📚 IoC(Inversion of Control), DI(Dependency Injection), 그리고 컨테이너
- IoC(Inversion of Control) : 프로그램 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것
- 프레임워크(framework) vs 라이브러리(library)
- 프레임워크는 내가 작성한 코드를 제어하고 대신 실행하는 것을 말한다.
- 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 라이브러리라고 한다.
📚 스프링 컨테이너 생성
- ApplicationContext를 스프링 컨테이너라고 한다.
- ApplicationContext는 인터페이스이다.
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfiguration.class)
- 스프링 컨테이너를 구성 후 구성 정보
AppConfiguration
클래스를 구성 정보로 지정한다.
@Configuration
public class AppConfiguration {
@Bean
public MemberRepository memberRepository() {
return new MemberRepositoryImpl();
}
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
// return new RateDiscountPolicy();
}
}
- 스프링 컨테이너는 설정 정보(Ex. AppConfiguration Class)를 참고해서 의존관계를 주입한다.
📚 컨테이너에 등록된 모든 빈 조회
class ApplicationContextInfoTest {
//AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfiguration.class);
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfiguration.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);
}
}
}
실행 결과
// 스프링 내부에서 사용하는 빈
name = org.springframework.context.annotation.internalConfigurationAnnotationProcessor, object = org.springframework.context.annotation.ConfigurationClassPostProcessor@66ea1466
name = org.springframework.context.annotation.internalAutowiredAnnotationProcessor, object = org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@f415a95
name = org.springframework.context.annotation.internalCommonAnnotationProcessor, object = org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@cf65451
name = org.springframework.context.event.internalEventListenerProcessor, object = org.springframework.context.event.EventListenerMethodProcessor@724f138e
name = org.springframework.context.event.internalEventListenerFactory, object = org.springframework.context.event.DefaultEventListenerFactory@37eeec90
// 사용자가 정의한 빈
name = appConfiguration, object = com.jwj.springPrinciple.config.AppConfiguration$$SpringCGLIB$$0@32fe9d0a
name = memberRepository, object = com.jwj.springPrinciple.repository.MemberRepositoryImpl@c9413d8
name = memberService, object = com.jwj.springPrinciple.service.MemberServiceImpl@64da2a7
name = orderService, object = com.jwj.springPrinciple.service.OrderServiceImpl@46074492
name = discountPolicy, object = com.jwj.springPrinciple.discount.FixDiscountPolicy@d78795
- 애플리케이션 빈을 구분해서 확인하고 싶다면 아래와 같이 작성하면 된다.
class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfiguration.class);
//ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfiguration.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);
}
}
// Get the role hint for this BeanDefinition. The role hint provides the frameworks as well as tools an indication of the role and importance of a particular BeanDefinition.
// See Also:
// ROLE_APPLICATION, ROLE_SUPPORT, ROLE_INFRASTRUCTURE
@Test
@DisplayName("빈을 선택적으로 조회한다.")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
// 스프링 컨테이너 내부에서 사용되는 빈의 경우 ROLE_INFRASTRUCTURE으로 조회하면 된다.
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + ", object = " + bean);
}
}
}
}
❓AnnotationConfigApplicationContext vs ApplicationContext 차이점
getBeanDefinition()
메서드 호출 과정에서ApplicationContext
인터페이스는 해당 메서드를 지원하지 않는다.ApplicationContext
은 XML, 애노테이션, Java Config 등 다양한 설정 방식을 지원하는 인터페이스이다.AnnotationConfigApplicationContext
은 자바 기반의 설정(@Configuration)과 애노테이션을 사용하는 구현체이다.
📚 스프링 빈 조회 - 기본
ac.getBean(타입)
ac.getBean(빈 이름, 타입)
@Test
@DisplayName("특정 타입의 빈을 조회한다. - 1")
void findBeanByName() {
Object bean = ac.getBean("discountPolicy");
assertThat(bean).isInstanceOf(DiscountPolicy.class);
}
@Test
@DisplayName("특정 타입의 빈을 조회한다. - 2")
void findBeanByType() {
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
- 조회 대상 빈이 없다면 아래와 같은 예외가 발생한다.
@Test
@DisplayName("빈을 찾을 수 없다면 예외가 발생한다.")
void findBeanNotFound() {
assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean("XXXXX", MemberService.class));
}
📚 스프링 빈 조회 - 동일한 타입이 둘 이상인 경우
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfigClass.class);
@Test
@DisplayName("동일한 타입의 빈이 두 개 이상 조회될 경우 중복 오류가 발생한다.")
void findBeanByTypeDuplicate() {
// NoUniqueBeanDefinitionException 예외가 발생한다.
assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("동일한 타입의 빈을 모두 조회한다.")
void findAllBeanByType() {
Map<String, DiscountPolicy> map = ac.getBeansOfType(DiscountPolicy.class);
for (String s : map.keySet()) {
System.out.println("key = " + s);
System.out.println("value = " + map.get(s));
}
}
// DiscountPolicy 타입의 빈이 2개(정액제, 정률제)가 등록된 상태
@Configuration
static class SameBeanConfigClass {
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
}
}
📚 스프링 빈 조회 - 상속 관계
- 인터페이스의 구현도 넓은 의미에선 상속으로 볼 수 있다.
- 따라서 할인 정책 인터페이스를 조회하게 되면 할인 정책 인터페이스를 구현한 구현체 클래스 빈을 조회할 수 있게 된다.
public class ApplicationContextExtendTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 모두 조회한다.")
void findAllBeanParentType() {
Map<String, DiscountPolicy> map = ac.getBeansOfType(DiscountPolicy.class);
assertThat(map.size()).isEqualTo(2);
}
@Configuration
static class TestConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
}
📚 BeanFactory와 ApplicationContext
-
BeanFactory
- 스프링 컨테이너의 최상위 인터페이스이다.
- 스프링 빈을 관리하고 조회하는 역할을 담당한다.
-
ApplicationContext(일반적으로 많이 사용되는 인터페이스)
- BeanFactory 기능을 모두 상속받아서 제공한다.
- BeanFactory 기능 뿐만 아니라 수많은 부가 기능을 ApplicationContext에서 제공한다.
스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있게 유연하게 설계되어 있다. 어노테이션 기반 자바 코드 설정 방법과 XML 등 여러 방법들이 있다.
📚 스프링 빈 설정 메타 정보 - BeanDefinition
- 스프링 컨테이너 자체는 BeanDefinition만 알고 있어도 다양한 형식의 설정 정보를 지원한다.