首页 > 技术文章 > Spring Boot的自动配置

zhangyunzhu 2020-11-06 16:06 原文

在教程《@SpringBootApplication注解》中讲到 @EnableAutoConfiguration 可以借助 SpringFactoriesLoader 这个特性将标注了 @Configuration 的 JavaConfig 类“一股脑儿”的汇总并加载到最终的 ApplicationContext,不过,这其实只是“简化版”的说明。

实际上,基于 @EnableAutoConfiguration 的自动配置功能拥有更加强大的调控能力,通过配合比如基于条件的配置能力或者调整加载顺序,我们可以对自动配置进行更加细粒度的调整和控制。

 

基于条件的自动配置

基于条件的自动配置来源于 Spring 框架中“基于条件的配置”这一特性。在 Spring 框架中,我们可以使用 @Conditional 这个 Annotation 配合 @Configuration 或者 @Bean 等 Annotation 来干预一个配置或者 bean 定义是否能够生效,其最终实现的效果或者语义类似于如下伪代码:

if(符合 @Conditional 规定的条件){
    加载当前配置(enable current Configuration)或者注册当前bean定义;
}

要实现基于条件的配置,我们只要通过 @Conditional 指定自己的 Condition 实现类就可以了(可以应用于类型 Type 的标注或者方法 Method 的标注):

@Conditional({MyCondition1.class, MyCondition2.class, ...})

最主要的是,@Conditional 可以作为一个 Meta Annotation 用来标注其他 Annotation 实现类,从而构建各色的复合 Annotation,比如 SpringBoot 的 autoconfigure 模块就基于这一优良的革命传统,实现了一批 Annotation(位于 org.springframework.boot.autoconfigure.condition 包下),条件注解如下:

  • @ConditionalOnBean:当容器里有指定的 Bean 的条件下。
  • @ConditionalOnClass:当类路径下有指定的类的条件下。
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件。
  • @ConditionalOnJava:基于 JVM 版本作为判断条件。
  • @ConditionalOnJndi:在 JNDI 存在的条件下查找指定的位置。
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下。
  • @ConditionalOnMissingClass:当类路径下没有指定的类的条件下。
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下。
  • @ConditionalOnProperty:指定的属性是否有指定的值。
  • @ConditionalOnResource:类路径是否有指定的值。
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选的 Bean。
  • @ConditionalOnWebApplication:当前项目是 Web 项目的条件下。


有了这些复合 Annotation 的配合,我们就可以结合 @EnableAuto-Configurationn 实现基于条件的自动配置了。

SpringBoot 能够风靡,很大一部分功劳需要归功于它预先提供的一系列自动配置的依赖模块,而这些依赖模块都是基于以上 @Conditional 复合 Annotation 实现的,这也意味着所有的这些依赖模块都是按需加载的,只有符合某些特定条件,这些依赖模块才会生效,这也就是我们所谓的“智能”自动配置。

调整自动配置的顺序

在实现自动配置的过程中,除了可以提供基于条件的配置,我们还可以对当前要提供的配置或者组件的加载顺序进行相应调整,从而让这些配置或者组件之间的依赖分析和组装可以顺利完成。

我们可以使用 @org.springframework.boot.autoconfigure.AutoConfigureBefore 或者 @org.springframework.boot.autoconfigure.AutoConfigureAfter 让当前配置或者组件在某个其他组件之前或者之后进行,比如,假设我们希望某些 JMX 操作相关的 bean 定义在 MBeanServer 配置完成之后进行,那么我们就可以提供一个类似如下的配置:

  1. @Configuration
  2. @AutoConfigureAfter(JmxAutoConfiguration.class)
  3. public class AfterMBeanServerReadyConfiguration {
  4. @AutoWired
  5. MBeanServer mBeanServer;
  6. //通过 @Bean 添加必要的 bean 定义
  7. }

至此,我们对 SpringBoot 的核心组件完成了基本的剖析,综合来看,大部分的东西都是 Spring 框架背后原有的一些概念和实践方式,SpringBoot 只是在这些概念和实践方式上对特定的场景实现进行了固化和升华,而也恰恰是这些固化让我们开发基于 Spring 框架的应用更加方便高效。

 

一般认为,SpringBoot 微框架从两个主要层面影响 Spring 社区的开发者们:

  • 基于 Spring 框架的“约定优先于配置(COC)”理念以及最佳实践之路。
  • 提供了针对日常企业应用研发各种场景的 spring-boot-starter 自动配置依赖模块,如此多“开箱即用”的依赖模块,使得开发各种场景的 Spring 应用更加快速和高效。


SpringBoot 提供的这些“开箱即用”的依赖模块都约定以 spring-boot-starter- 作为命名的前缀,并且皆位于 org.springframework.boot 包或者命名空间下(虽然 SpringBoot 的官方参考文档中提到不建议大家使用 spring-boot-starter- 来命名自己写的类似的自动配置依赖模块,但实际上,配合不同的 groupId,这不应该是什么问题)。

如果我们访问 http://start.spring.io,并单击图 1 中的“Switch to the full version”链接,就会发现 SpringBoot1.3.2 默认支持和提供了大约 80 多个自动配置依赖模块。

 

 

鉴于数量如此之多,并且也不是所有人都会在任何一个应用中用到所有,这里我们只对几个常见的通用 spring-boot-starter 模块进行讲解,希望大家可以举一反三,灵活应用所有日后工作过程中将会用到的那些 spring-boot-starter 模块。

所有的 spring-boot-starter 都有约定俗成的默认配置,但允许我们调整这些配置以改变默认的配置行为,即“约定优先于配置”。在介绍相应的 spring-boot-starter 的默认配置(约定)以及可调整配置之前,我们有必要对 SpringBoot 应用的配置约定先做一个简单的介绍。

