
1.2 Spring Boot核心特性
1.2.1 Web服务器:WebServer
内置Servlet容器已经成为过去式了,新版本的Spring Boot将此特性称为WebServer。
早期的Java Web应用都需要构造成WAR包(WEB-INF目录下有个 web.xml文件,大家一定不陌生),然后部署到Servlet容器(比如Tomcat、Undertow、Jetty)。
如果是Spring MVC应用,需要在web.xml中配置DispatcherServlet这个Servlet和对应的url-pattern,url-pattern 默认会拦截所有的请求。DispatcherServlet 拦截请求后,再通过内部的HandlerMapping 根据 URL 信息去匹配 Controller,最终找到匹配到的 Controller,然后使用Controller内部的方法对请求进行处理。
WebServer表示一个可配置的Web服务器(比如TomcatWebServer、JettyWebServer、NettyWebServer、UndertowServletWebServer、UndertowWebServer),可以通过 WebServer 接口对外提供的start方法启动服务器,用stop方法停止服务器。有了WebServer后,我们不再需要关心外部的 Web服务器、web.xml文件、各种 Servlet和 Filter的配置等因素,只需要编写代码并把项目打包成JAR文件后直接运行即可,这非常适合云原生架构中的可独立部署特性。
WebServer接口的定义如下:


对于WebServer,可通过ReactiveWebServerFactory或ServletWebServerFactory工厂接口去创建。这两个工厂接口对应的代码如下:

ConfigurableWebServerFactory 接口继承自 WebServerFactory 接口(这是一个空接口)和 ErrorPageRegistry接口(错误页注册表,内部维护错误页信息,提供 addErrorPages方法以添加错误页到 WebServer 中),ConfigurableWebServerFactory 接口表示这是一个可配置的WebServerFactory接口,定义如下:


ConfigurableWebServerFactory 提供了 WebServer 常用的配置信息,它的子接口表示各个WebServer 实现类独有的配置,比如 ConfigurableJettyWebServerFactory对应 Jetty独有的配置,ConfigurableTomcatWebServerFactory对应 Tomcat 独有的配置。真正创建 WebServer的工厂类(如 TomcatServletWebServerFactory)通过继承和实现接口的方式实现了 ConfigurableWebServer-Factory和ServletWebServerFactory这两个接口。
Spring Boot对于 WebServer概念新增了一些事件,比如 WebServerInitializedEvent 事件,表示ApplicationContext刷新过后且WebServer处于ready状态下会触发的事件。
提示:Spring Cloud服务注册的时机就是在WebServerInitializedEvent事件被触发的时候。
我们常用的 spring-boot-starter-web 模块默认使用的是 Tomcat(Pom 里存在 spring-boot-starter-tomcat依赖),如果要使用Undertow或者Jetty,需要在 spring-boot-starter-web 依赖中排除 spring-boot-starter-tomcat 依赖,然后加上 spring-boot-starter-undertow (对应Undertow)或spring-boot-starter-jetty(对应Jetty)依赖。


