美文网首页程序员
Spring Boot源码解惑——启动流程

Spring Boot源码解惑——启动流程

作者: 一只小哈 | 来源:发表于2019-01-19 21:21 被阅读17次

    写在前面

    随着微服务时代的到来,spring boot基本上是所有java开发人员标配的技术栈了,我们几乎每天都在和spring打着交到,所以深入理解spring的理念和细节就十分重要。但是,不得不说spring是一个庞大的体系,庞大到几乎所有的组件,spring都会有一套对应的解决方案。虽然写一个系统的深入spring的文章已经计划了很久,但是由于它的庞大和复杂,让我一直找不到头绪。所以我尝试着一点一点的将spring的核心链路整理成一个系列文章,一是让自己把spring系统的学习一遍,二也是想寻找志同道合的人一起学习和讨论,相互提高嘛。

    Spring Boot启动

    入口

    对于spring boot的启动方式分为两种,第一种是通过fatjar的方式进行启动,另一个是通过IED启动。本文主要关注Springboot启动流程,对于fatjar的启动方式后面单独介绍。

    spring boot的启动十分简单,当然这就是它的初衷,配置简单化:

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

    静态方法run,创建了一个SpringApplication并运行它,这里用静态方法创建实例并运行还是比较清晰(至少比调用构造函数的好):

        /**
         * 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的准备工作

    构造函数对SpringApplication做了一个初始化,主要是构建Spring的基本运行环境:

        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主要是判断当前环境是否为web环境,因为web环境要做一些更多的初始化,所以判断还是有必要的。

        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 主要是获取当前环境下配置的ApplicationContextInitializer,方法是通过classloader读取当前环境下所有的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

    一切就绪后,调用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启动事件发送

    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,构建SpringApplicationRunListeners,并发送started事件,那么整个事件发送给谁?
    其实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));
        }
    

    其实也就是将事件发送给前面我们初始化的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这里会根据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);
            }
        }
    

    这里一个会被调用的LoggingApplicationListener就是一个比较重要的Listener。

    environment构建

    spring 在启动之前会读取环境变量、jvm启动参数和自身的配置文件,加载这些配置文件到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;
        }
    

    首先加载环境变量和JVM启动参数:

        /**
         * 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环境,那么会加载Servlet相关的配置:

        @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的配置(读取环境变量中的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中。
    上面主要是对当前环境指定的Profile做一个汇总,加载是通过发送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配置,来新增搜索路径,除了设置路径还有几个默认的搜索路径(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设置配置名称,这里也有一个默认的配置名称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。这里的文件命名规则都是进行默认拼接的: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";
    
    

    创建ApplicationContext

    createApplicationContext通过判断是否为Web环境来创建ApplicationContext,非web环境为:AnnotationConfigApplicationContext,web环境为: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的实现,后面会专门针对Spring的Bean加载再写一些篇专题文章,这里就不展开了。

    上下文准备:

    prepareContext主要是做ApplicationContext refresh前的一些准备工作,例如调用所有的Initializers,来做些初始化的工作,同时加载启动类,并发送一些事件给到对应监听的listener

        private void prepareContext(ConfigurableApplicationContext context,
                ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                ApplicationArguments applicationArguments, Banner printedBanner) {
            context.setEnvironment(environment);//将Environment传递到上下文中
            postProcessApplicationContext(context);
            applyInitializers(context);//调用前文中声明的Initializers,Initializer大多是注册Listener或Processor到ApplictionContext。
            listeners.contextPrepared(context);//发送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,后续利用source类加载其他声明的bean
            listeners.contextLoaded(context);//发送ContextLoaded
        }
    

    这里值得一提的是Load Resource,Load Resource主要是用来加载Spring boot的启动类,启动类上一般会标识@Import、scanPackage等等一些配置,这些是整个Spring容器加载Bean需要的入口类。

    refreshContext

    refreshContext不多说,主要是refresh ApplicationContext ,里面逻辑十分复杂,很难一下子说全说透,本文主要是针对springBoot的启动流程,关于Spring的启动和加载,后续再写一些专题讨论:

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

    finished

    这一步主要是做Spring初始化之后的回调和通知工作,afterRefresh通过调用BeanFactory中定义的ApplicationRunner和CommandLineRunner来做context初始化之后的逻辑,listeners.finished主要是通知监听的Listeners。
    至此,Spring boot已经启动完成。

    总结

    springBoot启动流程图.jpg

    Spring Boot的启动流程是比较长的,但是也还是给我们留下了很多的类似于SPI的扩展点去实现我们的功能,尤其是Initializer,除了允许我们可以在ApplicationContext没有Refresh的时候做一些初始化的事情,同时还提供了一个无侵入的方式将我们自己插件(starter)中需要用到的processor和Listener等扩展注入到ApplicationContext中。

    在整个启动过程中Spring boot会有一些Event发出,这也就给了我们在不同阶段实现自己的扩展逻辑的机会,实现起来也比较简单,监听Event即可。

    除了Spring本身的功能配置的初始化之外,Spring Boot还要做Spring容器的初始化,因为Spring容器的初始化过程过于复杂,不能在这张图中展示出来。后面会拿出专题来写一下Spring的内部的运转机制。

    后续计划

    Spring Boot的启动流程算是一个开头,对于诸多的扩展Initializer、Listener以及Spring的内容都没有详细的写,毕竟Sping太大,内容太多,不是三言两语能说明白的。后面会把整个流程的各个节点单独的拆开来详细的学习一下,循序渐进的把Spring系统的学习一下。

    当然也不只是分析源码,之所以然更要知其所以然,对于好的思想和实现,理解的同时加以实践才是最好的。

    相关文章

      网友评论

        本文标题:Spring Boot源码解惑——启动流程

        本文链接:https://www.haomeiwen.com/subject/jlipnxtx.html