아래 포스팅에서 스프링 빈을 직접 조회하는 방법에 대해서 알아봤습니다. 아래 포스팅에서는 이름을 통해 빈을 찾았다면 이번 포스팅에서는 빈을 조회하는 다른 여러 가지 방법에 대해서 코드로 작성해 보았습니다.
스프링 컨테이너와 스프링 빈(Bean)
스프링 컨테이너란, 스프링 빈(Bean)을 저장하고 관리하는 저장소입니다. 스프링 빈(Bean)은 스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트입니다. 즉, 스프링 컨테이너가
silver-programmer.tistory.com
이번 포스팅은 위 포스팅에서 작성한 코드를 이어서 사용하니 위의 포스팅을 먼저 보고 읽는 것이 좋을 것 같습니다!!
// AppConfig.class
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(
memberRepository(),
discountPolicy());
}
@Bean
private static DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
@Bean
private static MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
<!--appConfig.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>
class CoreApplicationTests {
AnnotationConfigApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
OrderService orderService = (OrderService) ac.getBean("orderService");
assertThat(orderService).isInstanceOf(OrderService.class);
}
@Test
@DisplayName("이름 없이 타입만으로 조회")
void findBeanByType() {
OrderService memberService = ac.getBean(OrderService.class);
assertThat(memberService).isInstanceOf(OrderServiceImpl.class);
}
@Test
@DisplayName("구체 타입으로 조회")
void findBeanByConcreteType() {
OrderServiceImpl memberService = ac.getBean("orderService",
OrderServiceImpl.class);
assertThat(memberService).isInstanceOf(OrderServiceImpl.class);
}
@Test
@DisplayName("등록되지 않은 빈 이름으로 조회 시 에러를 내 뱉는다.")
void findBeanByNameX() {
assertThrows(NoSuchBeanDefinitionException.class, () ->
ac.getBean("noBean", OrderService.class));
}
AnnotationConfigApplicationContext ac2 = new
AnnotationConfigApplicationContext(TestConfig.class);
@Configuration
static class TestConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
@Test
@DisplayName("타입으로 조회시 같은 타입을 가진 빈이 둘 이상이면, 에러를 내 뱉는다.")
void findBeanByTypeDulicated() {
assertThrows(NoUniqueBeanDefinitionException.class, () ->
ac2.getBean(MemberRepository.class));
}
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByTypeWithName() {
MemberRepository memberRepository = ac2.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}
@Test
@DisplayName("특정 타입을 모두 조회")
void findAllBeanByType() {
// getBeansOfType()을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.
Map<String, MemberRepository> beansOfType = ac2.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);
}
@Test
@DisplayName("빈 설정 메타정보 확인")
void findApplicationBean() {
String[] beanDefinitionNames = ac2.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition =
ac2.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
System.out.println("beanDefinitionName = " + beanDefinitionName + ", " +
" beanDefinition = " + beanDefinition);
}
}
}
}
특히 가장 아래 메소드는 BeanDefinition을 이용하여 어떤 정보를 볼 수 있는지 확인할 수 있습니다. 출력결과 아래와 같은 정보를 볼 수 있습니다.
beanDefinitionName = coreApplicationTests.TestConfig, beanDefinition = Generic bean: class [hello.core.CoreApplicationTests$TestConfig$$SpringCGLIB$$0]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null
beanDefinitionName = memberRepository1, beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=coreApplicationTests.TestConfig; factoryMethodName=memberRepository1; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in hello.core.CoreApplicationTests$TestConfig
beanDefinitionName = memberRepository2, beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=coreApplicationTests.TestConfig; factoryMethodName=memberRepository2; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in hello.core.CoreApplicationTests$TestConfig
각각의 속성들을 정리해 보았습니다.
- BeanClassName: 생성할 빈의 클래스 명 (자바 설정처럼 팩토리 역할의 빈을 사용하면 null입니다.)
- scope: 싱글톤 (기본값)
- lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때까지 최대한 생성을 지연 처리 하는지 여부
- factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름 (ex. appConfig, TestConfig)
- factoryMethodName: 빈을 생성할 팩토리 메서드 지정 (ex. memberRepository1, memberRepository2)
- InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
- DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
- Constructor arguments, Properties: 의존관계 주입에서 사용 (자바 설정처럼 팩토리 역할의 빈을 사용하면 없습니다.)
스프링 컨테이너는 BeanDefinition에만 의존하도록 설계되어 있습니다. 즉, 개발자가 직접 BeanDefintion 구현체를 위와 같이 만들어 정의할 수 있습니다. (실제로는 거의 하는 일이 없지만요.)
@Bean 또는 설정정보 하나당(<bean>), 하나의 메타정보를 생성하여 BeanDefinition을 만듭니다. 스프링 컨테이너는 실제 설정 정보가 아닌 메타정보(BeanDefinition)만을 보고 이를 기반으로 스프링 빈을 생성합니다.
- 위 그림에서, AnnotationConfigApplicationContext는 AnnotatedBeanDefinitionReader를 사용해 AppConfig.class를 읽고 BeanDefinition을 생성합니다.
- GenericXmlApplicationContext는 XmlBeanDefinitionReader를 사용해 appConfig.xml 설정 정보를 읽고 BeanDefinition을 생성합니다.
- 새로운 형식의 설정 정보가 추가되면, XxxBeanDefinitionReader를 만들어서 BeanDefinition을 생성하 면 된다
이렇게 BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수 있지만, 실무에서 이렇게 직접 정의하거나 사용할 일은 거의 없다고 합니다. 따라서 BeanDefinition에 대해서 너무 깊이 있게 이해하려고 하기보다는, 스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용한다는 정도만 이해하면 될 것 같습니다.
[전체 소스코드]
GitHub - eunhwa99/Spring
Contribute to eunhwa99/Spring development by creating an account on GitHub.
github.com
[참고자료]
김영한, "스프링 핵심 원리 - 기본편", 인프런
'Spring Boot' 카테고리의 다른 글
@Autowired로 의존관계 주입하기 (1) | 2023.12.02 |
---|---|
@ComponentScan으로 스프링 빈 스캔하기 (2) | 2023.12.02 |
스프링 컨테이너와 스프링 빈(Bean) (0) | 2023.11.29 |
스프링 부트: Spring Initializr 예제 프로젝트 생성 (2) | 2023.11.27 |
ORM 이란? (0) | 2023.11.19 |