"인프런의 스프링 핵심원리 - 기본편 강의를 듣고 작성한 글 입니다."
BeanFactory 와 ApplicationContext 에 대해 알아보자.
BeanFactory
- 스프링 컨테이너의 최상위 인터페이스이다.
- 스프링 빈을 관리하고 조회하는 역할을 담당하며, getBean() 을 제공한다.
- 지금까지 사용했던 대부분의 기능은 BeanFactory 가 제공하는 기능이다.
ApplicationContext
- BeanFactory 기능을 모두 상속 받아서 제공한다.
- 빈을 관리하고 검색하는 기능을 BeanFactory 가 제공해주는데, 그러면 둘의 차이가 뭘까?
- 애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요하다.
* ApplicationContext 가 제공하는 부가기능
- 메시지 소스를 활용한 국제화 기능
: 에를 들어서, 요청이 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력한다.
- 환경 변수
: 로컬, 개발, 운영 등을 구분해서 처리해준다.
- 어플리케이션 이벤트
: 이벤트를 발행하고 구독하는 모델을 편리하게 지원한다.
- 편리한 리소스 조회
: 파일, 클래스 패스, 외부 등에서 리소스를 편리하게 조회할 수 있다.
정리
- ApplicationContext 는 BeanFactory 의 기능을 상속 받는다.
- ApplicationContext 는 빈 관리 기능 + 편리한 부가 기능을 제공한다.
- BeanFactory 를 직접 사용할 일은 없다. 부가기능이 포함된 ApplicationContext 를 사용한다.
- BeanFactory 나 ApplicationContext 를 스프링 컨테이너라 한다.
스프링 컨테이너는 다양한 설정 형식을 지원한다. - 자바 코드, XML
- 스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있도록 유연하게 설계되어 있다.
- 자바 코드, XML, Groovy 등등을 지원해준다.
어노테이션 기반의 자바 코드 설정 사용
- 지금까지 해왔던 것들이다.(new AnnotationConfigApplicationContext(AppConfig.class); )
- AnnotationConfigApplicationContext 클래스를 사용하면서 자바 코드로 된 설정 정보를 넘기면 된다.
XML 설정 사용
- 최근에는 스프링 부트를 많이 사용하면서 XML 기반의 설정은 잘 사용하지 않는다. 그러나 아직 많은 레거시 프로젝트 들이 XML 로 되어있고, 또 XML 을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있는 장점도 있으므로 한번쯤 배워두는 것도 괜찮다.
- GenericXmlApplicationContext 를 사용하면서 xml 설정 파일을 넘기면 된다.
test 폴더에서 hello.core.xml 패키지 아래에 XmlAppContext 클래스를 만들고 다음과 같이 코드를 작성한다.
- XmlAppContext.java
public class XmlAppContext {
@Test
void xmlAppContext(){
ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = ac.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
}
}
XML 기반의 스프링 빈 설정 정보는 아래와 같다.
경로는 src/main.resources/appConfig.xml 이다.
- 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>
- xml 기반의 appConfig.xml 스프링 설정 정보와 자바 코드로된 AppConfig.java 설정 정보를 비교해보면 거의 비슷하다는 것을 알 수 있다.
- xml 기반으로 설정하는 것은 최근에 잘 사용하지 않으므로 이정도로 마무리하고, 필요하면 스프링 공식 레퍼런스 문서를 확인하자.
https://spring.io/projects/spring-framework
스프링 빈 설정 메타 정보 BeanDefinition
스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까? 그 중심에는 BeanDefinition 이라는 추상화가 있다.
쉽게 이야기해서, 역할과 구현을 개념적으로 나눈것이다.
- XML 을 읽어서 BeanDefinition 을 만들면 된다.
- 자바 코드를 읽어서 BeanDefinition 을 만들면 된다.
- 스프링 컨테이너는 자바 코드인지 XML 인지 몰라도 된다. 오직 BeanDefinition 만 알면 된다.
* BeanDefinition 을 빈 설정 메타 정보라고 한다.(BeanDefinition 자체가 인터페이스 이다.)
- @Bean, <bean> 당 각각 하나씩 메타 정보가 생성된다.
- 스프링 컨테이넌는 이 메타정보를 기반으로 스프링 빈을 생성한다.
코드 레벨로 좀 더 깊이있게 들어가보자.
- AnnotationConfigApplicationContext 는 AnnotatedBeanDefinitionReader 를 사용해서 AppConfig.class 를 읽고 BeanDefinition 을 생성한다.
- GenericXmlApplicationContext 는 XmlBeanDefinitionReader 를 사용해서 appConfig.xml 설정 정보를 읽고 BeanDefinition 을 생성한다.
- 새로운 형식의 설정 정보가 추가되면, XxxBeanDefinitionReader 를 만들어서 BeanDefinition 을 생성하면 된다.
test 폴더 아래 hello.core.beandefinition 패키지에 BeanDefinitionTest 클래스를 만들고 다음과 같이 코드를 작성하자.
- BeanDefinitionTest.java
public class BeanDefinitionTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 설정 메타정보 확인")
void findApplicationBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
System.out.println("beanDefinitionName = " + beanDefinitionName +
" beanDefinition = " + beanDefinition);
}
}
}
}
AppConfig 클래스를 기반으로 스프링 컨테이너를 만들고, 만들어진 스프링 컨테이너에서 빈 객체들의 이름을 기준으로 하는 문자열 배열을 반환받는다.
이후 반복문에서 각 빈 객체 이름을 토대로 해당 빈의 메타 정보를 가져온 다음, 해당 정보의 타입이 ROLE_APPLICATION(개발자가 직접 정의한 객체) 이면 출력문에 작성한 대로 해당 빈 객체의 이름과 메타 정보 값을 출력해준다.
결과 :
beanDefinitionName = appConfig beanDefinition = Generic bean: class [hello.core.AppConfig$$EnhancerBySpringCGLIB$$2dfd6684]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
beanDefinitionName = memberService beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=memberService; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig
beanDefinitionName = orderService beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=orderService; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig
beanDefinitionName = memberRepository beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=memberRepository; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig
beanDefinitionName = discountPolicy beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=discountPolicy; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig
BeanDefinition 살펴보기
* BeanDefinition 정보
- BeanClassName : 생성할 빈의 클래스 명(자바 설정처럼 팩토리 역할의 빈을 사용하면 없다.)
- factoryBeanName : 팩토리 역할의 빈을 사용할 경우 이름, 예시 : appConfig
- factoryMethodName : 빈을 생성할 팩토리 메소드 지정, 예시 : memberService
- Scope : 싱글톤(값이 할당되지 않았을 경우 기본값)
- lazyInit : 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때까지 최대한 생성을 자연처리 하는지 여부
- InitMethodName : 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메소드 명
- DestroyMethodName : 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메소드 명
- Constructor arguments Properties : 의존관계 주입에서 사용한다.(자바 설정처럼 팩토리 역할의 빈을 사용하면 없다.)
정리
- BeanDefinition 을 직접 생성해서 스프링 컨테이너에 등록할 수도 있다. 하지만 실무에서 BeanDefinition 을 직접 정의하거나 사용할 일은 거의 없다. -> 어려우면 그냥 넘어가자.
- BeanDefinition 에 대해서는 너무 깊이있게 이해하기 보다는, 스프링이 다양한 형태의 설정 정보를 BeanDefinition 으로 추상화해서 사용하는 것 정도만 이해하면 된다.
- 가끔 스프링 코드나 스프링 관련 오픈 소스의 코드를 볼 때, BeanDefinition 이라는 것이 보일 때가 있다. 이때 이러한 메커니즘을 떠올리면 된다.
'Spring basic' 카테고리의 다른 글
스프링 핵심원리 : 기본편 - 싱글톤 컨테이너와 주의점 (0) | 2022.02.14 |
---|---|
스프링 핵심원리 : 기본편 - 웹 어플리케이션과 싱글톤 패턴 (0) | 2022.02.14 |
스프링 핵심원리 : 기본편 - 스프링 빈 조회 (0) | 2022.02.13 |
스프링 핵심원리 : 기본편 - 스프링 컨테이너 생성 및 빈 조회 (0) | 2022.02.12 |
스프링 핵심원리 : 기본편 - IoC, DI, 그리고 컨테이너, 스프링으로 전환 (0) | 2022.02.11 |