Springboot 초기화 프로세스 분석

33025 단어
입구
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

이상은 가장 간단한 Springboot 프로그램(2.0.3버전)의 예이자 우리가 가장 통용하는 문법이다. 그러나 그 중에서 이 복잡한 기능 조작을 봉인하여 우리는 점차적으로 분석하기 시작했다.
우선 여기서 가장 중요한 것은 주해 @SpringBootApplication입니다.
@SpringBootApplication 메모
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};


    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class>[] scanBasePackageClasses() default {};

}
@SpringBootApplication주해는 몇 개의 주해가 복합적으로 구성되어 있는데 그 중에서 가장 중요한 것은 @SpringBootConfiguration, @EnableAutoConfiguration@ComponentScan 이 세 가지이다.
@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

그 중에서 @ComponentScan은spring의 원생 주석이고 @SpringBootConfiguration은springboot의 주석이지만 실질은 포장된 @Configuration이다. 여전히spring의 주석으로 xml을 대체하는 방식으로 설정 bean을 관리하는 데 사용된다.
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    Class>[] exclude() default {};

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    String[] excludeName() default {};

}
@EnableAutoConfiguration의 정의는 다음과 같다. 여기서 가장 중요한 주석은 @Import(@AutoConfigurationPackage의 주석의 실현도 @Import을 바탕으로 한다)이다. @Import의 도움을 받아 자동 설정 조건에 부합되는 모든 bean 정의를 IoC 용기에 탑재한다.@EnableAutoConfiguration에 대한 주석은 후속 언급은 그때 다시 상세하게 설명할 것이다.여기서 우리는 먼저 가동류의 run 방법으로 돌아가 초기화 절차를 처음부터 분석한다.
run 방법
    public static ConfigurableApplicationContext run(Class> primarySource, String... args) {
        return run(new Class>[] { primarySource }, args);
    }
    public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

'run'방법이 최종적으로 호출된 것은 new SpringApplication(primarySources).run(args)이다. 여기서 먼저 SpringApplication 대상을 만들고 그 run 방법을 호출한다.
public SpringApplication(Class>... primarySources) {
        this(null, primarySources);
    }
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 = deduceWebApplicationType();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

여기에는 주로 SpringApplication의 대상을 초기화하는데 여기서 특별히 언급할 것은 webApplicationTypegetSpringFactoriesInstances이다.
webApplicationType
이것은 우리의 응용이 어떤 유형의 응용인지 표시하는 데 사용된다. deduceWebApplicationType() 방법의 실현을 살펴보자.
    private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

그 반환값은 WebApplicationType 유형의 매거류로 그 값은 NONE, SERVLET, REACTIVE 세 가지가 있는데 각각 비WEB 응용, servlet 기반의 WEB 응용과reactive 기반의 WEB 응용이다.
getSpringFactoriesInstances
    private  Collection getSpringFactoriesInstances(Class type) {
        return getSpringFactoriesInstances(type, new Class>[] {});
    }
    private  Collection getSpringFactoriesInstances(Class type,
            Class>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

여기서 핵심은 SpringFactoriesLoader.loadFactoryNames(type, classLoader) 방법입니다.
    public static List loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }
