Spring Boot源碼解惑——啟動流程

寫在前面

隨著微服務時代的到來椎木,spring boot基本上是所有java開發(fā)人員標配的技術(shù)棧了窄驹,我們幾乎每天都在和spring打著交到朝卒,所以深入理解spring的理念和細節(jié)就十分重要。但是馒吴,不得不說spring是一個龐大的體系扎运,龐大到幾乎所有的組件,spring都會有一套對應的解決方案饮戳。雖然寫一個系統(tǒng)的深入spring的文章已經(jīng)計劃了很久豪治,但是由于它的龐大和復雜,讓我一直找不到頭緒扯罐。所以我嘗試著一點一點的將spring的核心鏈路整理成一個系列文章负拟,一是讓自己把spring系統(tǒng)的學習一遍,二也是想尋找志同道合的人一起學習和討論歹河,相互提高嘛掩浙。

Spring Boot啟動

入口

對于spring boot的啟動方式分為兩種,第一種是通過fatjar的方式進行啟動秸歧,另一個是通過IED啟動厨姚。本文主要關(guān)注Springboot啟動流程,對于fatjar的啟動方式后面單獨介紹键菱。

spring boot的啟動十分簡單谬墙,當然這就是它的初衷,配置簡單化:

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

靜態(tài)方法run经备,創(chuàng)建了一個SpringApplication并運行它拭抬,這里用靜態(tài)方法創(chuàng)建實例并運行還是比較清晰(至少比調(diào)用構(gòu)造函數(shù)的好):

    /**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified sources using default settings and user supplied arguments.
     * @param sources the sources to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
    }

spring boot的準備工作

構(gòu)造函數(shù)對SpringApplication做了一個初始化,主要是構(gòu)建Spring的基本運行環(huán)境:

    private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        this.webEnvironment = deduceWebEnvironment();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

deduceWebEnvironment主要是判斷當前環(huán)境是否為web環(huán)境侵蒙,因為web環(huán)境要做一些更多的初始化造虎,所以判斷還是有必要的。

    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
    "org.springframework.web.context.ConfigurableWebApplicationContext" };
    private boolean deduceWebEnvironment() {
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return false;
            }
        }
        return true;
    }

setInitializers 主要是獲取當前環(huán)境下配置的ApplicationContextInitializer纷闺,方法是通過classloader讀取當前環(huán)境下所有的META-INF/spring.factories文件中的配置算凿,來進行實例化份蝴。這里classLoader可以獲取多個spring.factories配置,我們也可以使用這個特性去做一些擴展:

    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<String>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

像Spring boot本身定義的spring.factories:

# 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.context.web.ServerPortInfoApplicationContextInitializer

然后通過反射進行實例化:

    private <T> List<T> createSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
            Set<String> names) {
        List<T> instances = new ArrayList<T>(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;
    }

然后保存造SpringApplication的成員變量中:

    /**
     * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
     * {@link ApplicationContext}.
     * @param initializers the initializers to set
     */
    public void setInitializers(
            Collection<? extends ApplicationContextInitializer<?>> initializers) {
        this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
        this.initializers.addAll(initializers);
    }

同樣的澎媒,加載ApplicationContextInitializer之后對ApplicationListener進行了加載,下面是SpringBoot本身的Listener:

# 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.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
    /**
     * Sets the {@link ApplicationListener}s that will be applied to the SpringApplication
     * and registered with the {@link ApplicationContext}.
     * @param listeners the listeners to set
     */
    public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
        this.listeners = new ArrayList<ApplicationListener<?>>();
        this.listeners.addAll(listeners);
    }

啟動spring boot

