기본기능의 경우 가끔 순수 컨테이너에서 사용이 필요하다
부모타입으로 조회하는 경우 자식 타입도 함께 조회된다.
그래서 모든 자바 객체의 최고 부모인 Object 타입으로 스프링 빈을 조회하는 경우, 하위 모든 스프링 빈을 조회한다(전부 다)
이 내용은 자동 의존관계 주입에서 한번 더 등장하기 때문에 꼭 알아두는 것이 좋다
package hello.springintroduction.beanFind;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import hello.springintroduction.discount.DiscountPolicy;
import hello.springintroduction.discount.FixDiscountPolicy;
import hello.springintroduction.discount.RateDiscountPolicy;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class ApplicationContextExtendsFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면 중복 오류가 발생한다")
void findBeanByParentTypeDuplicate(){
//DiscountPolicy bean = ac.getBean(DiscountPolicy.class); // 자식 인스턴스가 두개라 두개 다 조회됨.
Assertions.assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(
DiscountPolicy.class));
}
@Test
@DisplayName("부모타입으로 조회할 경우 자식이 둘 이상인 경우, 빈 이름을 지정하면 된다")
void findBeanByParentWithBeanName(){
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("부모타입으로 조회할 경우 자식이 둘 이상인 경우, 특정 하위 타입으로 조회할 수 있다, 권장하지는 않는다(구현에 의존)")
void findBeanByParentWithSubType(){
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", RateDiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("부모타입으로 모두 조회하기")
void findBeanByParentType(){
Map<String, DiscountPolicy> beansOfType =
ac.getBeansOfType(DiscountPolicy.class);
assertThat(beansOfType.size()).isEqualTo(2);
for (String s : beansOfType.keySet()) {
System.out.println("s = " + s);
System.out.println("beansOfType.get(s) = " + beansOfType.get(s));
}
}
@Test
@DisplayName("부모타입으로 모두 조회하기, Object Type")
void findBeanByObjectType(){
Map<String, Object> beansOfType =
ac.getBeansOfType(Object.class);
// assertThat(beansOfType.size()).isEqualTo(2);
for (String s : beansOfType.keySet()) {
System.out.println("s = " + s);
System.out.println("beansOfType.get(s) = " + beansOfType.get(s));
}
}
@Configuration
static class TestConfig{
@Bean // rateDiscountPolicy & fixDiscountPolicy which inherits the DiscountPolicy
public DiscountPolicy rateDiscountPolicy(){
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy(){
return new FixDiscountPolicy();
}
}
}
Bean Factory와 ApplicationContext
1) BeanFactory
- 스프링 컨테이너의 최상위 인터페이스
- 스프링 빈을 관리하고 조회하는 역할을 담당
- .getBean()을 제공한다
- 지금까지 사용한 대부분의 기능은 BeanFactory가 제공하는 기능
2) ApplicationContext
- BeanFactory의 기능을 모두 상속받아서 제공한다.
- Bean을 관리하고 검색하는 기능을 BeanFactory가 제공해주는데 다음과 같은 부가기능을 제공한다.
- 메세지 소스를 활용한 국제화 기능 (한국 - KR, 영어 - EN, 중국 - CN)
- 환경변수를 통해 로컬, 개발, 운영 등을 구분해서 처리
- 실제 실무에서는 로컬 / 테스트 서버/ 스테이징 / 프로덕션 별로 환경변수를 별도로 설정하여 운영한다(로깅 등)
- 애플리케이션 이벤트를 발행하고 구독하는 모델을 편리하게 지원해준다
- 편리하게 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회한다
다양한 설정 형식 지원 - 자바코드, XML
스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있게 유연하게 설계되어 있습니다.
- 자바 코드, XML, Groovy를 설정정보로 받아올 수 있습니다.
XML 설정 사용
많은 레거시 프로젝트들이 아직 XML을 이용하기 때문에 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.springintroduction.Member.MemberServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository" />
</bean>
<bean id="memberRepository" class="hello.springintroduction.Member.MemoryMemberRepository"/>
<bean id="orderService" class="hello.springintroduction.order.OrderServiceImpl">
<constructor-arg name = "memberRepository" ref="memberRepository"/>
<constructor-arg name="discountPolicy" ref="discountPolicy"/>
</bean>
<bean id="discountPolicy" class="hello.springintroduction.discount.RateDiscountPolicy"/>
</beans>
package hello.springintroduction.xml;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import hello.springintroduction.Member.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class XmlAppContext {
@Test
void xmlAppContext(){
ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
}
다음과 같은 방법으로 XML을 통해 스프링 설정 정보를 넘길 수 있다는 사실을 보고 넘어갑니다.
스프링 빈 설정 메타 정보 - BeanDefinition
스프링은 BeanDefinition을 통한 추상화로 다양한 설정 형식을 지원한다
- 즉, 역할과 구현을 개념적으로 나눈 것이다.
XML, 자바코드를 읽어서 결국에는 BeanDefinition을 만든다.
스프링 컨테이너는 형식이 뭔지 몰라도, BeanDefinition만 알면 된다.
BeanDefinition은 위에서 기술한대로 빈 설정 메타정보라고 한다
@Bean, XML에서의 <bean> 하나당 하나씩 메타정보가 생성된다.
스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.
코드 설정 Flow를 관찰해보면 다음과 같다.
1) AnnotationConfigApplicationContext는 AnnotatedBeanDefinitionReader를 이용하여 AppConfig.class를 읽고 BeanDefinition을 생성한다
2) GenericXmlApplicationContext는 XmlBeanDefinitionReader를 이용하여 appConfig.xml 설정 정보를 읽고 BeanDefinition을 생성한다.
3) 새로운 형식의 설정 정보가 추가되는 경우, xxxBeanDefinitionReader를 생성하여 BeanDefinition을 만들어내면 된다.
BeanDefinition
- BeanClassName: 생성할 빈의 클래스 명(자바 설정처럼 팩토리 역할의 빈을 사용하면 없음) ?
- factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
- factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
- Scope: 싱글톤(기본값)
- lazyInit : 스프링 컨테이너를 생성할 때, 빈을 생성하는 것이 아니라 실제로 빈을 사용할때까지 최대한 생성을 지연하는지 여부
- InitMethodName: 빈을 생성하고 의존관계를 적용한 뒤에 호출하는 초기화 메서드 명
- DestroyMethodName : 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
- Constructor arguments, Properties : 의존관계 주입에서 사용한다.
(자바 설정처럼 팩토리 역할의 빈을 사용하면 없음)
예제 코드는 다음과 같다.
package hello.springintroduction.beanDefinitionTest;
import hello.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class BeanDefinitionTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
// ApplicationContext로 사용ㅎㄹ경우 AnnotationConfigApplicationContext 가 가진 메서드를 사용하지 못함.
@Test
@DisplayName("빈 설정 메타정보 확인")
void findApplicationBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName); // 이름으로 BeanDefinition 출력가능
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
System.out.println("beanDefinitionName = " + beanDefinitionName);
System.out.println("beanDefinition = " + beanDefinition);
}
}
}
}
- BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수도 있다
But, 실무에서 직접 BeanDefinition을 직접 정의하거나 사용할 일이 거의 없다
- 자세하게 파기보다, 이런게 있구나 정도로 넘어간다 (추상화를 하는구나!)
직접 스프링 빈으로 등록하는 방법
VS
팩토리 메서드 - 외부에서 호출해서 객체 제공 factoryBean에서 factoryMethod에 의해 스프링에 제공
'스프링 공부 (인프런 김영한 선생님) > 스프링 핵심원리' 카테고리의 다른 글
[스프링 핵심원리] 7. Component Scan (0) | 2023.05.14 |
---|---|
[스프링 핵심원리] 6. 싱글톤 컨테이너 (0) | 2023.05.10 |
[스프링 핵심원리] 4. 스프링 전환 / 스프링 컨테이너 기본 (1) | 2023.05.10 |
[스프링 핵심원리] 3. 객체지향 설계와 스프링, 정리 (0) | 2023.05.07 |
[스프링 핵심원리] 2. 객체지향 설계와 스프링, 리팩터링 (0) | 2023.05.07 |