Spring Boot启动流程分析

注意: 文章相对较长,可能会消耗您60分钟的时间。

未经作者允许,禁止转裁。

开局废话

Spring Boot作为当前最流行的开发框架,使用起来相当便利,因为其自身已为我们做了太多的事情,站在了巨人肩上,我们的开发也变得相当快速有效。

对于Spring Boot的简介就直接略过了,现在以最简单的一个WEB工程来了解一下Spring Boot的启动过程。

准备工作

我们已当前(2019-11-01)最新的spring boot (v2.2.0.RELEASE)来创建工程, 对于一个一分钟可搭建的工程,就只包含以下几个重要文件。
pom.xml
在选择工程拥有的能力时,只选择了一项spring-boot-starter-web, 其它都自动生成了。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.refiny</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

DemoApplication
命名的时候,我使用的是demo,因此生成了DemoApplication.

package com.refiny.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

DemoController
创建一个com.refiny.demo.controller的包, 写一个方法在里面。

package com.refiny.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @GetMapping("/demo")
    public String demo(){
        return "Hello World";
    }
}

整个工程里面唯一写的代码就在这里了.

我们将配置文件做一些调整, 将application.properties改成application.yml. 并创建一个新的application-pro.yml.

application.yml的文件内容是:

server:
  port: 9877
spring:
  profiles:
    active: pro

application-pro.yml的文件内容是:

server:
  port: 9876

完成之后,启动一下工程,通过页面访问http://localhost:9876/demo就可以看到Hello World的输出结果。

现在就基于这些代码,来从头看看是如何一步一步加载的。

Spring工程启动

入口

spring boot推荐使用jar包的方式启动工程, 因此,启动命令通常形似java -jar demo.jar,然后通过main方法作为入口开始工程的启动, SpringApplication.run(DemoApplication.class, args)将会执行初始化。

点击进入run方法之后,连续的几个run方法会直接把你带到最终的run方法执行的位置,先不要急,这中间还有一步是new SpringApplication(primarySources).run(args), 这里生成了一个SpringApplication的实例,再用这个实例去执行的下一步run

有两行代码是这样的:

    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

嗯,这里是要开始创建spring的工厂实例和监听器了。

看看getSpringFactoriesInstances干了啥呢, 从SpringFactoriesLoaderloadFactoryNames, 在加载不同的工厂名时,还区分了类加载器,加载一次之后,就存入了缓存中,以后根据类加载器就直接拿出值来返回,第一次加载的时候,是读取的类路下的META-INF/spring.factories这个文件, 在我们自定义自动配置文件的时候,也是使用的这个文件名, 是在这里用到的, 在这个只有WEB能力的工程下, 加载了三个jar包里的META-INF/spring.factories, 它们分别是:

  • org/springframework/boot/spring-boot/2.2.0.RELEASE/spring-boot-2.2.0.RELEASE.jar!/META-INF/spring.factories
  • org/springframework/boot/spring-boot-autoconfigure/2.2.0.RELEASE/spring-boot-autoconfigure-2.2.0.RELEASE.jar!/META-INF/spring.factories
  • org/springframework/spring-beans/5.2.0.RELEASE/spring-beans-5.2.0.RELEASE.jar!/META-INF/spring.factories

如果你还从来都没有打开看过这个文件的话,我列一下spring-boot-2.2.0.RELEASE.jar!/META-INF/spring.factories的内容在下面:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# 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.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
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

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
 org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertiesFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer

# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

将这些工厂名都找到之后,就需要将这些工厂实例都创建出来,createSpringFactoriesInstances就是创建工厂实例并加入列表中, 这里就是使用的BeanUtils来反射创建的实例。

使用同样的方式ApplicationContextInitializerApplicationListener的相关实例都会被创建进去。

run