若要使用NettyServer,则需要使用 spring-boot-starter-webflux(内部的 spring-boot-starter-reactor-netty 触发 ReactiveWebServerFactoryConfiguration#EmbeddedNetty 自动化配置生效)代替spring-boot-starter-web。
1.2.2 条件注解:@ConditionalOnXX
Spring Boot 有一个很重要的模块—spring-boot-autoconfigure,该模块内部包含了很多第三方依赖的 AutoConfiguration(自动化配置类),比如 KafkaAutoConfiguration、GsonAutoConfiguration、ThymeleafAutoConfiguration、WebMvcAutoConfiguration 等。这些AutoConfiguration只会在特定的情况下才会生效,这里的特定情况其实就是条件注解。
Spring Boot提供的条件注解如表1-1所示。
表1-1

续表

表 1-1中,条件注解仅仅只是一个注解,真正的判断逻辑在这些条件注解的解析类内部。解析类内部会根据注解的属性来判断是否满足条件,比如,OnJavaCondition条件注解解析类对应的是@ConditionOnJava条件注解,其内部会判断当前应用的JDK版本是否正确。内部处理逻辑的代码如下:


理解了这些条件注解后,Spring Boot在哪里使用这些条件注解来判断是否需要加载自动化配置类呢?Spring Boot通过spring-context模块中提供的ConditionEvaluator完成这个动作。
ConditionEvaluator在 AnnotatedBeanDefinitionReader、ClassPathScanningCandidate-ComponentProvider、ConfigurationClassBeanDefinitionReader、ConfigurationClassParser、AnnotatedBeanDefinitionReader 这些类扫描组件、配置类解析组件扫描、解析组件的时候来判断是否需要跳过(skip)某些配置类,具体的解析逻辑这里不再展开介绍。
Spring Cloud内部定义的一些新的条件注解如表1-2所示。
表1-2

续表

1.2.3 工厂加载机制
1.2.2 节介绍了条件注解作用于自动化配置类。那么这些自动化配置类是从哪里被加载的呢?这是通过工厂加载机制(factory loading mechanism)来实现的,这个机制会从 META-INF/spring.factories 文件中加载自动化配置类。下面是 spring-boot-autoconfigure 模块里META-INF/spring.factories文件的一部分内容:


spring.factories 是一个 properties 格式的文件。key 是一个类的全称,比如,“org.spring-framework.boot.autoconfigure.EnableAutoConfiguration”,value 是用“,”分割的自动化配置类的全称列表。
启动 Spring Boot 应用的@SpringBootApplication 注解内部被@EnableAutoConfiguration 注解修饰,@EnableAutoConfiguration注解会导入AutoConfigurationImportSelector这个ImportSelector。AutoConfigurationImportSelector内部的selectImports要导入的配置类是通过SpringFactoriesLoader获取的。

在SpringFactoriesLoader的加载过程中,选择的key(对应spring.factories文件中的key)是EnableAutoConfiguration这个类对应的类全名。
Spring Cloud内部也使用了工厂加载机制并扩展了一些 key。比如,org.springframework.cloud.bootstrap.BootstrapConfiguration用于在Bootstrap过程中加载对应的配置类。
1.2.4 配置加载机制
Spring Boot 把配置文件的加载封装成了 PropertySourceLoader 接口,该接口的定义如下:

Spring Boot对于该接口只有两种实现:
·PropertiesPropertySourceLoader:加载properties或xml文件。
·YamlPropertySourceLoader:加载yaml或yml文件。
提示:resources/application.properties 或 resources/application.yaml 配置文件就是被这两种ProperySourceLoader所加载的。
SpringApplication内部维护着一个 ApplicationListener集合属性,用于监听 ApplicationEvent。默认情况下,该属性会被工厂加载机制所加载(加载的 key 为 org.springframework.context.ApplicationListener):

spring-boot模块里的META-INF/spring.factories中存在ConfigFileApplicationListener:

ConfigFileApplicationListener 是 Spring Boot 配置加载的核心类,它实现了 Environment-PostProcessor 接口。EnvironmentPostProcessor 接口是配置环境信息 Environment 的PostProcessor,可以对应用的Environment进行修改。
由于 ConfigFileApplicationListener 实现了 ApplicationListener 接口,会监听 Spring 的事件。其中,对ApplicationEnvironmentPreparedEvent 进行了监听,会调用 onApplicationEnvironment-PreparedEvent方法:


ConfigFileApplicationListener 的 postProcessEnvironment方法内部构造了 Loader,并调用load方法进行配置文件的加载:

Loader内部有不少细节,比如,配置文件的文件名是根据spring.config.name配置项来决定的,不设置时默认为 application。默认配置文件的加载路径为 classpath:/、classpath:/config/、file:./和 file:./config/,这个加载路径可以通过 spring.config.location 配置项来修改。spring.profiles.active用于指定生效的profile等,这里不再具体展开介绍。
提示:Spring Cloud在加载过程中把spring.config.name 配置设置成了bootstrap,所以加载的文件名是bootstrap.properties或bootstrap.yml。
1.2.5 Spring Boot Actuator
Spring Boot 提供了不少 Production-ready 的特性,这些特性需要引入模块 spring-boot-starter-actuator才能自动生效。

其中,Endpoint 是比较核心的功能,其作用是让我们监控应用,以及跟应用之间的交互。比如,Health Endpoint、HttpTrace Endpoint 用于应用的健康检查和 HTTP 历史链路(应用监控),Loggers Endpoint用于动态改变应用的日志级别(应用交互)。
表1-3列出了常用的一些Endpoint。
表1-3

续表

提示:在Spring Boot 2.x以后的版本中,Endpoint 对应的路径是/actuator/endpointid;对于Spring Boot 1.x,路径是/endpointid。
Spring Cloud也创建了一些新的Endpoint,如表1-4所示。
表1-4

Spring Cloud母公司Pivotal旗下的Cloud Foundry产品基于Endpoint做了很多图形化的界面,例如,Health Endpoint(健康检查)对应的界面如图1-1所示,Loggers Endpoint(修改日志级别)对应的界面如图1-2所示,HttpTrace Endpoint(显示HTTP链路)对应的界面如图1-3所示。

图1-1

图1-2

图1-3
对Spring Boot的核心概念有了一定的了解之后,我们开始进入Spring Cloud世界。