spring-boot-starter-logging和spring-boot-starter-web

 

本节主要讲解 spring-boot-starter-logging 和 spring-boot-starter-web 两个常见通用的 spring-boot-starter 模块。

应用日志和spring-boot-starter-logging

Java 的日志系统多种多样,从 java.util 默认提供的日志支持,到 log4j,log4j2,commons logging 等,复杂繁多,所以,应用日志系统的配置就会比较特殊,从而 spring-boot-starter-logging 也比较特殊一些,下面将其作为我们第一个了解的自动配置依赖模块。

假如 maven 依赖中添加了 spring-boot-starter-logging,如以下代码所示:

  1. <dependency>
  2. <groupId> org.springframework.boot </groupId>
  3. <artifactId> spring-boot-starter-logging </artifactId>
  4. </dependency>

那么,我们的 SpringBoot 应用将自动使用 logback 作为应用日志框架,SpringBoot 启动的时候,由 org.springframework.boot.logging.Logging-Application-Listener 根据情况初始化并使用。

SpringBoot 为我们提供了很多默认的日志配置,所以,只要将 spring-boot-starter-logging 作为依赖加入到当前应用的 classpath,则“开箱即用”,不需要做任何多余的配置,但假设我们要对默认 SpringBoot 提供的应用日志设定做调整,则可以通过几种方式进行配置调整:

  • 遵循 logback 的约定,在 classpath 中使用自己定制的 logback.xml 配置文件。
  • 在文件系统中任何一个位置提供自己的 logback.xml 配置文件,然后通过 logging.config 配置项指向这个配置文件来启用它,比如在 application.properties 中指定如下的配置。

logging.config=/{some.path.you.defined}/any-logfile-name-I-like.log


SpringBoot 默认允许我们通过在配置文件或者命令行等方式使用 logging.file 和 logging.path 来自定义日志文件的名称和存放路径,不过,这只是允许我们在 SpringBoot 框架预先定义的默认日志系统设定的基础上做有限的设置,如果我们希望更灵活的配置,最好通过框架特定的配置方式提供相应的配置文件,然后通过 logging.config 来启用。

如果大家更习惯使用 log4j 或者 log4j2,那么也可以采用类似的方式将它们对应的 spring-boot-starter 依赖模块加到 Maven 依赖中即可:

  1. <dependency>
  2. <groupId> org.springframework.boot </groupId>
  3. <artifactId> spring-boot-starter-log4j </artifactId>
  4. </dependency>

或者

  1. <dependency>
  2. <groupId> org.springframework.boot </groupId>
  3. <artifactId> spring-boot-starter-log4j2 </artifactId>
  4. </dependency>


但一定不要将这些完成同一目的的 spring-boot-starter 都加到依赖中。

快速 Web 应用开发与 spring-boot-starter-web

在这个互联网时代,使用 Spring 框架除了开发少数的独立应用,大部分情况下实际上在使用 SpringMVC 开发 web 应用,为了帮我们简化快速搭建并开发一个 Web 项目,SpringBoot 为我们提供了 spring-boot-starter-web 自动配置模块。

只要将 spring-boot-starter-web 加入项目的 maven 依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>

我们就得到了一个直接可执行的 Web 应用,当前项目下运行 mvn spring-boot:run 就可以直接启动一个使用了嵌入式 tomcat 服务请求的 Web 应用,只不过,我们还没有提供任何服务 Web 请求的 Controller,所以,访问任何路径都会返回一个 SpringBoot 默认提供的错误页面(一般称其为 whitelabel error page),我们可以在当前项目下新建一个服务根路径 Web 请求的 Controller 实现:

  1. @RestController
  2. public class IndexController {
  3. @RequestMapping("/")
  4. public String index() {
  5. return "hello, there";
  6. }
  7. }

重新运行 mvn spring-boot:run 并访问 http://localhost:8080,错误页面将被我们的 Controller 返回的消息所替代,一个简单的 Web 应用就这样完成了。

但是,简单的背后,其实却有很多“潜规则”(约定),我们只有充分了解了这些“潜规则”,才能更好地应用 spring-boot-starter-web。

项目结构层面的约定

项目结构层面与传统打包为 war 的 Java Web 应用的差异在于,静态文件和页面模板的存放位置变了,原来是放在 src/main/webapp 目录下的一系列资源,现在都统一放在 src/main/resources 相应子目录下,比如:

  • src/main/resources/static 用于存放各类静态资源,比如 css,js 等。
  • src/main/resources/templates 用于存放模板文件,比如 *.vm。


当然,如果还是希望以 war 包的形式,而不是 SpringBoot 推荐使用的独立 jar 包形式发布 Web 应用,也可以继续原来 Java Web 应用的项目结构约定。

SpringMVC 框架层面的约定和定制

spring-boot-starter-web 默认将为我们自动配置如下一些 SpringMVC 必要组件:

  • 必要的 ViewResolver,比如 ContentNegotiatingViewResolver 和 Bean-NameViewResolver。
  • 将必要的 Converter、GenericConverter 和 Formatter 等 bean 注册到 IoC 容器。
  • 添加一系列的 HttpMessageConverter 以便支持对 Web 请求和相应的类型转换。
  • 自动配置和注册 MessageCodesResolver。
  • 其他。


任何时候,如果我们对默认提供的 SpringMVC 组件设定不满意,都可以在 IoC 容器中注册新的同类型的 bean 定义来替换,或者直接提供一个基于 WebMvcConfigurerAdapter 类型的 bean 定义来定制,甚至直接提供一个标注了 @EnableWebMvc 的 @Configuration 配置类完全接管所有 SpringMVC 的相关配置,自己完全重新配置。

推荐阅读