有了一上步的一些初始化动作, 现在再来看如何将这个工程run起来。

      /**
       * 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) {
1         StopWatch stopWatch = new StopWatch();
2         stopWatch.start();
3         ConfigurableApplicationContext context = null;
4         Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
5         configureHeadlessProperty();
6         SpringApplicationRunListeners listeners = getRunListeners(args);
7         listeners.starting();
8         try {
9             ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
10            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
11            configureIgnoreBeanInfo(environment);
12            Banner printedBanner = printBanner(environment);
13            context = createApplicationContext();
14            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
15                    new Class[] { ConfigurableApplicationContext.class }, context);
16            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
17            refreshContext(context);
18            afterRefresh(context, applicationArguments);
19            stopWatch.stop();
20            if (this.logStartupInfo) {
21                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
22            }
23            listeners.started(context);
24            callRunners(context, applicationArguments);
25        }
26        catch (Throwable ex) {
27            handleRunFailure(context, ex, exceptionReporters, listeners);
28            throw new IllegalStateException(ex);
29        }
    
30        try {
31            listeners.running(context);
32        }
33        catch (Throwable ex) {
34            handleRunFailure(context, ex, exceptionReporters, null);
35            throw new IllegalStateException(ex);
36        }
37        return context;
      }

前面几行都只是启动一个定时器用于计算整个启动过程花了多长时间的,第6行又是获取一个监听器列表, 进入方法查看一下,也是调用的getSpringFactoriesInstances, 直接到META-INF/spring.factories中就可以看到SpringApplicationRunListener的监听器就只有一个org.springframework.boot.context.event.EventPublishingRunListener, 但这里不要误认为只能有一个呦,starting里的代码是这样的:

    void starting() {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.starting();
        }
    }

因此是有多少监听器就启动多少个监听器。

这里的EventPublishingRunListener就是在应用启动过程中发布事件使用的, starting之后,就会有一个ApplicationStartingEvent事件发布出来, 后续过程中将会有更多的发布事件出来。

准备环境

10行代码开准备环境, 当前Spring Boot工程的类型是是在SpringApplication初始化的时候,就通过WebApplicationType.deduceFromClasspath()确定了的, 为WebApplicationType.SERVLET. 在getOrCreateEnvironment中根据这个类型拿到一个new StandardServletEnvironment()的实例。

environment中, 设置了一些参数,但都是一些初始的参数,例如ActiveProfiles, 现在还并没有去读取application.ymlapplication.properties, 所以,也并没有把spring.profiles.active设置的值读到。

环境有了之后, 监听器发布了一个事件listeners.environmentPrepared(environment);, 由于当前只有一个EventPublishingRunListener, 在environmentPrepared中发布了ApplicationEnvironmentPreparedEvent:

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster
                .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
    }

这个事件发布出来之后,你也可以根据这个事件对environment做一些自己的事情, 但我们分析到这里,还没有出现容器和bean的初始化, 因此不能直接通过注解的方式来完成对这个事件的接收处理,我们可以如下完成这个事情:
写一个事件监听器:

public class ApplicationEnvironmentPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        System.out.println("------------------------------<接收到了ApplicationEnvironmentPreparedEvent>--------------------------------");
    }
}

这个事件监听器只对ApplicationEnvironmentPreparedEvent有效, 然后把这个监听器添加到SpringApplication中:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(DemoApplication.class);
        springApplication.addListeners(new ApplicationEnvironmentPreparedEventListener());
        springApplication.run(args);
    }
}

还有另一种写法,即写到/META-INF/spring.factories下面, 在工程的resources下创建这个文件, 然后把类名全称写进去:

# My Listeners
org.springframework.context.ApplicationListener=\
com.refiny.demo.listener.ApplicationEnvironmentPreparedEventListener

以后自己定义starterAuto Configuration的时候也会是这种写法。

既然ApplicationEnvironmentPreparedEvent的事件已经发了出来,有哪些监听这个事件的对象会做出响应呢? 在/META-INF/spring.factories中就定义了一堆将用于事件的监听器,在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);
        }
    }

在这些监听器里面, 有一个非常重要的监听器ConfigFileApplicationListener, 它将读取spring boot的的配置文件, 在接收到ApplicationEnvironmentPreparedEvent之后, 会对所有的EnvironmentPostProcessor执行postProcessEnvironment,我们只看ConfigFileApplicationListener的。

postProcessEnvironment方法会直接执行addPropertySources并创建一个Loader然后,执行它的load

配置文件加载的顺序是如何的呢? 看看以下代码:

    private Set<String> getSearchLocations() {
        if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
            return getSearchLocations(CONFIG_LOCATION_PROPERTY);
        }
        Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
        locations.addAll(
                asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
        return locations;
    }
    
    private Set<String> getSearchLocations(String propertyName) {
        Set<String> locations = new LinkedHashSet<>();
        if (this.environment.containsProperty(propertyName)) {
            for (String path : asResolvedSet(this.environment.getProperty(propertyName), null)) {
                if (!path.contains("$")) {
                    path = StringUtils.cleanPath(path);
                    if (!ResourceUtils.isUrl(path)) {
                        path = ResourceUtils.FILE_URL_PREFIX + path;
                    }
                }
                locations.add(path);
            }
        }
        return locations;
    }
    
    private Set<String> getSearchNames() {
        if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
            String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
            return asResolvedSet(property, null);
        }
        return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
    }

如果环境中有spring.config.location这个属性, 就使用这个值, 如果没有,再去看环境中有没有spring.config.additional-location这个属性, 将这个值作为待加载的一项配置, 再继续去读取默认配置的地址classpath:/,classpath:/config/,file:./,file:./config/,

这里所有的这些属性都是可以传多个值,并以,分隔开来, 例如在读取默认配置的地址时就是先将字符串以,切开,再转换也LinkedHashSet, 最后得到的列表为:

  • file:./config/
  • file:./
  • classpath:/config/
  • classpath:/

读配置文件的地址有了,现在就依次去读取所在文件夹下的配置文件, 读取的文件名则由getSearchNames方法来确定, 在getSearchNames中, 如果能从环境中读取到spring.config.name, 则使用,否则使用默认的名称application

当然,spring.config.location也是可以直接指定某一个文件,而不一定要是文件夹。

/META-INF/spring.factories中的配置可以看出,读取spring的配置文件会使用到两个PropertySourceLoader:

  • org.springframework.boot.env.PropertiesPropertySourceLoader
  • org.springframework.boot.env.YamlPropertySourceLoader

首先是PropertiesPropertySourceLoader去读, 它带的后缀是{ "properties", "xml" }, 这时还没有任何一个配置文件被读取到,所以, 还不存在active profiles这个概念, 第一个被读取的文件地址就是: file:./config/application.properties, 我们现在使用的是一个默认的工程,这个目录是不存在的,所以,在代码追踪时会看到Skipped missing config 'file:./config/application.properties' (file:./config/application.properties), 这个文件会被跳过。 接着会去读取的文件是: file:./config/application.xml, 同样也没有。

哇, 既然spring boot可以使用xml来写配置文件, 那之前我们的端口配置也可以写成下面这种形式:
创建一个application-pro.xml, 将下列内容写入文件中:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <entry key="server.port">9875</entry>
</properties>

好像也像那么一回事儿, 关于最终会是哪一个被应用到工程中,后续再说。

file:./config/这个目录下, PropertiesPropertySourceLoader就读完了,然后, 采用YamlPropertySourceLoader去读, 它带的后缀是{ "yml", "yaml" }, 我上面一样,会去依次读取file:./config/application.ymlfile:./config/application.yaml, 两个文件都不存在。

在路径规则是,它会使用PropertiesPropertySourceLoaderYamlPropertySourceLoader去读取这四个路径, 依次会读取到:

  • file:./config/application.properties
  • file:./config/application.xml
  • file:./config/application.yml
  • file:./config/application.yaml
  • file:./application.properties
  • file:./application.xml
  • file:./application.yml
  • file:./application.yaml
  • classpath:/config/application.properties
  • classpath:/config/application.xml
  • classpath:/config/application.yml
  • classpath:/config/application.yaml
  • classpath:/application.properties
  • classpath:/application.xml
  • classpath:/application.yml
  • classpath:/application.yaml

我样要是从来都没有配置过任何的applicaion文件,那所有的路径读取完成了之后,就会在系统是全部采用默认的配置数据了, 既然我们配置了,那就会在读取classpath:/application.yml的时候获取到一个有效的配置文件。

在执行new Document(propertySource, binder.bind("spring.profiles", STRING_ARRAY).orElse(null), getProfiles(binder, ACTIVE_PROFILES_PROPERTY), getProfiles(binder, INCLUDE_PROFILES_PROPERTY))的时候,是去刻意读取了spring.profiles.activespring.profiles.include的, 所以这里,我们也得到一个Active Profiles: pro.

但还是会接着把所有的文件都读取完,不会因为拿到了一个Active Profiles就中止读取。

接着pro会被添加到环境中, 然后用这个Active Profiles继续读配置文件, 读取的文件依次为:

  • file:./config/application-pro.properties
  • file:./config/application-pro.xml
  • file:./config/application-pro.yml
  • file:./config/application-pro.yaml
  • file:./application-pro.properties
  • file:./application-pro.xml
  • file:./application-pro.yml
  • file:./application-pro.yaml
  • classpath:/config/application-pro.properties
  • classpath:/config/application-pro.xml
  • classpath:/config/application-pro.yml
  • classpath:/config/application-pro.yaml
  • classpath:/application-pro.properties
  • classpath:/application-pro.xml
  • classpath:/application-pro.yml
  • classpath:/application-pro.yaml

这时,如果读到了文件的话,就会被存入到当前环境中, 这些读取出来的值还不能直接使用,毕竟里面可能存在占位符, 而且必须要声明一点的是, 无论你使用的是什么格式的文件, 最终在系统中都会统一成properties文件的格式。

接下来就是环境绑定和环境转换了。

打印Banner

SpringApplication默认的banner是private Banner.Mode bannerMode = Banner.Mode.CONSOLE;, 也就是直接打印到控制台, 创建一个SpringApplicationBannerPrinter, 调用print, 但是打印的内容来自哪里呢?

Spring尝试获取文本和图片的Banner, 至少有一个存在才会打印出Banner,否则在没有显示的设置Banner的情况下,会直接使用默认的banner.

打印Banner的查找顺序如下:

  • 从系统中查找spring.banner.image.location对应的地址是否存在"gif", "jpg", "png"的图片, 有的话,则加载进来。
  • 从系统中查找spring.banner.location查找对应的文件地址,如果没有的话,则使用系统路径下的banner.txt作为路径名, 如果存在,则加载进来。
  • 如果上面两者一个都没有, 也没有显式的配置banner地址,那就使用new SpringBootBanner()的实例来打印banner, 即一个Spring加上spring boot的版本号。

创建ApplicationContext

方法上面的注释写的是:

用于创建ApplicationContext的策略方法。默认情况下,在返回到适当的默认值之前,此方法将尊重任何显式设置的应用程序上下文或应用程序上下文类。

基于当前应用的类型, 它会加载一个org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext的类, 然后把它实例化了。

现在来看看这个被实例化的AnnotationConfigServletWebServerApplicationContext是一个什么类.
以下是它的类继承关系:
AnnotationConfigServletWebServerApplicationContext

实例化时使用的无参数构造方法。

    /**
     * Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs
     * to be populated through {@link #register} calls and then manually
     * {@linkplain #refresh refreshed}.
     */
    public AnnotationConfigServletWebServerApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

