Repeat is the best medicine for memory
앞서, 스프링으로 전환과 더불어 스프링 컨테이너의 작동 원리에 대해서 알아보고 있었다.
그리고 컨테이너 생성에 있어 ApplicationiContext 부모 타입을 사용해왔고, 구현체로는 AnnotationConfigApplicationContext을 사용하였다.
이번에는 이러한 컨테이너가 어떻게 설계되었는 가에 대해서 살펴보며, 동시에 다양한 설정 형식을 지원해주는 구조를 지닌 스프링의 설계적 측면을 파악하며 알 수 있는 BeanDefinition의 역할을 알아보자.
스프링 컨테이너의 설계 ( 1 ) - BeanFactory
지금까지 사용해온 ApplicationiContext는 사실 BeanFactory라는 부모타입을 상속한 인터페이스이다.
이 BeanFactory는 다음과 같은 특징을 지닌 인터페이스에 해당한다.
✅ 스프링 컨테이너 최상위 인터페이스
✅ 스프링빈을 관리하고 조회하는 역할 담당
✅ 이전에 호출했던 getBean() 메소드와 같이 사용한 대부분의 기능은 빈팩토리에서 제공하는 기능이다.
그리고 ApplicationContext는 BeanFactory 기능을 모두 상속받아서 제공하는 컨테이너에 해당한다.
그런데, Bean을 관리하고 검색하는 기능을 BeanFactory가 제공해주는데, ApplicationContext가 상속받아 사용하는 이유는 무엇일까??
애플리케이션을 개발함에 필요한 기능들은 단순하게 Bean 을 관리하고 조회하는 것을 넘어서 수 많은 기능들이 필요하다.
ApplicationContext의 내부 코드와 구조를 파악해보면 아래와 같음을 알 수 있다.
package org.springframework.context;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.lang.Nullable;
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
@Nullable
String getId();
String getApplicationName();
String getDisplayName();
long getStartupDate();
@Nullable
ApplicationContext getParent();
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
그리고 각 인터페이스에서 정의하는 기능은 다음과 같다.
정리하자면, 스프링 컨테이너에 해당하는 ApplicationContext는 BeanFactory와 더불어 다양한 인터페이스로 정의한 부가 기능을 탑재한 스프링 컨테이너이자 인터페이스인 것이다.
스프링 컨테이너의 설계 ( 2 ) - 다양한 설정 형식 지원
스프링 컨테이너를 설정함에 다양한 형식을 사용할 수 있는데, 지금까지는 Java 코드를 사용하여 구성했지만 이외에도 Xml 파일이나 더 나아가서는 임의로 만든 설정을 사용할 수도 있다.
그리고 다양한 설정을 지원함에는 아래와 같은 스프링 컨테이너의 설계가 있기에 가능하다.
물론, 최근에는 Java 코드를 사용하는 경우(Annotation)가 대부분이지만 이전에는 Xml을 많이 사용해왔다. 따라서 이를 사용한 환경 설정 방법에 대해서도 알아보자.
xml 파일을 사용하는 경우에 대한 테스트 코드를 작성해보자.
먼저, 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 파일, 즉 설정 정보가 담긴 appConfig.xml을 만들어 주는데, 자바 코드를 사용하는 경우와 달리 xml 파일은 resources 폴더 하위에 생성하여 사용한다.
해당 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="com.example.demo.member.MemberServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository"/>
</bean>
<bean id="memberRepository" class="com.example.demo.member.MemoryMemberRepository">
</bean>
<bean id="orderService" class="com.example.demo.order.OrderServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository"/>
<constructor-arg name="discountPolicy" ref="discountPolicy"/>
</bean>
<bean id="discountPolicy" class="com.example.demo.discount.RateDiscountPolicy">
</bean>
</beans>
앞서, AppConfig.java와 비슷하다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(MemberRepository());
}
@Bean
public MemberRepository MemberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(MemberRepository(), DiscountPolicy());
}
@Bean
public DiscountPolicy DiscountPolicy() {
return new RateDiscountPolicy();
}
}
BeanDefinition을 통한 다양한 설정 환경 지원
그렇다면, 스프링은 어떻게 이런 다양한 설정 형식을 지원할까??
BeanDefinition이 있기 때문이다. BeanDefinition은 스프링 컨테이너가 설정 파일이 Java 코드이든, XML 이든 상관없이 BeanDefinition만 알면 되도록 추상화 하였다.
컨테이너를 생성함에 어떤 구현체를 사용해도 각 구현체는 Reader를 사용해서 BeanDefinition을 생성하고, 이에 따른 컨테이너 설정 정보를 가진다.
그리고, 빈 메타 정보들을 살펴보기 위한 테스트 코드는 아래와 같다.
public class BeanDefinitionTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 설정 메타정보 확인")
//BeanDefinition 확인
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);
}
}
}
}
출력 결과
beanDefinitionName = appConfig beanDefinition = Generic bean: class [com.example.demo.AppConfig$$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 = memberService beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=appConfig; factoryMethodName=memberService; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in com.example.demo.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; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in com.example.demo.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; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in com.example.demo.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; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in com.example.demo.AppConfig
위의 출력을 통해 얻을 수 있는 메타 정보는 아래와 같다.
'Spring > SpringCore - basic' 카테고리의 다른 글
스프링 핵심 원리 - ComponentScan의 자동 빈 등록과 빈 탐색 관련 옵션, 빈 중복에 의한 충돌에 대한 고려 (0) | 2024.12.30 |
---|---|
스프링 핵심 원리 - 싱글톤 컨테이너(stateless 코드 작성의 필요성과 @Configuration의 바이트 조작(CGLIB)을 통한 싱글톤 작동원리) (0) | 2024.12.28 |
스프링 핵심 원리 - 스프링 컨테이너와 스프링 빈(컨테이너 생성 원리 및 다양한 Bean 조회 방법들) (2) | 2024.12.27 |
스프링 핵심 원리 - 스프링으로 전환 (0) | 2024.11.13 |
스프링 핵심 원리 - 예제를 통해 이해하는 스프링의 핵심 원리 (3) | 2024.11.07 |
댓글