loadSpringFactories(classLoader)이 뭘 했는지 주목해주세요.
    private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            Enumeration urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry, ?> entry : properties.entrySet()) {
                    List factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

이곳의 FACTORIES_RESOURCE_LOCATIONMETA-INF/spring.factories으로 정의되어 있기 때문에 이 방법은 모든 가방에 있는 이 파일을 스캔하여 맵의 대상으로 해석하고 cache에 캐시하여 중복 불러오는 것을 피한다.springboot 가방에 있는 이 파일의 일부 부분은 다음과 같다.

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

이를 통해 알 수 있듯이 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class))setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));이 각각 상기 유형에 대응한다.
해석 완료 후 createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names) 처리 해석 결과를 호출하여 대응하는 실례를 생성한다. 원본 코드는 다음과 같다.
    @SuppressWarnings("unchecked")
    private  List createSpringFactoriesInstances(Class type,
            Class>[] parameterTypes, ClassLoader classLoader, Object[] args,
            Set names) {
        List instances = new ArrayList<>(names.size());
        for (String name : names) {
            try {
                Class> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor> constructor = instanceClass
                        .getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException(
                        "Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }

이곳의 핵심은 ClassUtils.forName(name, classLoader) 방법을 통해 반사적인 방식으로 유형의 실례를 생성하는 instanceClass이다.이를 통해 알 수 있듯이 SpringFactoriesLoader.loadFactoryNames(type, classLoader)의 역할은 META-INF/spring.factories에 배치된 내용을 실례화한 공장 방법류로 매우 강한 확장성을 갖추고 SPI 메커니즘과 비슷한 효과가 있다.SpringApplication의 초기화를 보고 run으로 돌아가서 방법을 계속 분석합니다.
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);//        
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);//      
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();//    
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);//    
            refreshContext(context);//    
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

여기서 그중에서 비교적 중요한 몇 가지 방법을 골라 분석하다
  • 작성 ConfigurableEnvironment 객체
  •     private ConfigurableEnvironment prepareEnvironment(
                SpringApplicationRunListeners listeners,
                ApplicationArguments applicationArguments) {
            // Create and configure the environment
            ConfigurableEnvironment environment = getOrCreateEnvironment();//   environment
            configureEnvironment(environment, applicationArguments.getSourceArgs());//      
            listeners.environmentPrepared(environment);//       ,          
            bindToSpringApplication(environment);
            if (this.webApplicationType == WebApplicationType.NONE) {
                environment = new EnvironmentConverter(getClassLoader())
                        .convertToStandardEnvironmentIfNecessary(environment);
            }
            ConfigurationPropertySources.attach(environment);
            return environment;
        }
    
    getOrCreateEnvironment() 방법으로 용기 환경 만들기
        private ConfigurableEnvironment getOrCreateEnvironment() {
            if (this.environment != null) {
                return this.environment;
            }
            if (this.webApplicationType == WebApplicationType.SERVLET) {
                return new StandardServletEnvironment();
            }
            return new StandardEnvironment();
        }
    
    environment이 존재하면 중복 창설되지 않습니다. 응용 형식이 servlet일 때 창설된 대상은 StandardServletEnvironment 대상입니다. 그렇지 않으면 StandardEnvironment 대상을 창설합니다.
    이어서 configureEnvironment(environment, applicationArguments.getSourceArgs()) 보겠습니다.
        protected void configureEnvironment(ConfigurableEnvironment environment,
                String[] args) {
            configurePropertySources(environment, args);//           
            configureProfiles(environment, args);//  active  
        }
    
    configurePropertySources(environment, args) 시작 명령줄의 설정 속성을 불러옵니다. 실현을 보십시오.
        protected void configurePropertySources(ConfigurableEnvironment environment,
                String[] args) {
            MutablePropertySources sources = environment.getPropertySources();
            if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
                sources.addLast(
                        new MapPropertySource("defaultProperties", this.defaultProperties));
            }
            //       
            if (this.addCommandLineProperties && args.length > 0) {
                String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
                if (sources.contains(name)) {
                    PropertySource> source = sources.get(name);
                    CompositePropertySource composite = new CompositePropertySource(name);
                    composite.addPropertySource(new SimpleCommandLinePropertySource(
                            "springApplicationCommandLineArgs", args));
                    composite.addPropertySource(source);
                    sources.replace(name, composite);
                }
                else {
                    sources.addFirst(new SimpleCommandLinePropertySource(args));
                }
            }
        }
    

    이곳의 MutablePropertySources 대상은 설정 집합을 저장하는 데 사용되며, 내부에는 CopyOnWriteArrayList 형식의list 대상이 유지되며, 기본 설정이 존재할 때 이list의 끝에 new MapPropertySource("defaultProperties", this.defaultProperties) 대상을 삽입합니다.
    이어서 configureProfiles(environment, args) 보겠습니다.
        protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
            environment.getActiveProfiles(); // ensure they are initialized
            // But these ones should go first (last wins in a property key clash)
            Set profiles = new LinkedHashSet<>(this.additionalProfiles);
            profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
            environment.setActiveProfiles(StringUtils.toStringArray(profiles));
        }
    

    여기서 주로 하는 일은 environment.getActiveProfiles()의 매개 변수를 environment에 설정하는 것이다. 즉, spring.profiles.active에 대응하는 환경 변수이다.
    마지막으로 listeners.environmentPrepared(environment) 보겠습니다.
        public void environmentPrepared(ConfigurableEnvironment environment) {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.environmentPrepared(environment);
            }
        }
    

    이곳의 listeners은 이전에 META-INF/spring.factories을 통해 등록된 모든listeners입니다. 그 중에서 가장 중요한 ConfigFileApplicationListener을 예로 삼아 분석한 다음에 listener.environmentPrepared(environment)을 살펴보겠습니다.
        @Override
        public void environmentPrepared(ConfigurableEnvironment environment) {
            this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                    this.application, this.args, environment));
        }
    

    여기에서 ApplicationEnvironmentPreparedEvent 유형의 이벤트를 만들고 multicastEvent 방법을 호출했습니다. 이 방법을 통해 최종적으로listener의 onApplicationEvent 방법을 호출하여 이벤트 감청기의 실행을 촉발합니다.
    다음은 ConfigFileApplicationListeneronApplicationEvent 방법이 어떻게 되었는지 살펴보겠습니다.
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof ApplicationEnvironmentPreparedEvent) {
                onApplicationEnvironmentPreparedEvent(
                        (ApplicationEnvironmentPreparedEvent) event);
            }
            if (event instanceof ApplicationPreparedEvent) {
                onApplicationPreparedEvent(event);
            }
        }
    
    ApplicationEnvironmentPreparedEvent 유형의 사건을 감시할 때 onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event) 방법을 사용합니다
        private void onApplicationEnvironmentPreparedEvent(
                ApplicationEnvironmentPreparedEvent event) {
            List postProcessors = loadPostProcessors();
            postProcessors.add(this);
            AnnotationAwareOrderComparator.sort(postProcessors);
            for (EnvironmentPostProcessor postProcessor : postProcessors) {
                postProcessor.postProcessEnvironment(event.getEnvironment(),
                        event.getSpringApplication());
            }
        }
    

    여기에는 loadPostProcessors() 방법으로 META-INF/spring.factories의 모든 EnvironmentPostProcessor류를 리스트에 싣고 ConfigFileApplicationListener 자신도 추가한 것을 볼 수 있다.다음으로list의 모든 대상을 훑어보고 postProcessEnvironment 방법을 실행합니다. 이어서 이 방법을 보십시오
        @Override
        public void postProcessEnvironment(ConfigurableEnvironment environment,
                SpringApplication application) {
            addPropertySources(environment, application.getResourceLoader());
        }
    
        protected void addPropertySources(ConfigurableEnvironment environment,
                ResourceLoader resourceLoader) {
            RandomValuePropertySource.addToEnvironment(environment);
            new Loader(environment, resourceLoader).load();
        }
    

    여기의 핵심은 new Loader(environment, resourceLoader).load()입니다. 여기의 Loader은 내부 클래스로 프로필의 마운트를 처리하는 데 사용됩니다. 우선 그 구조 방법을 살펴보겠습니다.
            Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
                this.environment = environment;
                this.resourceLoader = (resourceLoader != null ? resourceLoader
                        : new DefaultResourceLoader());
                this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
                        PropertySourceLoader.class, getClass().getClassLoader());
            }
    

    여기에는 resourceLoaderSpringFactoriesLoader을 통해 로드되는 것을 볼 수 있습니다. 그러면 META-INF/spring.factories에 정의된 resourceLoader이 무엇인지 살펴보겠습니다.
    org.springframework.boot.env.PropertySourceLoader=\
    org.springframework.boot.env.PropertiesPropertySourceLoader,\
    org.springframework.boot.env.YamlPropertySourceLoader
    

    이름에서 알 수 있듯이 PropertiesPropertySourceLoaderYamlPropertySourceLoader은 각각 처리에 쓰인다.properties 및.yml 형식의 프로필입니다.
    이어서 load() 방법이 어떻게 되었는지 살펴보겠습니다.
            public void load() {
                this.profiles = new LinkedList<>();
                this.processedProfiles = new LinkedList<>();
                this.activatedProfiles = false;
                this.loaded = new LinkedHashMap<>();
                initializeProfiles();//   
                while (!this.profiles.isEmpty()) {//        
                    Profile profile = this.profiles.poll();
                    if (profile != null && !profile.isDefaultProfile()) {
                        addProfileToEnvironment(profile.getName());
                    }
                    load(profile, this::getPositiveProfileFilter,
                            addToLoaded(MutablePropertySources::addLast, false));
                    this.processedProfiles.add(profile);
                }
                load(null, this::getNegativeProfileFilter,
                        addToLoaded(MutablePropertySources::addFirst, true));//             
                addLoadedPropertySources();
            }
    
    initializeProfiles()profiles의 초기화를 진행했고 기본적으로 nulldefault에서 profiles까지 null에 대응하는 프로필 응용 프로그램을 추가했다.properties와 응용 프로그램.yml, default 대응 프로필 응용 프로그램-default.yml와 응용 프로그램-default.properties, 여기 있는 null은 우선적으로 처리됩니다. 후처리는 선처리를 덮어쓰기 때문에 우선순위가 가장 낮습니다.
    이어서 load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)) 방법을 살펴보겠습니다.
            private void load(Profile profile, DocumentFilterFactory filterFactory,
                    DocumentConsumer consumer) {
                getSearchLocations().forEach((location) -> {
                    boolean isFolder = location.endsWith("/");
                    Set names = (isFolder ? getSearchNames() : NO_SEARCH_NAMES);
                    names.forEach(
                            (name) -> load(location, name, profile, filterFactory, consumer));
                });
            }
    

    여기서 중점은 getSearchLocations()을 통해 프로필을 가져오는 경로입니다. 기본적으로 4개의 경로를 가져옵니다.
  • file:./config/
  • file:./
  • classpath:/config/
  • classpath:/

  • 이어서 이 경로를 옮겨다니며 프로필 이름을 연결하고 적합한yml나properties 해상도를 선택하여 분석한 다음에 결과를 environmentpropertySources에 추가합니다.
  • createApplicationContext()을 통해 run 메서드의 반환값 대상 context
  •     protected ConfigurableApplicationContext createApplicationContext() {
            Class> contextClass = this.applicationContextClass;
            if (contextClass == null) {
                try {
                    switch (this.webApplicationType) {
                    case SERVLET:
                        contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                        break;
                    case REACTIVE:
                        contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                        break;
                    default:
                        contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                    }
                }
                catch (ClassNotFoundException ex) {
                    throw new IllegalStateException(
                            "Unable create a default ApplicationContext, "
                                    + "please specify an ApplicationContextClass",
                            ex);
                }
            }
            return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
        }
    

    보시다시피 여기도 webApplicationType의 수치에 따라 각각 다른 반환 유형을 만듭니다.
  • prepareContext(context, environment, listeners, applicationArguments,printedBanner) 방법을 통해 listeners, environment, applicationArguments, printedBanner 등 중요한 구성 요소와 상하문 대상 context
  • 과 관련
        private void prepareContext(ConfigurableApplicationContext context,
                ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                ApplicationArguments applicationArguments, Banner printedBanner) {
            context.setEnvironment(environment);//      ,      
            postProcessApplicationContext(context);//        ,        
            applyInitializers(context);
            listeners.contextPrepared(context);
            if (this.logStartupInfo) {
                logStartupInfo(context.getParent() == null);
                logStartupProfileInfo(context);
            }
    
            // Add boot specific singleton beans
            context.getBeanFactory().registerSingleton("springApplicationArguments",
                    applicationArguments);
            if (printedBanner != null) {
                context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
            }
    
            // Load the sources
            Set sources = getAllSources();//     
            Assert.notEmpty(sources, "Sources must not be empty");
            load(context, sources.toArray(new Object[0]));//        ,        
            listeners.contextLoaded(context);//         。
        }
    

    여기 sources은 바로 우리의 시동류를 장착한 다음에 load(context, sources.toArray(new Object[0])) 방법을 통해 탑재한다
        protected void load(ApplicationContext context, Object[] sources) {
            if (logger.isDebugEnabled()) {
                logger.debug(
                        "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
            }
            BeanDefinitionLoader loader = createBeanDefinitionLoader(
                    getBeanDefinitionRegistry(context), sources);
            if (this.beanNameGenerator != null) {
                loader.setBeanNameGenerator(this.beanNameGenerator);
            }
            if (this.resourceLoader != null) {
                loader.setResourceLoader(this.resourceLoader);
            }
            if (this.environment != null) {
                loader.setEnvironment(this.environment);
            }
            loader.load();
        }
    
    loader이 어떻게 탑재되었는지 살펴보겠습니다.
        public int load() {
            int count = 0;
            for (Object source : this.sources) {
                count += load(source);
            }
            return count;
        }
    
        private int load(Object source) {
            Assert.notNull(source, "Source must not be null");
            if (source instanceof Class>) {
                return load((Class>) source);
            }
            if (source instanceof Resource) {
                return load((Resource) source);
            }
            if (source instanceof Package) {
                return load((Package) source);
            }
            if (source instanceof CharSequence) {
                return load((CharSequence) source);
            }
            throw new IllegalArgumentException("Invalid source type " + source.getClass());
        }
    
        private int load(Class> source) {
            if (isGroovyPresent()
                    && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
                // Any GroovyLoaders added in beans{} DSL can contribute beans here
                GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
                        GroovyBeanDefinitionSource.class);
                load(loader);
            }
            if (isComponent(source)) {
                this.annotatedReader.register(source);
                return 1;
            }
            return 0;
        }
    

    일련의 호출을 거친 후에 최종적으로 load(Class> source) 방법으로 실행되었다. 여기서 재미있는 것은 Groovy가 존재할 때 Groovy를 우선적으로 호출하는 방식으로 불러오는 것이다. 그렇지 않으면 this.annotatedReader.register(source) 방법으로 시작 클래스를 beanDefinitionMap에 등록하는 것이다.
  • refreshContext(context) 리셋 용기
  •     private void refreshContext(ConfigurableApplicationContext context) {
            refresh(context);
            if (this.registerShutdownHook) {
                try {
                    context.registerShutdownHook();
                }
                catch (AccessControlException ex) {
                    // Not allowed in some environments.
                }
            }
        }
    
        protected void refresh(ApplicationContext applicationContext) {
            Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
            ((AbstractApplicationContext) applicationContext).refresh();
        }
    
        @Override
        public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
                // Prepare this context for refreshing.
                prepareRefresh();
    
                // Tell the subclass to refresh the internal bean factory.
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
                // Prepare the bean factory for use in this context.
                prepareBeanFactory(beanFactory);
    
                try {
                    // Allows post-processing of the bean factory in context subclasses.
                    postProcessBeanFactory(beanFactory);
    
                    // Invoke factory processors registered as beans in the context.
                    invokeBeanFactoryPostProcessors(beanFactory);
    
                    // Register bean processors that intercept bean creation.
                    registerBeanPostProcessors(beanFactory);
    
                    // Initialize message source for this context.
                    initMessageSource();
    
                    // Initialize event multicaster for this context.
                    initApplicationEventMulticaster();
    
                    // Initialize other special beans in specific context subclasses.
                    onRefresh();
    
                    // Check for listener beans and register them.
                    registerListeners();
    
                    // Instantiate all remaining (non-lazy-init) singletons.
                    finishBeanFactoryInitialization(beanFactory);
    
                    // Last step: publish corresponding event.
                    finishRefresh();
                }
    
                catch (BeansException ex) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Exception encountered during context initialization - " +
                                "cancelling refresh attempt: " + ex);
                    }
    
                    // Destroy already created singletons to avoid dangling resources.
                    destroyBeans();
    
                    // Reset 'active' flag.
                    cancelRefresh(ex);
    
                    // Propagate exception to caller.
                    throw ex;
                }
    
                finally {
                    // Reset common introspection caches in Spring's core, since we
                    // might not ever need metadata for singleton beans anymore...
                    resetCommonCaches();
                }
            }
        }
    

    refresh() 방법은 상당히 중요하다. 특히 invokeBeanFactoryPostProcessors(beanFactory)은spring-boot-starter-*(mybatis,redis 등) 자동화 설정을 실현하는 관건적인 부분이므로 후속으로 상세하게 설명한다.
    총결산
    이로써 Springboot의 시작 절차는 대체적으로 분석되었고 설정 파일과 시작 클래스가 어떻게 불러오는지 파악했지만 두 가지 문제가 남아 있다. 첫째, Springboot의 핵심 사상은 설정보다 크다는 약속이다. 둘째, Springboot의 각종spring-boot-starter-*가 어떻게 역할을 발휘하는지 이 두 가지 문제는 후속 글에서 계속 분석해야 한다.

    좋은 웹페이지 즐겨찾기