这里初始化了两个类,一个是用于读取注解定义的bean, 一个是用于扫描类路径下的bean.

AnnotatedBeanDefinitionReader

getOrCreateEnvironment时,registry instanceof EnvironmentCapable从继承关系图中可知是返回True的, 直接拿到当前环境。

接着创建conditionEvaluator, 需要将如下内部变量都填充出来:

    this.registry = registry;
    this.beanFactory = deduceBeanFactory(registry);
    this.environment = (environment != null ? environment : deduceEnvironment(registry));
    this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry));
    this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);

第一个就是AnnotationConfigServletWebServerApplicationContext自身, beanFactory也是自己返回出来的一个DefaultListableBeanFactory实例, 来自于父类GenericApplicationContext的构造方法创建的类:

    /**
     * Create a new GenericApplicationContext.
     * @see #registerBeanDefinition
     * @see #refresh
     */
    public GenericApplicationContext() {
        this.beanFactory = new DefaultListableBeanFactory();
    }

environment本身不为空,直接返回。

resourceLoader也是自己。

classLoader返回的是ClassUtils.getDefaultClassLoader()默认类加载器, 即AppClassLoader

创建完成conditionEvaluator就开始执行AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry), 所执行的主要代码为:

    /**
     * Register all relevant annotation post processors in the given registry.
     * @param registry the registry to operate on
     * @param source the configuration source element (already extracted)
     * that this registration was triggered from. May be {@code null}.
     * @return a Set of BeanDefinitionHolders, containing all bean definitions
     * that have actually been registered by this call
     */
    public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
            BeanDefinitionRegistry registry, @Nullable Object source) {

        DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
        if (beanFactory != null) {
            if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
                beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
            }
            if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
                beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
            }
        }

        Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

        if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

        if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

        // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
        if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

        // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
        if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition();
            try {
                def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
                        AnnotationConfigUtils.class.getClassLoader()));
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
            }
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
        }

        if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
        }

        if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
        }

        return beanDefs;
    }