一切就緒后搞乏,調(diào)用run方法,啟動Spring Boot:

    /**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.started();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }

SpringBoot啟動事件發(fā)送

SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.started();
    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args));
    }

這里面代碼其實比較熟悉了戒努,就是獲取SpringApplicationRunListener请敦,構(gòu)建SpringApplicationRunListeners,并發(fā)送started事件储玫,那么整個事件發(fā)送給誰侍筛?
其實SpringApplicationRunListener 在spring boot中配置的只有EventPublishingRunListener:

    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }
    @Override
    public void started() {
        this.initialMulticaster
                .multicastEvent(new ApplicationStartedEvent(this.application, this.args));
    }

其實也就是將事件發(fā)送給前面我們初始化的Listeners:

# 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.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener

當然不是通知所有的Listeners,Springboot這里會根據(jù)event和listener類型做一個過濾:

    /**
     * Return a Collection of ApplicationListeners matching the given
     * event type. Non-matching listeners get excluded early.
     * @param event the event to be propagated. Allows for excluding
     * non-matching listeners early, based on cached matching information.
     * @param eventType the event type
     * @return a Collection of ApplicationListeners
     * @see org.springframework.context.ApplicationListener
     */
    protected Collection<ApplicationListener<?>> getApplicationListeners(
            ApplicationEvent event, ResolvableType eventType) {

        Object source = event.getSource();
        Class<?> sourceType = (source != null ? source.getClass() : null);
        ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

        // Quick check for existing entry on ConcurrentHashMap...
        ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
        if (retriever != null) {
            return retriever.getApplicationListeners();
        }

        if (this.beanClassLoader == null ||
                (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                        (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
            // Fully synchronized building and caching of a ListenerRetriever
            synchronized (this.retrievalMutex) {
                retriever = this.retrieverCache.get(cacheKey);
                if (retriever != null) {
                    return retriever.getApplicationListeners();
                }
                retriever = new ListenerRetriever(true);
                Collection<ApplicationListener<?>> listeners =
                        retrieveApplicationListeners(eventType, sourceType, retriever);
                this.retrieverCache.put(cacheKey, retriever);
                return listeners;
            }
        }
        else {
            // No ListenerRetriever caching -> no synchronization necessary
            return retrieveApplicationListeners(eventType, sourceType, null);
        }
    }

這里一個會被調(diào)用的LoggingApplicationListener就是一個比較重要的Listener。

environment構(gòu)建

spring 在啟動之前會讀取環(huán)境變量撒穷、jvm啟動參數(shù)和自身的配置文件匣椰,加載這些配置文件到environment:

            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        if (isWebEnvironment(environment) && !this.webEnvironment) {
            environment = convertToStandardEnvironment(environment);
        }
        return environment;
    }

首先加載環(huán)境變量和JVM啟動參數(shù):

    /**
     * Create a new {@code Environment} instance, calling back to
     * {@link #customizePropertySources(MutablePropertySources)} during construction to
     * allow subclasses to contribute or manipulate {@link PropertySource} instances as
     * appropriate.
     * @see #customizePropertySources(MutablePropertySources)
     */
    public AbstractEnvironment() {
        customizePropertySources(this.propertySources);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(String.format(
                    "Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
        }
    }
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

如果為WEB環(huán)境,那么會加載Servlet相關(guān)的配置:

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
        propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
        }
        super.customizePropertySources(propertySources);
    }

接下來加載spring的配置:

    protected void configureEnvironment(ConfigurableEnvironment environment,
            String[] args) {
        configurePropertySources(environment, args);
        configureProfiles(environment, args);
    }

通過configureProfiles加載Profile:

    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<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
    }

environment.getActiveProfiles在Environment中獲取Profile的配置(讀取環(huán)境變量中的spring.profiles.active配置):

    protected Set<String> doGetActiveProfiles() {
        synchronized (this.activeProfiles) {
            if (this.activeProfiles.isEmpty()) {
                String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
                if (StringUtils.hasText(profiles)) {
                    setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.activeProfiles;
        }
    }

然后把additionalProfiles和activeProfiles做一個合并端礼,set到ActiveProfiles中禽笑。
上面主要是對當前環(huán)境指定的Profile做一個匯總,加載是通過發(fā)送ApplicationEnvironmentPreparedEvent給對應的ConfigFileApplicationListener進行Profile解析:

    private void onApplicationEnvironmentPreparedEvent(
            ApplicationEnvironmentPreparedEvent event) {
        List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
        postProcessors.add(this);
        AnnotationAwareOrderComparator.sort(postProcessors);
        for (EnvironmentPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessEnvironment(event.getEnvironment(),
                    event.getSpringApplication());
        }
    }
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        addPropertySources(environment, application.getResourceLoader());
        configureIgnoreBeanInfo(environment);
        bindToSpringApplication(environment, application);
    }

addPropertySources中對Profile進行加載蛤奥,獲取之前加載的Profile和default Profile:

        public void load() {
            this.propertiesLoader = new PropertySourcesLoader();
            this.activatedProfiles = false;
            this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
            this.processedProfiles = new LinkedList<Profile>();

            // Pre-existing active profiles set via Environment.setActiveProfiles()
            // are additional profiles and config files are allowed to add more if
            // they want to, so don't call addActiveProfiles() here.
            Set<Profile> initialActiveProfiles = initializeActiveProfiles();
            this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
            if (this.profiles.isEmpty()) {
                for (String defaultProfileName : this.environment.getDefaultProfiles()) {
                    Profile defaultProfile = new Profile(defaultProfileName, true);
                    if (!this.profiles.contains(defaultProfile)) {
                        this.profiles.add(defaultProfile);
                    }
                }
            }

            // The default profile for these purposes is represented as null. We add it
            // last so that it is first out of the queue (active profiles will then
            // override any settings in the defaults when the list is reversed later).
            this.profiles.add(null);

            while (!this.profiles.isEmpty()) {
                Profile profile = this.profiles.poll();
                for (String location : getSearchLocations()) {
                    if (!location.endsWith("/")) {
                        // location is a filename already, so don't search for more
                        // filenames
                        load(location, null, profile);
                    }
                    else {
                        for (String name : getSearchNames()) {
                            load(location, name, profile);
                        }
                    }
                }
                this.processedProfiles.add(profile);
            }

            addConfigurationProperties(this.propertiesLoader.getPropertySources());
        }

getSearchLocations獲取Profile的搜索路徑佳镜,這里可以通過spring.config.location配置,來新增搜索路徑凡桥,除了設(shè)置路徑還有幾個默認的搜索路徑(classpath:/,classpath:/config/,file:./,file:./config/)

        private Set<String> getSearchLocations() {
            Set<String> locations = new LinkedHashSet<String>();
            // User-configured settings take precedence, so we do them first
            if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
                for (String path : asResolvedSet(
                        this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {
                    if (!path.contains("$")) {
                        path = StringUtils.cleanPath(path);
                        if (!ResourceUtils.isUrl(path)) {
                            path = ResourceUtils.FILE_URL_PREFIX + path;
                        }
                    }
                    locations.add(path);
                }
            }
            locations.addAll(
                    asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
                            DEFAULT_SEARCH_LOCATIONS));
            return locations;
        }

getSearchNames獲取搜索的文件名稱蟀伸,這里可以通過spring.config.name設(shè)置配置名稱,這里也有一個默認的配置名稱application缅刽,默認搜索application.properties文件:

        private Set<String> getSearchNames() {
            if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
                return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
                        null);
            }
            return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
        }

