2022. 5. 13. 20:20ㆍLanguage`/Spring
스프링 컨테이너
스프링 컨테이너는 스프링 빈 객체들의 생명 주기를 관리하고, 생성된 Bean 객체들에 대해서 부가적인 기능을 제공하는 역할을 한다.
- 빈 객체가 Singleton일 경우 해당되는 정의
- Prototype일 경우 생명주기 전체를 관리하지 않고, 빈 객체의 초기화까지만 관여하고 이후에는 전혀 관여 X
일반적으로 코딩을하면 객체를 생성하는 것은 개발자가 해주는 일이다.
하지만 스프링 컨테이너는 IoC라는 제어의 역전 개념을 통해서 이러한 객체 관련 일들을 대신 해준다
그리고 의존 관계 주입(DI)도 스프링 컨테이너에서 빈 객체를 생성 후 자동으로 맺어준다
@Configuration으로 지정한 스프링 설정 클래스인 AppConfig를 "스프링 컨테이너"를 생성할 때 파라미터로 전달해준다
위 코드에서 ApplicationContext는 스프링 컨테이너의 인터페이스로써 많은 구현체들이 존재한다
- AnnotationConfigApplicationContext = 애노테이션에 의한 스프링 설정 클래스
따라서 스프링 컨테이너는 AppConfig라는 "스프링 설정 클래스"에 위치한 여러 객체들을 스프링 빈으로 생성해주고 서로간의 의존관계를 설정해준다
- 나중에 설명하겠지만 스프링 빈 객체는 Singleton으로 관리되므로 실제 객체를 스프링 빈으로 등록하는 것이 아닌 "CGLIB"라는 라이브러리를 통해서 해당 객체를 상속받은 "프록시 객체"가 스프링 빈으로 등록된다
1. 스프링 컨테이너 생성
AppConfig라는 스프링 설정 클래스를 토대로 스프링 컨테이너를 생성하였다 (With AnnotationConfigApplicationContext)
2. 스프링 빈 등록
컨테이너를 만들 때 파라미터로 넘어온 스프링 설정 클래스 정보를 사용해서 스프링 빈을 등록한다
여기서 default로 빈 이름은 메소드 이름으로 설정된다
Bean이름을 직접 부여해줄수는 있지만 반드시 빈 이름은 항상 다른 이름을 부여해야 한다는 점을 조심해야 한다.
따라서 그냥 default로 메소드 이름을 빈 이름으로 사용하는 것이 깔끔하다
3. 스프링 빈 의존관계 설정
스프링 컨테이너는 설정 클래스 정보를 참조해서 의존관계를 주입(DI)한다
등록된 빈 조회하기
크게 2가지로 나눌 수 있다
- 스프링상에 등록된 모든 빈 정보를 조회
- 내가 등록한 빈만 조회
※ 스프링상에 모든 빈 조회
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);
}
}
※ 내가 등록한 빈만 조회
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("애플리케이션 빈 출력")
void findApplicationBean(){
String [] beanDefinitionNames = ac.getBeanDefinitionNames();
for(String beanDefinitionName : beanDefinitionNames){
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
/*
ROLE_APPLICATION : 직접 등록한 스프링 빈
ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
*/
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
Object bean = ac.getBean(beanDefinitionName);
System.out.println("{Name : " + beanDefinitionName + " / Object : " + bean);
}
}
}
스프링상에서 사용되는 빈은 "각 빈들의 메타 정보를 저장하는 BeanDefinition"의 getRole()을 통해서 파악할 수 있다
일단 공통적으로 빈을 조회할때는 BeanFactory의 "getBean()"을 사용한다
이것도 2가지 조회 타입이 존재한다
- ~~.getBean(빈 이름, 타입)
- ~~.getBean(타입)
여기서 조회대상인 스프링 빈이 없다면 "NoSuchBeanDefinitionException"예외가 발생하게 된다
// 1. 빈 이름 + 타입으로 조회
MemberService memberService = ac.getBean("memberService", MemberService.class);
// 2. 타입으로만 조회
MemberService memberService = ac.getBean(MemberService.class);
// 3. 빈 이름 + 구체적 타입으로 조회
MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
여기서 구체적인 타입으로 조회하는 경우 나중에 다른 구체적 타입을 조회할 때 변경해줘야 하기 때문에 유연성이 떨어진다고 볼 수 있다
그리고 타입으로만 조회하는 경우 다음과 같은 조건이 존재한다면 문제가 발생할 수 있다
동일한 타입이 둘 이상일 경우
@Configuration
static class SampleConfig{
@Bean
public MemberRepository memberRepository1(){
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2(){
return new MemoryMemberRepository();
}
}
이 경우 "MemberRepository"라는 동일한 타입에 대해서 2가지 Bean이 존재한다
이 때 그냥 MemberRepository.class라는 타입으로만 Bean을 조회한다면 중복 오류가 발생할 것이다
@Test
@DisplayName("타입으로 조회할 때 같은 타입 2개 이상이면 중복 오류 발생")
void findBeanByTypeDuplicate(){
MemberRepository memberRepository = ac.getBean(MemberRepository.class);
}
따라서 동일한 타입에 대해서 둘 이상의 Bean이 존재한다면 반드시 빈 이름도 같이 파라미터로 넘겨줘야 한다
상속 관계가 존재할 경우, 부모 타입으로 조회하면 자식 타입들은 자동적으로 따라서 조회가 된다
만약 부모 타입으로 조회할 때 자식 타입이 둘 이상이면 이 때도 반드시 빈 이름을 지정해야 오류가 발생하지 않을 것이다
BeanFactory & ApplicationContext
BeanFactory Interface는 최상위 객체이고, ApplicationContext는 BeanFactory를 상속받은 Interface이다
ApplicationContext를 구현한 구현체는 AnnotationConfigApplicationContext, GenericXmlApplicationContext 등이 존재한다
BeanFactory
스프링 컨테이너의 최상위 인터페이스로써 "스프링 빈을 관리하고 조회(getBean)하는 역할"을 담당한다
BeanFactory를 직접적으로 사용하는 경우는 거의 없고 사실상 ApplicationContext를 사용한다
ApplicationContext
BeanFactory의 모든 기능을 상속받고 그에 더해서 부가적 기능도 제공해준다
- 메시지소스를 이용한 국제화 기능
이 기능은 그냥 영어권에서 접속하면 영어로 출력되고 한국에서 접속하면 한글로 출력하는 기능이다
- 환경변수
로컬/개발/운영 등을 구분해서 처리할 수 있게 도와준다
- 애플리케이션 이벤트
이벤트를 발생하고 구독하는 모델을 편리하게 지원해준다
- 편리한 리소스 조회
파일/클래스패스/외부 등에서 리소스를 편리하게 조회할 수 있게 해준다
AnnotationConfigApplicationContext는 "Annotation"을 통해서 스프링 설정을 할 때 사용한다
GenericXmlApplicationContext는 Annotation이 아닌 XML 파일을 통해서 스프링 설정을 할 때 사용한다
BeanDefinition
BeanDefinition은 스프링 빈 설정 메타 정보라고 볼 수 있다
스프링이 다양한 설정 형식(Annotation, XML, ...)을 지원할 수 있게 해주는 추상화된 존재이다
- 역할/구현을 개념적으로 나눈 것이다
따라서 스프링 컨테이너는 Annotation으로 설정했는지 XML으로 설정했는지 모른다 :: BeanDefinition이 제어하도록 한다
그리고 @Bean 하나당 각각 BeanDefinition이라는 메타 정보가 생성된다