拿到DefaultListableBeanFactory, 检查是否包含某些bean, 不包含则创建:

  • org.springframework.context.annotation.internalConfigurationAnnotationProcessor
  • org.springframework.context.annotation.internalAutowiredAnnotationProcessor
  • org.springframework.context.annotation.internalCommonAnnotationProcessor
  • org.springframework.context.annotation.internalPersistenceAnnotationProcessor
  • org.springframework.context.event.internalEventListenerProcessor
  • org.springframework.context.event.internalEventListenerFactory

检查的方式也就是从Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256)中判断键是否存在this.beanDefinitionMap.containsKey(beanName), beanDefinitionMap将会用来保存所有的bean.

如果不存在,则将该bean注册进去, 注册bean使用的方法是registerPostProcessor.

    private static BeanDefinitionHolder registerPostProcessor(
            BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {

        definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(beanName, definition);
        return new BeanDefinitionHolder(definition, beanName);
    }

注册bean的过程就是将键值对放入到beanDefinitionMap中。
由于我们没有添加jpaPresent支持,所有,最终会有五个Bean被注册成功。

ClassPathBeanDefinitionScanner
这个类没做太多事情, 首先将Component``ManagedBean``Named包含到过滤器中, 然后设置环境,设置类加载器等操作。

到此, ApplicationContext创建完成了。

刷新上下文

环境也准备好了,上下文也创建了, prepareContext这个环节主要是将环境与上下文结合在一起,然后做一些初始化的工作。

最主要的工作全部都在((AbstractApplicationContext) applicationContext).refresh()中执行, AnnotationConfigServletWebServerApplicationContext的初始化早已完成, 现在调用的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();
			}
		}
	}
