Spring ‐ 스프링 컨테이너와 스프링 빈 - dnwls16071/Backend_Study_TIL GitHub Wiki

📚 IoC(Inversion of Control), DI(Dependency Injection), 그리고 컨테이너

  • IoC(Inversion of Control) : 프로그램 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것
  • 프레임워크(framework) vs 라이브러리(library)
    • 프레임워크는 내가 작성한 코드를 제어하고 대신 실행하는 것을 말한다.
    • 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 라이브러리라고 한다.

📚 스프링 컨테이너 생성

  • ApplicationContext를 스프링 컨테이너라고 한다.
  • ApplicationContext는 인터페이스이다.

스크린샷 2025-01-20 오전 12 15 19

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();
	}
}

스크린샷 2025-01-20 오전 12 15 52

스크린샷 2025-01-20 오전 12 20 10

스크린샷 2025-01-20 오전 12 20 54

  • 스프링 컨테이너는 설정 정보(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

스크린샷 2025-01-20 오전 1 45 48

  • BeanFactory

    • 스프링 컨테이너의 최상위 인터페이스이다.
    • 스프링 빈을 관리하고 조회하는 역할을 담당한다.
  • ApplicationContext(일반적으로 많이 사용되는 인터페이스)

    • BeanFactory 기능을 모두 상속받아서 제공한다.
    • BeanFactory 기능 뿐만 아니라 수많은 부가 기능을 ApplicationContext에서 제공한다.

스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있게 유연하게 설계되어 있다. 어노테이션 기반 자바 코드 설정 방법과 XML 등 여러 방법들이 있다.

📚 스프링 빈 설정 메타 정보 - BeanDefinition

스크린샷 2025-01-20 오전 1 57 20

  • 스프링 컨테이너 자체는 BeanDefinition만 알고 있어도 다양한 형식의 설정 정보를 지원한다.