工程配置
前面章节我们介绍过,SpringBoot的一个核心特性是约定优于配置,基于这一理念,SpringBoot内部包含了大量“约定”配置以减少开发者需要编写的样板式配置代码。然而,任何实际项目都离不开必要的配置,例如数据库连接信息、服务器端口、业务配置参数等。为此SpringBoot提供了一套强大且灵活的工程配置机制,允许我们将配置从代码中剥离到多个配置源,并灵活的实现配置在不同环境(开发、测试、生产)间的切换。
这篇笔记我们将详细讲解SpringBoot中配置文件的使用,包括配置文件的格式、配置值的绑定、多环境配置以及配置的加载优先级。
配置文件格式
SpringBoot支持两种主要的配置文件格式:application.properties和application.yml(或application.yaml)。它们可以共存,但实际开发中建议选择其中一种,避免二者混用。
Properties配置
.properties文件是Java中传统的配置文件格式,它采用key=value的简单语法,配置文件中可通过.来表示层级关系。
Properties配置的语法特点:
- 配置为键值对格式,
key=value - 使用点号
.来分隔层级,例如server.port=8080 - 注释以
#开头 - 值部分表达复杂数据结构(如List、Map)需要依靠特定格式(如逗号分隔)和约定来解析
下面是一个Properties配置文件的例子。
# server
server.port=8080
# mysql datasource
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=abc123
# app configuration
app.name=My Spring Boot Application
app.feature-enabled=true
app.ip-whitelist=192.168.1.1,192.168.1.2,10.0.0.1
Properties配置格式其实有两个缺点,一个是表达复杂数据结构(如List、Map)时可读性较差,另一个是该类型仅允许ISO-8859-1编码,对于中文用户,如果你在配置文件中使用中文编写注释信息可能会被IDE自动转为Unicode转义序列,可读性较差。不过Properties配置仍以其简洁、紧凑的语法而广泛使用。
YAML配置
YAML是一种更易读的配置格式,特别适合用来表达层次结构化的配置信息。SpringBoot也支持使用YAML格式配置。
YAML配置的语法特点:
- 严格使用缩进来表示层级关系,注意禁止使用Tab
- 键值对使用冒号
:加空格分隔,如key: value,注意冒号后必须有一个空格 - YAML原生支持多种基本数据结构,包括标量(字符串、数字、布尔)、数组和映射
- 注释以
#开头
下面是一个YAML配置文件的例子。
# 服务器配置
server:
port: 8080
# MySQL数据源配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: abc123
# 应用配置
app:
name: My Spring Boot Application
feature-enabled: true
ip-whitelist:
- 192.168.1.1
- 192.168.1.2
- 10.0.0.1
可以看到,YAML语法层次清晰、可读性更好,适用于复杂配置,然而它的缺点是语法严格且格式冗长。Properties和YAML具体如何选择,我们还是需要结合实际情况考虑。
加载配置
SpringBoot中,加载配置文件中的有两种主要方式:使用@Value注解,或使用@ConfigurationProperties进行类型安全的绑定。@Value适用于注入单个、分散的配置值,在Spring Framework章节我们已经介绍过了;而@ConfigurationProperties则是将一组具有共同前缀的配置批量、结构化地绑定到一个Java Bean,对于复杂的结构化配置信息,后者是更推荐的方式,因为它提供了更好的类型安全和IDE支持。
使用@ConfigurationProperties
假设我们有如下配置。
app.name=My Spring Boot Application
app.feature-enabled=true
app.ip-whitelist=192.168.1.1,192.168.1.2,10.0.0.1
对应的配置属性类可以如下定义。
package com.gacfox.demo.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Data
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private boolean featureEnabled;
private List<String> ipWhitelist;
}
对于配置属性类,我们可以像校验普通的Bean一样,使用JSR-303/380 Bean Validation注解来校验配置属性的值,不过这里别忘了使用配置校验需要添加相关的起步依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
下面是一个带有配置数据校验的配置属性类。
package com.gacfox.demo.config;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import java.util.List;
@Data
@Component
@Validated
@ConfigurationProperties(prefix = "app")
public class AppProperties {
@NotBlank
private String name;
@NotNull
private Boolean featureEnabled;
@NotEmpty
private List<String> ipWhitelist;
}
如果配置值不满足校验条件,应用将无法启动并给出明确的错误信息。
松散绑定
SpringBoot支持配置的松散绑定(Relaxed Binding),即配置文件中的属性名可以采用多种格式(kebab-case、snake_case、camelCase),SpringBoot都能将其匹配到对应的配置属性类字段上,这在实际开发中非常实用。对于大多数SpringBoot程序,配置文件中通常都采用kebab-case,而Java类中使用camelCase,这种写法符合绝大多数Java开发者的习惯,这里也建议遵循这个约定。
Profile多环境配置
在实际开发中,应用通常需要在开发、测试、生产等不同环境中运行,每个环境的配置(如数据库地址、外部接口地址、日志级别等)可能截然不同。SpringBoot对配置的环境隔离提供了内置的支持。
环境配置文件
SpringBoot中,我们可以创建名为application-{profile}.properties(或.yml)的配置文件,例如:
application.properties:主配置文件application-dev.properties:用于开发环境application-test.properties:用于测试环境application-prod.properties:用于生产环境
在主配置文件application.properties中我们可以设置默认激活的Profile,此外在对应环境上,我们还可以使用命令行参数--spring.profiles.active设置当前需要激活的Profile,后者拥有更高的优先级。加载时,环境特定的配置文件中的配置会覆盖主配置文件中的同名配置,并且只在该Profile激活时生效。下面是一个多环境配置例子,该组配置中默认激活dev环境。
application.properties
spring.profiles.active=dev
app.greeting=Hello from default config!
application-dev.properties
app.greeting=Hello from Development!
app.database-url=jdbc:h2:mem:devdb
app.log-level=debug
application-prod.properties
app.greeting=Welcome to Production!
app.database-url=jdbc:mysql://prod-db-host:3306/proddb
app.log-level=info
在生产环境服务器上,我们可以配置启动命令添加参数--spring.profiles.active=prod来激活生产配置。
激活Profile
实际上,除了配置文件中的默认激活Profile和命令行参数--spring.profiles.active,我们还有多种方式可以激活Profile,且Profile可以同时激活多个,优先级从高到低分别如下:
- 命令行参数:
java -jar myapp.jar --spring.profiles.active=prod,feature-a - JVM系统属性:
-Dspring.profiles.active=prod - 环境变量:
SPRING_PROFILES_ACTIVE=prod - 配置文件内指定:在配置文件中设置
spring.profiles.active,常用于配置默认激活dev环境
Profile分组
SpringBoot还支持Profile分组功能,我们可以将多个Profile定义为一组,激活该组时等同于激活组内所有Profile。
spring.profiles.group.production=proddb,prodmetrics
spring.profiles.group.local=dev,h2db,debuglog
如上配置后,例如设置--spring.profiles.active=production即可激活整个组。
在代码中使用Profile判断
我们可以使用@Profile注解来条件化地注册Bean或配置类,下面是一个例子。
@Configuration
public class DataSourceConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
return DataSourceBuilder.create().build();
}
}
在代码中读取当前激活的Profile
这里要尤其注意,虽然Profile可通过spring.profiles.active配置,但不要使用@Value("${spring.profiles.active}")来注入这个属性,这个属性在应用启动早期可能不可用或为空,会导致注入null或默认值。实际开发中,建议基于Environment来获取当前激活的Profile。
package com.gacfox.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class DemoComponent {
private final Environment environment;
@Autowired
public DemoComponent(Environment environment) {
this.environment = environment;
}
public void printProperties() {
String[] activeProfiles = environment.getActiveProfiles();
log.info("Active Profiles: {}", String.join(", ", activeProfiles));
}
}
指定外部配置文件
除了之前提到的几个配置源,SpringBoot还可以在启动参数中使用spring.config.additional-location追加指定外部配置文件路径,它具有更高的优先级,下面是一个例子。
java -jar app.jar --spring.config.additional-location=/home/ubuntu/configmap.properties
--spring.config.additional-location:指定配置文件路径,支持可以指定多个(使用逗号分隔),后指定的相同配置键会覆盖前面的
@SpringBootApplication注解分析
前面我们多次提到过,SpringBoot的核心特性之一是自动配置。在手动配置Spring的时代,我们编写了大段XML,而到了SpringBoot时代,配置则相当简化了。那么SpringBoot是如何实现自动配置的?这里我们逐步分析。
SpringBoot的启动类上标注了@SpringBootApplication注解,我们查看其源码,其实它是一个复合的注解,其中主要包括@SpringBootConfiguration和@EnableAutoConfiguration。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// ...
}
我们可以发现,@SpringBootConfiguration其实和@Configuration的作用类似,都是用来标注配置类的。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
// ...
}
SpringBootApplication注解上的@EnableAutoConfiguration也是一个复合注解,包括@AutoConfigurationPackage和@Import。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
// ...
}
AutoConfigurationImportSelector用于扫描加载SpringBoot内置的配置类。我们可以在spring-boot-autoconfigure包中的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中找到自动配置类的列表。
@AutoConfigurationPackage中也包含了@Import注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
// ...
}
而AutoConfigurationPackages.Registrar用于扫描启动类包的子包中的组件注册到Spring容器。
由上述分析总结来看,我们可以得出结论,SpringBoot框架默认会从启动类和spring-boot-autoconfigure定义的自动配置类注册Spring配置,此外还会扫描启动类包的子包中的组件。这里再额外补充一点,如果我们定义的SpringBean不在启动类包的子包下,就需要手动指定扫描包,SpringBoot提供了@ComponentScan注解,我们可以将其标注到启动类上并配置相关的包名。
@SpringBootApplication
@ComponentScan({"com.gacfox.bootdemo"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
spring-boot-autoconfigure包中包含了大量自动配置,我们具体开发如果用到相关功能,推荐查看一下其源码,简要了解其中都配置了哪些内容。

覆盖配置JavaConfig
SpringBoot中,有时仅仅覆盖application.properties中的配置项在某些极特殊情况下可能还是无法满足我们的需求,此时我们需要彻底重写某个Bean的JavaConfig配置代码。实际上,spring-boot-autoconfigure包中的自动配置类大都允许我们这么做。这里我们简单分析一下,我们可以打开任意一个spring-boot-autoconfigure包中的自动配置类,下面是一个例子。
@AutoConfiguration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RedisConnectionDetails.class)
PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties,
ObjectProvider<SslBundles> sslBundles) {
return new PropertiesRedisConnectionDetails(properties, sslBundles.getIfAvailable());
}
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
我们看到JavaConfig装配的Bean标注了@ConditionalOnMissingBean注解,该注解表示如果我们自定义了配置Bean,那么就使用我们自定义的,不会再使用内置的配置类生成Bean了;如果没有自定义的,才使用该配置对象。了解了这些,我们想覆盖默认的JavaConfig也就比较简单了,我们按照自动配置类定义的Bean,注册我们自己的JavaConfig配置类即可。