准备待刷新的上下文

在try catch之前的三个方法,主要是设置环境,PropertySources, 准备BeanFactory, 主要的工作都包含在了try catch中, 也是我们重要需要关注的位置。

注册上下文中的Bean

invokeBeanFactoryPostProcessors的调用层给比较深, 主要是对Bean进行注册。

在工程中我们并没有对需要扫包的路径进行配置,系统会自动根据main方法所有的包进行扫描, 在PostProcessorRegistrationDelegateinvokeBeanFactoryPostProcessors方法里调用了invokeBeanDefinitionRegistryPostProcessors, 再深入到ConfigurationClassPostProcessor类中的processConfigBeanDefinitions, 这里定义了一个处理器:

	// Parse each @Configuration class
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

上面有一行注释// Parse each @Configuration class, 为什么会是处理所有的@Configuration注解。 先看看这个方法执行完了之后,都得到了些什么结果再来看这个问题。

接着就开始执行parser.parse(candidates);这里需要处理的BeanDefinitionHolder只有一个demoApplication, 在ComponentScanAnnotationParser的方法parse中, 定义了ClassPathBeanDefinitionScanner, 从名字可以看出, 这是通过类路径来对bean进行扫描的, 在没有设置basePackages的情况下, 将通过ClassUtils.getPackageName(declaringClass)直接截取出一个待扫描的包名, 当前类路径为com.refiny.demo.DemoApplication, 截取出的待扫描包为com.refiny.demo, 接着开始进行扫描scanner.doScan(StringUtils.toStringArray(basePackages));.

Bean的扫描与注册步骤如下:

  1. 找到包下所有的类, 得到需要被管理的bean

基于传入的待扫描包名可以得到一个完整的扫包规则:

    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;

得到的结果是: classpath*:com/refiny/demo/**/*.class, 再由之前已经注册的一个beanServletContextResourcePatternResolver来处理这个扫包规则, 它会首先将classpath*:转化为硬盘路径, 然后就是一般的文件读取操作了:

  • File[] files = dir.listFiles();
  • content.isDirectory();
  • new File[0];

最后存入Set<File> result中。

这里就将所有的指定包下的字节码文件都读了出来,注意这里是所有呦, 并没有关注这个类是否是已经被注解标记过的。

接着对拿到的类进行处理, 依次检索每一个类, 判断它是否有注解Component或者ManagedBean, 判断出哪些是需要被管理的bean.

  1. 设置Bean的属性

通过注解得到Bean的Scope, 设置到bean上, 从ScopeMetadata类上,我们中以看到, 默认的scope是scopeName = BeanDefinition.SCOPE_SINGLETONScopedProxyMode类型是ScopedProxyMode.NO.

	/**
	 * Scope identifier for the standard singleton scope: "singleton".
	 * Custom scopes can be added via {@code registerScope}.
	 * @see #registerScope
	 */
	String SCOPE_SINGLETON = "singleton";

	/**
	 * Scope identifier for the standard prototype scope: "prototype".
	 * Custom scopes can be added via {@code registerScope}.
	 * @see #registerScope
	 */
	String SCOPE_PROTOTYPE = "prototype";

Scope这里只写了两个, 但是可以通过registerScope继续扩展。 比如往后启动服务器时注册的:

	private void registerApplicationScope(ServletContext servletContext) {
		ServletContextScope appScope = new ServletContextScope(servletContext);
		getBeanFactory().registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
		// Register as ServletContext attribute, for ContextCleanupListener to detect it.
		servletContext.setAttribute(ServletContextScope.class.getName(), appScope);
	}

如果你使用Scope注解进行过其它的定义, 那就是另的scope了。

默认的两个scope的作用分别是:

  • singleton: 表示在spring容器中的单例,通过spring容器获得该bean时总是返回唯一的实例
  • prototype: 表示每次获得bean都会生成一个新的对象

然后接着获取bean的名字, 尝试去获取bean上的注解, 判断这个注解是否属于org.springframework.stereotype.Component, javax.annotation.ManagedBean, javax.inject.Named, 并且包含value这个属性, 如果多个可用于定义Bean名字的注解上的value值不一致的话,就会抛出IllegalStateException异常。