然后做Profile的加載啊掏,上文可以看到一句this.profiles.add(null),這一句主要為了加載默認的Profile配置衰猛。還有一個Profile 名為default迟蜜,主要是為了加載application-default.properties。這里的文件命名規(guī)則都是進行默認拼接的:location + name + "-" + profile + "." + ext

        private void load(String location, String name, Profile profile) {
            String group = "profile=" + (profile == null ? "" : profile);
            if (!StringUtils.hasText(name)) {
                // Try to load directly from the location
                loadIntoGroup(group, location, profile);
            }
            else {
                // Search for a file with the given name
                for (String ext : this.propertiesLoader.getAllFileExtensions()) {
                    if (profile != null) {
                        // Try the profile-specific file
                        loadIntoGroup(group, location + name + "-" + profile + "." + ext,
                                null);
                        for (Profile processedProfile : this.processedProfiles) {
                            if (processedProfile != null) {
                                loadIntoGroup(group, location + name + "-"
                                        + processedProfile + "." + ext, profile);
                            }
                        }
                        // Sometimes people put "spring.profiles: dev" in
                        // application-dev.yml (gh-340). Arguably we should try and error
                        // out on that, but we can be kind and load it anyway.
                        loadIntoGroup(group, location + name + "-" + profile + "." + ext,
                                profile);
                    }
                    // Also try the profile-specific section (if any) of the normal file
                    loadIntoGroup(group, location + name + "." + ext, profile);
                }
            }
        }

這里值得一提的是啡省,對于application.properties中也可以對配置文件進行配置娜睛,然后新增一個Profile:

        private void handleProfileProperties(PropertySource<?> propertySource) {
            Set<Profile> activeProfiles = getProfilesForValue(
                    propertySource.getProperty(ACTIVE_PROFILES_PROPERTY));
            maybeActivateProfiles(activeProfiles);
            Set<Profile> includeProfiles = getProfilesForValue(
                    propertySource.getProperty(INCLUDE_PROFILES_PROPERTY));
            addProfiles(includeProfiles);
        }

通過在application.properties中定義spring.profiles.active,可以加載指定的application-${spring.profiles.active}的配置文件

/**
     * The "active profiles" property name.
     */
    public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
    /**
     * The "includes profiles" property name.
     */
    public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";

創(chuàng)建ApplicationContext

createApplicationContext通過判斷是否為Web環(huán)境來創(chuàng)建ApplicationContext冕杠,非web環(huán)境為:AnnotationConfigApplicationContext,web環(huán)境為:AnnotationConfigEmbeddedWebApplicationContext酸茴。

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment
                        ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }

這里初始換ApplicationContext是很重要的分预,因為在實例化ApplicationContext的時候注冊了很多的Processor,這些Processer被用來處理各種@Annotation薪捍,這里主要涉及Spring的實現(xiàn)笼痹,后面會專門針對Spring的Bean加載再寫一些篇專題文章配喳,這里就不展開了。

上下文準備:

