🌱 spring

애플리케이션 컨텍스트와 빈팩토리

beomsic 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://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ListableBeanFactory.html

https://catsbi.oopy.io/0f28d626-febb-421d-91c8-5d44c6df7d1f