如果大家都没有写value的值, 那就采用默认的名称了, 拿到全限定名, 截取出类名, 变换一下返回出去, 他还举了一个例子:Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays as "URL"., 意思就是如果第一位是大写,则转换为小写, 如果第一位和第二位都是大写,则没变化, 直接返回。

  1. 注册Bean

当前Bean如果不存在, 那就直接注册啦, 如果已经存在, 就判断一下两个bean是不是兼容的,如果不兼容就会报错ConflictingBeanDefinitionException.

注册Bean是在DefaultListableBeanFactory中完成的, 在确保名称和Bean的定义都不为空之后, 使用bean的名称去尝试获取一Bean, 如果不存在, 则把bean放入beanDefinitionMap中, 这是一个ConcurrentHashMap.

this.beanDefinitionMap.put(beanName, beanDefinition);

synchronized中对已定义Bean名称的更新采用的是:

	List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
	updatedDefinitions.addAll(this.beanDefinitionNames);
	updatedDefinitions.add(beanName);
	this.beanDefinitionNames = updatedDefinitions;

而在synchronized外, 则是很直接的:

    this.beanDefinitionNames.add(beanName);

注册完成之后, 接着又对Bean上存在的其它注解进行解析,比如Import, ImportResource, Bean等, 至此自己的Bean注册完了,然后再接着对系统的Bean进行注册完成。

创建应用服务器

Spring Boot是内置了Tomcat的, 当前版本的Tomcat是9.0.27。 spring boot尝试去读取有哪些可用的服务器, 如果一个都没有读出来,或者读出了多于一个的, 都会抛出异常, 在没有做出任何修改的情况下, 读取到的, 自然是默认的tomcatServletWebServerFactory, 然后执行:

    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);

这是一个在spring中比较通用的流程, 去获取一个bean的实例出来, 来看一下这段创建Bean的代码:

	// Create bean instance.
	if (mbd.isSingleton()) {
		sharedInstance = getSingleton(beanName, () -> {
			try {
				return createBean(beanName, mbd, args);
			}
			catch (BeansException ex) {
				// Explicitly remove instance from singleton cache: It might have been put there
				// eagerly by the creation process, to allow for circular reference resolution.
				// Also remove any beans that received a temporary reference to the bean.
				destroySingleton(beanName);
				throw ex;
			}
		});
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
	}

这里使用了Lambda表达式, 执行结果就是返回一个创建出来的bean, 在createBean中, 最主要的代码为:

	try {
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		if (logger.isTraceEnabled()) {
			logger.trace("Finished creating instance of bean '" + beanName + "'");
		}
		return beanInstance;
	}
	catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
		// A previously detected exception with proper bean creation context already,
		// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
		throw ex;
	}
	catch (Throwable ex) {
		throw new BeanCreationException(
				mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
	}

好了, 接下来会做些很多事情, 我们重点关注一下服务器是如何被自定义配置的, 即之前在配置文件里的server.port信息是如何被应用起来的。

  1. 使用系统已经有的BeanPostProcessors在Bean创建后初始化前进行预处理。
	for (BeanPostProcessor processor : getBeanPostProcessors()) {
		Object current = processor.postProcessBeforeInitialization(result, beanName);
		if (current == null) {
			return result;
		}
		result = current;
	}

BeanPostProcessors有很多, 例如:

  • ApplicationContextAwareProcessor
  • WebApplicationContextServletContextAwareProcessor
  • ConfigurationPropertiesBindingPostProcessor
  • WebServerFactoryCustomizerBeanPostProcessor

这里重点看一下WebServerFactoryCustomizerBeanPostProcessor, 字面意思就是对服务器进行定义, 它是这么重写的:

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (bean instanceof WebServerFactory) {
			postProcessBeforeInitialization((WebServerFactory) bean);
		}
		return bean;
	}

postProcessBeforeInitialization方法如下:

	private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
		LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
				.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
				.invoke((customizer) -> customizer.customize(webServerFactory));
	}

他要做的是获取所有用于定义服务才需要的类, 包含如下一些Customizer:

  • TomcatWebSocketServletWebServerCustomizer
  • ServletWebServerFactoryCustomizer
  • TomcatServletWebServerFactoryCustomizer
  • TomcatWebServerFactoryCustomizer
  • HttpEncodingAutoConfiguration$LocaleCharsetMappingsCustomizer
    然后调用他们的customizer.customize(webServerFactory)把服务器定义出来。

这里又来重点看一下ServletWebServerFactoryCustomizer这个服务器定义类的初始化。
这个类的构造函数是带了参数的:

	public ServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
		this.serverProperties = serverProperties;
	}