prepareContext主要是做ApplicationContext refresh前的一些準備工作凳干,例如調(diào)用所有的Initializers晴裹,來做些初始化的工作,同時加載啟動類救赐,并發(fā)送一些事件給到對應監(jiān)聽的listener

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);//將Environment傳遞到上下文中
        postProcessApplicationContext(context);
        applyInitializers(context);//調(diào)用前文中聲明的Initializers,Initializer大多是注冊Listener或Processor到ApplictionContext涧团。
        listeners.contextPrepared(context);//發(fā)送contextPreparedEvent
        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<Object> sources = getSources();//獲取SpringApplication的source成員,一般為springboot的啟動類经磅。
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[sources.size()]));//加載啟動類到ApplicationContext泌绣,后續(xù)利用source類加載其他聲明的bean
        listeners.contextLoaded(context);//發(fā)送ContextLoaded
    }

這里值得一提的是Load Resource,Load Resource主要是用來加載Spring boot的啟動類预厌,啟動類上一般會標識@Import阿迈、scanPackage等等一些配置,這些是整個Spring容器加載Bean需要的入口類轧叽。

refreshContext

refreshContext不多說苗沧,主要是refresh ApplicationContext ,里面邏輯十分復雜炭晒,很難一下子說全說透待逞,本文主要是針對springBoot的啟動流程,關(guān)于Spring的啟動和加載腰埂,后續(xù)再寫一些專題討論:

    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }

finished

這一步主要是做Spring初始化之后的回調(diào)和通知工作飒焦,afterRefresh通過調(diào)用BeanFactory中定義的ApplicationRunner和CommandLineRunner來做context初始化之后的邏輯,listeners.finished主要是通知監(jiān)聽的Listeners屿笼。
至此牺荠,Spring boot已經(jīng)啟動完成。

總結(jié)

springBoot啟動流程圖.jpg

Spring Boot的啟動流程是比較長的驴一,但是也還是給我們留下了很多的類似于SPI的擴展點去實現(xiàn)我們的功能休雌,尤其是Initializer,除了允許我們可以在ApplicationContext沒有Refresh的時候做一些初始化的事情肝断,同時還提供了一個無侵入的方式將我們自己插件(starter)中需要用到的processor和Listener等擴展注入到ApplicationContext中杈曲。

在整個啟動過程中Spring boot會有一些Event發(fā)出,這也就給了我們在不同階段實現(xiàn)自己的擴展邏輯的機會胸懈,實現(xiàn)起來也比較簡單担扑,監(jiān)聽Event即可。

除了Spring本身的功能配置的初始化之外趣钱,Spring Boot還要做Spring容器的初始化涌献,因為Spring容器的初始化過程過于復雜,不能在這張圖中展示出來首有。后面會拿出專題來寫一下Spring的內(nèi)部的運轉(zhuǎn)機制燕垃。

后續(xù)計劃

Spring Boot的啟動流程算是一個開頭枢劝,對于諸多的擴展Initializer、Listener以及Spring的內(nèi)容都沒有詳細的寫卜壕,畢竟Sping太大您旁,內(nèi)容太多,不是三言兩語能說明白的轴捎。后面會把整個流程的各個節(jié)點單獨的拆開來詳細的學習一下鹤盒,循序漸進的把Spring系統(tǒng)的學習一下。

當然也不只是分析源碼轮蜕,之所以然更要知其所以然昨悼,對于好的思想和實現(xiàn),理解的同時加以實踐才是最好的跃洛。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末率触,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子汇竭,更是在濱河造成了極大的恐慌葱蝗,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件细燎,死亡現(xiàn)場離奇詭異两曼,居然都是意外死亡,警方通過查閱死者的電腦和手機玻驻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門悼凑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人璧瞬,你說我怎么就攤上這事户辫。” “怎么了嗤锉?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵渔欢,是天一觀的道長。 經(jīng)常有香客問我瘟忱,道長奥额,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任访诱,我火速辦了婚禮垫挨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘触菜。我一直安慰自己九榔,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帚屉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪漾峡。 梳的紋絲不亂的頭發(fā)上攻旦,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音生逸,去河邊找鬼牢屋。 笑死,一個胖子當著我的面吹牛槽袄,可吹牛的內(nèi)容都是我干的烙无。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼遍尺,長吁一口氣:“原來是場噩夢啊……” “哼截酷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乾戏,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤迂苛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鼓择,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體三幻,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年呐能,在試婚紗的時候發(fā)現(xiàn)自己被綠了念搬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡摆出,死狀恐怖朗徊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情懊蒸,我是刑警寧澤荣倾,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站骑丸,受9級特大地震影響舌仍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜通危,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一铸豁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧菊碟,春花似錦节芥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚣驼。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背户矢。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工药有, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親获讳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容