from Dictionary - ConfigurableApplicationContext

ConfigurableApplicationContext


class SpringApplication {

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    
    public ConfigurableApplicationContext run(String... args) {
        long startTime = System.nanoTime();

        // #1. BootStrapContext 생성
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        ConfigurableApplicationContext context = null;

        // #2. SYSTEM_PROPERTY_JAVA_AWT_HEADLESS 생성 {
        //
        //      -> SpringApplication.java
        //      private void configureHeadlessProperty() {
        //          System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
        //                  System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
        //      }   
        // 
        // }
        configureHeadlessProperty();
        
        // #3. SpringApplicationListener 조회 및 start
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //
        // -> SpringApplication.java
        // private SpringApplicationRunListeners getRunListeners(String[] args) {
        //     Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        //     return new SpringApplicationRunListeners(logger,
        //             getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
        //             this.applicationStartup);
        // }
        //
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        //
        // -> SpringApplicationRunListeners.java
        // void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
        //    doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
        //            (step) -> {
        //                if (mainApplicationClass != null) {
        //                    step.tag("mainApplicationClass", mainApplicationClass.getName());
        //                }
        //            });
        //
        // }
        
        
        
        try {
            
            // #4. Arguments 래핑 및 Environment 준비
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            
            // #5. IgnoreBeanInfo 설정 {
            //
            //      -> SpringApplication.java
            //
            //      private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
            //          if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
            //              Boolean ignore = environment.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
            //                      Boolean.class, Boolean.TRUE);
            //              System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
            //          }
            //      }
            // }
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            
            // #6. ApplicationContext 생성
            context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            
            // #7. Context 준비
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            
            // #8. Context Refresh
            refreshContext(context);
            
            // #9. Context Refresh 후처리 
            afterRefresh(context, applicationArguments);
            
            Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
            }
            
            // #10. Listener 시작
            listeners.started(context, timeTakenToStartup);
            
            // #11. Runners 실행
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }
        try {
            Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
            listeners.ready(context, timeTakenToReady);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }
}

1. BootStrapContext 생성

BootStrapContext 란 애플리케이션 컨텍스트 준비 전까지 Spring의 Environment 객체를 후처리하기 위한 임시 컨텍스트 생성

2. SYSTEM_PROPERTY_JAVA_AWT_HEADLESS 생성

Java AWT로 UI를 생성할 경우 UI 없이도 작동할 수 있게 해주는 설정

3. SpringApplicationListener 조회 및 start

컨텍스트 준비 시 필요한 리스너를 찾아서 BootStrapContext를 사용해서 실행한다. 생성 시간이 길 경우 Listener로 등록해서 Notify해서 애플리케이션 컨텍스트를 준비함과 동시에 객체를 생성할 수 있게 해준다.

4. Arguments 래핑 및 Environment 준비

String[] 인자를 ApplicationArgument로 래핑한다. 이를 Environment를 준비하는 prepareEnvironment에 넘겨준다. 애플리케이션 타입에 따라 Environment 구현체를 생성한다.

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    switch (this.webApplicationType) {
    case SERVLET:
        return new ApplicationServletEnvironment();
    case REACTIVE:
        return new ApplicationReactiveWebEnvironment();
    default:
        return new ApplicationEnvironment();
    }
}

5. IgnoreBeanInfo 설정

JSP의 JavaBeans를 Ingore

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
    if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
        Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
        System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
    }
}

6. ApplicationContext 생성

팩토리 클래스에 위임한다.

@FunctionalInterface
public interface ApplicationContextFactory {

    ApplicationContextFactory DEFAULT = (webApplicationType) -> {
        try {
            switch (webApplicationType) {
            case SERVLET:
                return new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE:
                return new AnnotationConfigReactiveWebServerApplicationContext();
            default:
                return new AnnotationConfigApplicationContext();
            }
        }
        catch (Exception ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                + "you may need a custom ApplicationContextFactory", ex);
        }
    };

    ConfigurableApplicationContext create(WebApplicationType webApplicationType);

    static ApplicationContextFactory ofContextClass(Class<? extends ConfigurableApplicationContext> contextClass) {
        return of(() -> BeanUtils.instantiateClass(contextClass));
    }

    static ApplicationContextFactory of(Supplier<ConfigurableApplicationContext> supplier) {
        return (webApplicationType) -> supplier.get();
    }
}

7. Context 준비

Context 준비 후 하는 후처리 작업과 빈을 등록하는 Refresh 단계를 위한 전처리 작업이 수행된다.

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(context);
    bootstrapContext.close(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
    }

}

beanNameGenerator, resourceLoader, conversionService 등이 생성됐다면 싱글톤 bean으로 등록한다. 추가로 BootStrapContext는 필요 없어지므로 종료한다.

8. Context Refresh

빈을 찾아서 등록하고 웹 서버를 만들어 실행하는 등의 핵심 작업들이 진행된다. refresh를 거치면 모든 객체들이 싱글톤으로 인스턴스화 되어 있다. 에러가 나면 모든 빈을 제거한다. 그래서 모 아니면 도다.

9. Context Refresh 후처리

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

이전에 callRunners()가 있어서 ApplicationRunner, CommandLineRunner가 있었다고 한다.

10. Listener 시작

11. Runners 실행

어플리케이션이 실행된 이후에 초기화하는 작업을 필요로 하는 경우에 Runner로 등록하는데, 이들을 찾아서 실행한다.