所以,需要先把ServerProperties这个bean创建出来。 ServerProperties就不一样了, 它头上有一个注解ConfigurationProperties, 所以, 创建bean的过程中ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);就不会再返回空了, 就需要执行一次this.binder.bind(bean);动作, 这个动作的流程有点儿长。

主要的绑定逻辑是在JavaBeanBinder中的:

	private <T> boolean bind(DataObjectPropertyBinder propertyBinder, Bean<T> bean, BeanSupplier<T> beanSupplier) {
		boolean bound = false;
		for (BeanProperty beanProperty : bean.getProperties().values()) {
			bound |= bind(beanSupplier, propertyBinder, beanProperty);
		}
		return bound;
	}

beanSupplierBinder中的一个Lambda表达式:

(propertyName, propertyTarget) -> bind(name.append(propertyName), propertyTarget, handler, context, false, false);

现在就去找properties吧, ConfigurationPropertySource来自于context.getSources(), 之前我们通过ConfigFileApplicationListener读取出来的配置文件现在可以用上了, 另外这里也有很多系统本身就拥有的配置信息, 例如systemProperties, systemEnvironment等等。
还是把代码贴一下:

	private ConfigurationProperty findProperty(ConfigurationPropertyName name, Context context) {
		if (name.isEmpty()) {
			return null;
		}
		for (ConfigurationPropertySource source : context.getSources()) {
			ConfigurationProperty property = source.getConfigurationProperty(name);
			if (property != null) {
				return property;
			}
		}
		return null;
	}

走到这里, 我们就应该回过头来考虑另外两个问题了:

  1. 配置文件读取的选择顺序是如何的?
  2. 配置文件里的占位符该怎么处理?

读配置文件的时候, 是读到结果之后, 就立刻终止并返回了, 系统的属性是放在前面的, 因此即便是你写了类似os.name在你的配置文件中, 也会获取到正确的值, 而不是你配置的值。 例如我们当前的端口配置信息server.port, 读取文件的顺序是:

  • file:./config/application-pro.properties
  • file:./config/application-pro.xml
  • file:./config/application-pro.yml
  • file:./config/application-pro.yaml
  • file:./application-pro.properties
  • file:./application-pro.xml
  • file:./application-pro.yml
  • file:./application-pro.yaml
  • classpath:/config/application-pro.properties
  • classpath:/config/application-pro.xml
  • classpath:/config/application-pro.yml
  • classpath:/config/application-pro.yaml
  • classpath:/application-pro.properties
  • classpath:/application-pro.xml
  • classpath:/application-pro.yml
  • classpath:/application-pro.yaml
  • file:./config/application.properties
  • file:./config/application.xml
  • file:./config/application.yml
  • file:./config/application.yaml
  • file:./application.properties
  • file:./application.xml
  • file:./application.yml
  • file:./application.yaml
  • classpath:/config/application.properties
  • classpath:/config/application.xml
  • classpath:/config/application.yml
  • classpath:/config/application.yaml
  • classpath:/application.properties
  • classpath:/application.xml
  • classpath:/application.yml
  • classpath:/application.yaml

按这个顺序依次去获取值, 整理一下即, 先从文件根目录读取到工程目录, 每个目录下, config文件夹没有读取到再读根目录, 文件后缀依次为: properties > xml > yml > yaml;

配置文件读取出来之后,如果没有占位符, 会直接返回, 如果里面有占位符的话, 会在继续处理占位符:

	private <T> Object bindProperty(Bindable<T> target, Context context, ConfigurationProperty property) {
		context.setConfigurationProperty(property);
		Object result = property.getValue();
		result = this.placeholdersResolver.resolvePlaceholders(result);
		result = context.getConverter().convert(result, target);
		return result;
	}

