스프링 컨테이너와 스프링 빈
이전 강의까지에서는 객체지향에 대한 전반적인 개념에서 시작하여 간단한 예제를 통해 할인 시스템을 만들어 보았다. 예제를 통해 객체지향적 사고를 배울 수 있었고, 이제부터는 본격적으로 스프링의 원리에 대해서 정리한다.
스프링 컨테이너 생성
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
...
}
public static void min(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
}
위 코드는 전 강의에서 의존성 주입을 스프링 컨테이너를 이용해서 하도록 리펙토링한 코드이다. 위 코드를 이해하기 위해서는 다음과 같은 사항을 알고 있으면 좋다.
- ApplicationContext 는 인터페이스이며 스프링 컨테이너라고 한다.
- 스프링 컨테이너는 xml, annotation 기반으로 만들 수 있다.
- AnnotationConfigApplicationContext는 ApplicationContext 인터페이스의 구현체이다.
스프링 컨테이너 생성 과정
1. 스프링 컨테이너 생성
스프링 컨테이너가 생성되면 컨테이너 안에 스프링 빈 저장소가 생기게 된다. 이 때 스프링 빈은 빈 이름, 빈 객체로 key-value 형태를 가진다.
컨테이너 생성 시에는 구성 정보를 지정해주어야 하며 여기서는 AppConfig.class를 구성 정보로 지정해주었다.
2. 스프링 빈 등록
컨테이너는 구성 정보를 이용하여 스프링 빈을 등록하며 이 때 빈 이름은 메소드 명을 사용한다.
빈 이름은 보통 메서드 명을 사용하나 아래와 같이 이름을 변경할 수도 있다. 이 때 주의할 점은 각 빈은 싱글톤 패턴으로 각각 한 개씩만 존재해야 하므로 모든 빈의 이름은 각기 달라야 한다!
@Bean(name="memberService2")
3. 스프링 빈 의존관계 설정
스프링 컨테이너는 설정 정보(AppConfig.class)를 참고해서 **의존관계를 주입(DI)**한다. 이 때 빈을 생성하고 주입하는 단계가 나누어져 처리된다.(빈 생성, 주입하는 스프링의 라이프 사이클이 서로 다르다)
스프링 빈 조회
빈 이름, 타입으로 조회
스프링 컨테이너에 등록된 빈을 조회할 때에는 빈 이름 혹은 타입으로 조회할 수 있다.
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);
}
}
위처럼 ApplicationContext에서 getBean 메소드를 통해 빈을 조회할 수 있으며 메소드 오버라이딩이 되어 있어 서로 다른 인자로 조회할 수 있다.
구현체로 조회
위 예제에서는 인터페이스로 빈을 조회하였고, 실제 config에도 인터페이스로 등록을 하였지만 해당 인터페이스의 구현체로도 빈을 조회할 수 있다.
class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName2() {
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
빈이 조회되지 않을 시
만약 ApplicationContext에서 빈이 조회되지 않을 시, 스프링은 NoSuchBeanDeficitionException을 던진다!
class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByNameX() {
Assertions.assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean("xxxxx", MemberService.class));
}
}
동일한 타입이 둘 이상일 때
만약 동일한 타입의 빈이 다른이름으로 등록되어 있을 때, 이름을 명시하지 않고 타입으로만 빈을 조회한다면 스프링에서는 NoUniqueBeanDefinitionException 예외를 던질 것이다. 이 때에는 이름을 명시해서 조회하면 되며, 만약 동일 타입의 빈을 모두 조회하고 싶은 경우에은 getBeansOfType 메소드를 사용하면 된다.
class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByTypeDuplicate() {
//DiscountPolicy bean = ac.getBean(MemberRepository.class);
assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(MemberRepository.class));
}
@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);
}
@Configuration
static class SameBeanConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
상속 관계의 빈 조회
스프링에서 빈을 조회할 때 해당 빈을 상속한 객체가 있다면 해당 자식 객체까지 모두 조회되게 된다. 해당 개념을 모식도로 나타내면 아래와 같다.
자바의 모든 객체는 Object의 자식 객체이기 때문에 Object 클래스로 빈을 조회하게 되면 스프링의 모든 빈이 조회된다.
@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));
}
}
BeanFactory와 ApplicationContext
BeanFactory
- 스프링 컨테이너의 최상위 인터페이스이며 스프링 빈을 관리, 조회하는 역할을 담당한다.
- 위 예제에서 사용했던 getBean() 등의 빈 조회 메소드들은 전부 여기서 제공하는 기능이다.
ApplicationContext
- BeanFactory의 기능을 모두 상속받아 제공하며 애플리케이션 개발시 필요한 부가 기능을 추가로 제공한다.
- MessageSource: 한국 접근은 한국어로, 영어권 접근은 영어로 출력 등
- EnvironmentCapable: 로컬, 개발, 운영 환경을 구분해서 처리해줌
- ApplicationEventPublisher: 이벤트 발생, 구독 모델 지원
- ResourceLoader: 파일, 외부 리소스 편리하게 조회
위에서 살펴보았듯이 ApplicationContext는 BeanFactory의 기능을 모두 지원할 뿐만 아니라 개발에 필요한 부가기능도 함께 많이 제공하므로 실제 개발에서는 BeanFactory를 직접 사용하기 보다는 ApplicationContext를 사용한다.
또한 BeanFactory 혹은 ApplicationContext를 전부 스프링 컨테이너라고 할 수 있으며 보통 사용되는 것은 ApplicationContext이므로 이것을 스프링 컨테이너라고 지칭한다.
BeanDefinition
스프링 빈은 자바 클래스를 이용해서 등록할 수 있고 xml 파일을 이용하거나 그 외의 여러 방법으로도 등록이 가능하다.
이는 스프링 컨테이너가 참조하는 BeanDefinition(빈 메타정보)이 추상화 되어 있기 때문인데 클래스, xml을 읽어 Bean을 만드는 과정을 모식도로 나타내면 아래와 같다.
ApplicationContext의 각 구현체는 각각 다른 이름의 BeanDefinitionReader 객체를 가지며 이 클래스를 통해 각각 자바 클래스, xml 등의 파일에서 빈 설정 정보를 가져와 BeanDefinition을 생성한다.
ApplicationContext 입장에서는 BeanDefinition만을 참조하면 되므로 configuration이 어떤 방식으로 이뤄지는 지 알 필요가 없고 이는 SOLID 원칙의 DIP(구체화 보다는 추상화에 의존하라)를 잘 지킨 예라고 볼 수 있다.
'Spring' 카테고리의 다른 글
SpringBoot @Transactional이 동작하지 않는 이유와 해결법 (1) | 2024.10.02 |
---|---|
Spring @RequestBody로 데이터 바인딩 오류 (0) | 2024.10.01 |
Spring Boot 애플리케이션 로그 설정하기 (0) | 2024.09.29 |
Spring Boot Actuator 엔드포인트가 작동하지 않는 문제 해결 (0) | 2024.09.28 |
Spring Boot Auto Configuration 작동 원리 (0) | 2024.09.28 |
댓글