-
애플리케이션 컨텍스트와 빈팩토리🌱 spring 2022. 11. 16. 11:23
❓애플리케이션 컨텍스트 (Application Context)
애플리케이션 컨텍스트는 빈들의 생성과 의존성 주입 등의 역할을 하는 일종의 DI 컨테이너이다.
SpringBoot를 이용한다면 애플리케이션 종류에 따라 각기 다른 종류의 ApplicationContext가 내부에서 만들어진다.
- 웹 애플리케이션이 아닌 경우
- 애플리케이션 컨텍스트 : AnnotationConfigApplication
- 웹 서버 : ❌
- 서블릿 기반의 웹 애플리케이션인 경우
- 애플리케이션 컨텍스트 : AnnotationConfigServletWebServerApplicationContext
- 웹 서버 : Tomcat
- 리액티브 웹 애플리케이션인 경우
- 애플리케이션 컨텍스트 : AnnotationConfigReactiveWebServerApplicationContext
- 웹 서버 : Reactor Netty
일반적인 ApplicationContext 관련 클래스들은 spring-context 프로젝트에 존재한다.
- spring-core
- spring-bean
- spring-aop
를 추가하면 같이 불러와진다.
AnnotationConfigServletWebServerApplicationContext 나 AnnotationConfigReactiveWebServerApplicationContext는
Springboot에서 추가된 클래스이므로
spring-boot-starter-web 또는 spring-boot-starter-webflux 같은 spring-boot 의존성을 추가해야 한다.
기존 Spring 프로젝트와 달리 SpringBoot는 내장 웹서버를 가지고 있다.
- 따라서, 타입에 맞는 웹서버를 만들고 애플리케이션 실행과 함께 내장 웹서버가 시작된다.
DI 컨테이너와 애플리케이션 컨텍스트
애플리케이션 컨텍스트는 애플리케이션을 실행하기 위한 환경이다.
- 그럼에도 애플리케이션 컨텍스트가 DI 컨테이너라고 불리고 그런 역할을 할 수 있는 이유는 ApplicationContext 상위에 빈들을 생성하는 BeanFactory 인터페이스를 부모로 상속받고 있기 때문이다.
❗ 빈팩토리(BeanFactory)
BeanFactory는 애플리케이션 컨텍스트의 최상위 인터페이스 중 하나이며, 다음과 같이 1개의 빈을 찾기 위한 메소드들을 갖고 있다.
- 빈을 등록하고 생성하고 조회하고 돌려주는 등 빈을 관리하는 역할을 한다.
BeanFactory는 애플리케이션 컨텍스트의 최상위 인터페이스 중 하나이다.
- 1개의 빈을 찾기 위한 메소드들을 갖고 있다.
- getBean() 메소드를 통해 빈을 인스턴스화할 수 있다.
BeanFactory 인터페이스
public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; Object getBean(String name) throws BeansException; <T> T getBean(String name, Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType); <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType); boolean containsBean(String name); boolean isSingleton(String name) throws NoSuchBeanDefinitionException; boolean isPrototype(String name) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException; String[] getAliases(String name); }
스프링은 동일한 타입의 빈이 여러개 존재할때에도 List로 빈을 찾아서 주입해준다.
- 최상위 BeanFactory는 단일 빈을 처리하기 위한 퍼블릭 인터페이스를 갖고 있지만
- ListableBeanFactory는 빈 리스트를 처리하기 위한 퍼블릭 인터페이스를 갖는다.
- HierarchicalBeanFactory는 여러 BeanFactory들 간의 계층 (부모-자식) 관계를 설정하기 위한 퍼블릭 인터페이스를 갖는다.
ListableBeanFactory
public interface ListableBeanFactory extends BeanFactory { boolean containsBeanDefinition(String beanName); int getBeanDefinitionCount(); String[] getBeanDefinitionNames(); <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType, boolean allowEagerInit); <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType, boolean allowEagerInit); String[] getBeanNamesForType(ResolvableType type); String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit); String[] getBeanNamesForType(@Nullable Class<?> type); String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit); <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException; <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException; String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType); Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException; @Nullable <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) throws NoSuchBeanDefinitionException; @Nullable <A extends Annotation> A findAnnotationOnBean( String beanName, Class<A> annotationType, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException; }
HierarchicalBeanFactory
public interface HierarchicalBeanFactory extends BeanFactory { /** * Return the parent bean factory, or {@code null} if there is none. */ @Nullable BeanFactory getParentBeanFactory(); boolean containsLocalBean(String name); }
ApplicationContext는 BeanFactory를 바로 상속받는 것이 아니라 BeanFactory의 자식 인터페이스인 ListableBeanFactory 와 HierarchicalBeanFactory를 통해 상속받는다.
- 이를 통해 애플리케이션 컨텍스트는 단일 빈 외에도 다양하게 처리할 수 있다.
AutowireCapableBeanFactory
- @Autowired 처리를 위한 BeanFactory
ApplicationContext 코드는 AutowireCapableBeanFactory 를 상속받지 않는다.
- 그럼 어떻게 @Autowired 를 처리할 수 있나 ❓
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; }
❗ 애플리케이션 컨텍스트에는 @Autowired를 처리하기 위한 AutowireCapableBeanFactory를 합성(Has-A) 관계로 가지고 있기 때문.
- 따라서 AutowireCapableBeanFactory를 반환하는 퍼블릭 인터페이스를 가지고 있다.
⇒ Spring의 애플리케이션 컨텍스트는
- BeanFactory
- ListableBeanFactory
- HierarchicalBeanFactory
를 상속받아 애플리케이션 컨텍스트를 통해 빈을 찾을 수 있다.
하지만, 스프링의 빈들은 실제로 애플리케이션 컨텍스트에서 관리되는 것은 아니다.
- ApplicationContext 하위 다양한 구현체들이 존재
- 일반적으로 스프링부트가 만들어내는 3가지 애플리케이션 컨텍스트는 모두 GenericApplicationContext 라는 애플리케이션 컨텍스트를 부모로 가지고 있다.
GenericApplicationContext
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { private final DefaultListableBeanFactory beanFactory; @Nullable private ResourceLoader resourceLoader; private boolean customClassLoader = false; private final AtomicBoolean refreshed = new AtomicBoolean(); // 생성자 public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); } public GenericApplicationContext(DefaultListableBeanFactory beanFactory) { Assert.notNull(beanFactory, "BeanFactory must not be null"); this.beanFactory = beanFactory; } public GenericApplicationContext(@Nullable ApplicationContext parent) { this(); setParent(parent); } public GenericApplicationContext(DefaultListableBeanFactory beanFactory, ApplicationContext parent) { this(beanFactory); setParent(parent); } @Override public void setParent(@Nullable ApplicationContext parent) { super.setParent(parent); this.beanFactory.setParentBeanFactory(getInternalParentBeanFactory()); } @Override public void setApplicationStartup(ApplicationStartup applicationStartup) { super.setApplicationStartup(applicationStartup); this.beanFactory.setApplicationStartup(applicationStartup); } ... }
- 클래스의 생성자를 보면 내부에서 진짜 빈들을 등록하여 관리하고 찾아주는 DefaultListableBeanFactory 를 생성한다.
즉, 애플리케이션 컨텍스트는 빈들을 관리하는 BeanFactory 구현체인 DefaultListableBeanFactory를 합성(Has-A) 관계로 내부에 가지고 있다.
- 애플리케이션 컨텍스트에 빈을 등록하거나 찾아달라는 빈 처리 요청이 오면 beanFactory로 이러한 요청을 위임 하여 처리한다.
- 애플리케이션 컨텍스트가 @Autowired를 처리해주는 빈 팩토리를 반환하는 getAutowireCapableBeanFactory를 퍼블릭 인터페이스로 가지고 있는데, 해당 메소드를 호출하면 반환되는 빈 팩토리가 DefaultListableBeanFactory 이다.
DefaultListableBeanFactory 는 상위에
- @Autowired 처리를 위한 인터페이스인 AutowireCapableBeanFactory와
- 그에 대한 추상 클래스인 AbstractAutowireCapableBeanFactory 를 상속받고 있다.
→ 애플리케이션 컨텍스트로 getAutowireCapableBeanFactory를 요청하면 AutowireCapableBeanFactory 타입으로 추상화된 DefaultListableBeanFactory 구현체 객체를 반환받는다.
ConfigurableApplicationContext
💡 ConfigurableApplicationContext 는 거의 모든 애플리케이션 컨텍스트가 갖는 공통 애플리케이션 컨텍스트 인터페이스이다.
- ApplicationContext, Lifecycle, Closable 인터페이스를 상속받는다.
- 애플리케이션 컨텍스트가 시작되고 종료될 때 사용되는 메소드들을 가진다.
- 처음 본 3가지 애플리케이션 컨텍스트 모두 ConfigurableApplicationContext 인터페이스를 직접 구현한다.
⇒ 스프링부트 애플리케이션을 실행하는 run 메소드 호출시 받는 반환 타입이 ConfigurableApplicationContext이다.
ConfigurableApplicationContext는 Closable 인터페이스를 상속받고 있다.
- 애플리케이션을 실행하고 종료하고 반복해야 하는 경우 try-with-resources 를 사용하면 작업을 조금 편리하게 만들 수 있다.
@SpringBootApplication(proxyBeanMethods = false) public class TestApplication { public static void main(String[] args) { try (ConfigurableApplicationContext ctx = SpringApplication.run(TestApplication.class,args)) { } catch (Exception e) { } } }
⇒ run() 호출 후 try-with-resources 로 인해 자동으로 close가 호출된다.
- 따라서, 매번 끄고 재시작하지 않고 1번 실행 후에 자동 종료되도록 한다.
실제 close 메소드 - AbstractApplicationContext에 구현되어 있다.
@Override public void close() { synchronized (this.startupShutdownMonitor) { doClose(); // If we registered a JVM shutdown hook, we don't need it anymore now: // We've already explicitly closed the context. if (this.shutdownHook != null) { try { Runtime.getRuntime().removeShutdownHook(this.shutdownHook); } catch (IllegalStateException ex) { // ignore - VM is already shutting down } } } }
📌 정리
ApplicationContext는 BeanFactory의 기능을 상속받는다.
- ApplicationContext는 빈 관리기능 + 기타 기능(MessageSource, EnvironmentCapable 등)을 제공
BeanFactory - 단일 빈을 처리
ListableBeanFactory - 여러 빈을 처리
HierarchicalBeanFactory - 계층 관계를 처리
- 이를 분리하여 인터페이스를 나누고 있다.
참고 자료
https://mangkyu.tistory.com/210
https://catsbi.oopy.io/0f28d626-febb-421d-91c8-5d44c6df7d1f
'🌱 spring' 카테고리의 다른 글
JDBC ❓ (0) 2023.01.13 Spring Interceptor에서 Request 데이터 처리 (1) 2022.12.06 JPA - @ElementCollection (0) 2022.11.02 JPA - Embedded Type (0) 2022.11.02 Spring Scheduler (0) 2022.10.24 - 웹 애플리케이션이 아닌 경우