判断和处理占位符的逻辑为:

    protected String parseStringValue(
			String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {

		int startIndex = value.indexOf(this.placeholderPrefix);
		if (startIndex == -1) {
			return value;
		}

		StringBuilder result = new StringBuilder(value);
		while (startIndex != -1) {
			int endIndex = findPlaceholderEndIndex(result, startIndex);
			if (endIndex != -1) {
				String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
				String originalPlaceholder = placeholder;
				if (visitedPlaceholders == null) {
					visitedPlaceholders = new HashSet<>(4);
				}
				if (!visitedPlaceholders.add(originalPlaceholder)) {
					throw new IllegalArgumentException(
							"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
				}
				// Recursive invocation, parsing placeholders contained in the placeholder key.
				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
				// Now obtain the value for the fully resolved key...
				String propVal = placeholderResolver.resolvePlaceholder(placeholder);
				if (propVal == null && this.valueSeparator != null) {
					int separatorIndex = placeholder.indexOf(this.valueSeparator);
					if (separatorIndex != -1) {
						String actualPlaceholder = placeholder.substring(0, separatorIndex);
						String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
						propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
						if (propVal == null) {
							propVal = defaultValue;
						}
					}
				}
				if (propVal != null) {
					// Recursive invocation, parsing placeholders contained in the
					// previously resolved placeholder value.
					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
					result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
					if (logger.isTraceEnabled()) {
						logger.trace("Resolved placeholder '" + placeholder + "'");
					}
					startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
				}
				else if (this.ignoreUnresolvablePlaceholders) {
					// Proceed with unprocessed value.
					startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
				}
				else {
					throw new IllegalArgumentException("Could not resolve placeholder '" +
							placeholder + "'" + " in value \"" + value + "\"");
				}
				visitedPlaceholders.remove(originalPlaceholder);
			}
			else {
				startIndex = -1;
			}
		}
		return result.toString();
	}

找到占位符里的内容, 又去source里找找看是否有配置相应的值, 有的话, 就替换占位符, 没有的话, 那就原封不动的放在那儿。

serverProperties拿到了, ServletWebServerFactoryCustomizer的实例也就有了, 其它一干Customizer完成之后, 就会可以执行服务器定制了。

  1. 服务器参数定制
    有了这一干Customizer, 再来依次调用(customizer) -> customizer.customize(webServerFactory), 我们也仅仅来看看一下跟我们设置的server.port相关的ServletWebServerFactoryCustomizer, 它的customize是这么重写的:
	@Override
	public void customize(ConfigurableServletWebServerFactory factory) {
		PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
		map.from(this.serverProperties::getPort).to(factory::setPort);
		map.from(this.serverProperties::getAddress).to(factory::setAddress);
		map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
		map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
		map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
		map.from(this.serverProperties::getSsl).to(factory::setSsl);
		map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
		map.from(this.serverProperties::getCompression).to(factory::setCompression);
		map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
		map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
		map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
	}

这段代码言简义赅, 就是使用我们配置出来的serverPropertiesConfigurableServletWebServerFactory进行了自定义。

至此, 服务器已经准备好了。

收尾

在最后, 还对一些不是lazy-init的实例进行了初始化, 然后就是启动服务器了。

	protected void finishRefresh() {
		// Clear context-level resource caches (such as ASM metadata from scanning).
		clearResourceCaches();

		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// Publish the final event.
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}

清除缓存的资源信息, 继续发布事件。 其实这个ContextRefreshedEvent还挺常用到的, 通常我都是在这个阶段开始对应用中我需要用到的信息开始初始化着走了。

	@Override
	protected void finishRefresh() {
		super.finishRefresh();
		WebServer webServer = startWebServer();
		if (webServer != null) {
			publishEvent(new ServletWebServerInitializedEvent(webServer, this));
		}
	}

启动服务器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园的建设目标是通过数据整合、全面共享,实现校园内教学、科研、管理、服务流程的数字化、信息化、智能化和多媒体化,以提高资源利用率和管理效率,确保校园安全。 智慧校园的建设思路包括构建统一支撑平台、建立完善管理体系、大数据辅助决策和建设校园智慧环境。通过云架构的数据中心与智慧的学习、办公环境,实现日常教学活动、资源建设情况、学业水平情况的全面统计和分析,为决策提供辅助。此外,智慧校园还涵盖了多媒体教学、智慧录播、电子图书馆、VR教室等多种教学模式,以及校园网络、智慧班牌、校园广播等教务管理功能,旨在提升教学品质和管理水平。 智慧校园的详细方案设计进一步细化了教学、教务、安防和运维等多个方面的应用。例如,在智慧教学领域,通过多媒体教学、智慧录播、电子图书馆等技术,实现教学资源的共享和教学模式的创新。在智慧教务方面,校园网络、考场监控、智慧班牌等系统为校园管理提供了便捷和高效。智慧安防系统包括视频监控、一键报警、阳光厨房等,确保校园安全。智慧运维则通过综合管理平台、设备管理、能效管理和资产管理,实现校园设施的智能化管理。 智慧校园的优势和价值体现在个性化互动的智慧教学、协同高效的校园管理、无处不在的校园学习、全面感知的校园环境和轻松便捷的校园生活等方面。通过智慧校园的建设,可以促进教育资源的均衡化,提高教育质量和管理效率,同时保障校园安全和提升师生的学习体验。 总之,智慧校园解决方案通过整合现代信息技术,如云计算、大数据、物联网和人工智能,为教育行业带来了革命性的变革。它不仅提高了教育的质量和效率,还为师生创造了一个更加安全、便捷和富有智慧的学习与生活环境。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值