新版SpringBoot无法主动读取bootstrap.yml(解决方案及源码追踪)

技术栈:Springboot 2024.0.0 + MyBatisPlus3 + MySql8 + Hikari连接池
前言:

在使用新版 Springboot 搭建微服务时 发现配置数据源失败(Failed to configure a DataSource: ‘url‘ attribute is not specified and no emb)如下图依赖 配置 注解等所示 所有均为正确

注意:

因追踪代码过长 所以放在最后方 有兴趣的可以看一下

相关依赖如下:
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.4.1</version>
    <relativePath/>
</parent>

<dependencies>
    <!-- MySQL Connector -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.31</version>
    </dependency>
    <!-- Mybatis-Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.9</version>
    </dependency>
</dependencies>
相关配置如下:
# DataSource 配置
spring:
  application:
    name: ai-grading-base # 服务名 将在 Eureka 中注册
  datasource:
    url: jdbc:mysql://localhost:3306/xxx-xxx?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: xxx
    password: yyy
    hikari:
      maximum-pool-size: 10  # 设置最大连接池大小
      minimum-idle: 5        # 最小空闲连接数
      idle-timeout: 30000    # 空闲连接的最大存活时间 单位:毫秒
      max-lifetime: 600000   # 连接最大生命周期 单位:毫秒
      connection-timeout: 30000  # 连接超时时间 单位:毫秒
      validation-timeout: 5000    # 校验连接的超时时间 单位:毫秒
      leak-detection-threshold: 15000  # 连接泄漏检测阈值 单位:毫秒
      pool-name: HikariCP      # 连接池的名称
      auto-commit: true        # 是否启用自动提交

# MyBatis 配置
mybatis-plus:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: xxx.xxx.xxx.pojo  
  configuration:
    map-underscore-to-camel-case: true
    #开启日志打印
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
启动类注解:
@MapperScan("xxx.xx.xxx.mapper")
@SpringBootApplication
public class AiGradingBaseApplication {
    public static void main(String[] args) {
        SpringApplication.run(AiGradingBaseApplication.class, args);
    }
}
配置数据源失败(Failed to configure a DataSource: ‘url‘ attribute is not specified and no emb)
开发场景:

采用 Mysql 8 配置 bootstrap.yml 失败

报错原因:

在 Spring Boot 2.4 之前 配置文件的加载顺序是按照以下顺序进行的:

  1. application.properties
  2. application.yml
  3. bootstrap.properties
  4. bootstrap.yml

其中 bootstrap.yml 的加载优先级大于 application.yml 因此如果同一个配置项在两个文件中都有定义 bootstrap.yml 中的值会覆盖 application.yml 中的值

配置文件加载优先级

  • bootstrap.yml: 用于在应用程序启动时加载应用上下文之前的配置 通常用于配置服务发现 配置中心等
  • application.yml: 用于在应用程序上下文加载之后加载配置 通常用于业务相关的配置

关键点:

  • bootstrap.yml 在 Spring Boot 2.4 之前的版本中有更高的优先级 因此如果配置在 bootstrap.yml 和 application.yml 中重复定义 bootstrap.yml 中的配置会生效
  • 从 Spring Boot 2.4 开始 bootstrap.yml 已经被移除 并且将其功能移交给 application.yml 和 application.properties Spring Cloud 配置被集成到 application.yml 中
  • 如果使用的是 Spring Boot 2.4 之后的版本 应该最好禁用 bootstrap.yml 改用 application.yml 来进行配置
主要解决方案一:将 bootstrap.yml 配置移动至 application.yml 既可
主要解决方案二:认真检查MySql(datascoure)配置文件是否有误 特别是Tab缩进等 可参考上方贴出的配置
主要解决方案三:使用 spring.cloud.bootstrap.enabled 属性启用引导配置

bootstrap.yml 文件的引导配置仍然可以通过设置 spring.cloud.bootstrap.enabled 属性来启用 如果该属性设置为 true Spring Cloud 将尝试加载 bootstrap.yml 文件 并将其中的配置应用于应用程序的环境中

关键步骤:
  • 确保 spring.cloud.bootstrap.enabled=true 配置被激活 可以通过系统属性 环境变量或配置文件中设置
  • bootstrap.yml 会被自动加载并包含在 Spring 环境中

例如 可以在 application.propertiesapplication.yml 中配置:

spring.cloud.bootstrap.enabled=true

或者通过环境变量或系统属性传递:

-Dspring.cloud.bootstrap.enabled=true
代码层面:

在新版 Spring Cloud 中 PropertyUtils.bootstrapEnabled() 方法会检查 spring.cloud.bootstrap.enabled 属性的值以及 Marker 类的存在 从而决定是否启用 bootstrap 配置

public static boolean bootstrapEnabled(Environment environment) {
    return (Boolean) environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, false) || MARKER_CLASS_EXISTS;
}
主要解决方案四:读取 bootstrap.yml 配置文件

在新版 Spring Cloud 中 bootstrap.yml 文件可以直接通过 Spring 的配置机制加载 通常无需手动指定文件路径 Spring Cloud 会根据 spring.cloud.bootstrap.enabled 属性的设置决定是否加载 bootstrap.ymlbootstrap.properties 文件

典型的 bootstrap.yml 配置文件:
spring:
  cloud:
    config:
      uri: http://localhost:8888
    bootstrap:
      enabled: true

该配置告诉 Spring Cloud Config Client 从指定的 Config Server 获取配置 并在应用程序启动时加载

主要解决方案五: 使用 ConfigurableEnvironmentApplicationContext 读取引导配置

在 Spring Cloud 中 可以通过 ConfigurableEnvironmentApplicationContext 来访问和读取 bootstrap.yml 配置文件中的属性

ConfigurableEnvironment environment = event.getEnvironment();
String configUri = environment.getProperty("spring.cloud.config.uri", String.class, "http://localhost:8888");

通过这种方式 可以读取 bootstrap.yml 文件中配置的其他属性(如 spring.cloud.config.uri) 并将其用于应用程序的初始化过程

主要解决方案六: 手动加载 bootstrap.yml 文件(如果需要)

在一些特定的应用场景下 可能需要手动加载 bootstrap.yml 文件 Spring 提供了多种方式加载外部配置文件 包括使用 @PropertySource 注解或者 Environment 对象 此方法一般用于需要手动控制文件加载顺序或路径的情况

@Configuration
@PropertySource("classpath:/bootstrap.yml")
public class BootstrapConfig {
    @Value("${spring.cloud.config.uri}")
    private String configUri;
}
主要解决方案七: 通过 @EnableConfigurationProperties 绑定配置

如果希望将 bootstrap.yml 配置文件中的属性绑定到一个配置类中 可以使用 @EnableConfigurationProperties 注解来启用属性绑定 这种方式在新版中也非常常见

@Configuration
@EnableConfigurationProperties(ConfigProperties.class)
public class ConfigBootstrap {
    // 自动注入配置类
    @Autowired
    private ConfigProperties configProperties;
}

其中 ConfigProperties 类可以定义从 bootstrap.yml 文件中读取的配置属性:

@ConfigurationProperties(prefix = "spring.cloud.config")
public class ConfigProperties {
    private String uri;

    // Getter and Setter
}
主要解决方案八: 通过 ApplicationContextInitializer 初始化 bootstrap.yml 配置

Spring Cloud 使用了 ApplicationContextInitializer 来初始化 Spring 应用上下文 在 BootstrapApplicationListenerApplicationContextInitializer 被用来处理 bootstrap.yml 配置的加载和处理

如果你需要定制加载逻辑 可以通过实现自己的 ApplicationContextInitializer 来进一步控制 bootstrap.yml 的加载顺序和方式:

public class CustomBootstrapApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 自定义初始化逻辑 处理 bootstrap 配置
    }
}

并在 SpringApplication 启动时注册这个初始化器:

SpringApplication app = new SpringApplication(MyApplication.class);
app.addInitializers(new CustomBootstrapApplicationContextInitializer());
app.run(args);
主要解决方案九: 使用 SpringApplication 配置程序的监听器

Spring Cloud 使用了 BootstrapApplicationListener 来处理 bootstrap.yml 文件的加载和应用配置 它在 ApplicationEnvironmentPreparedEvent 事件中触发 并将配置应用到 Spring 环境

public class BootstrapApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        if (PropertyUtils.bootstrapEnabled(environment)) {
            // 初始化 bootstrap 上下文 加载 bootstrap.yml 配置
        }
    }
}
其余解决方案一:添加节点 保证文件被正常被扫描并成功加载(以 SpringBoot 3.4.1 举例 官方已在依赖中声明 resources 中内容 所以不用单独声明)
<build>
    <finalName>xx-xx-xx</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
            <include>/*.yml</include>
            <include>/*.properties</include>
            <include>/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
            <include>/*.yml</include>
            <include>/*.properties</include>
            <include>/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>
其余方式二(除特殊场景外 其余皆不推荐):在启用类上添加注解声明不需要加载数据源(DataSourceAutoConfiguration)
@MapperScan("xxx.xxx.xxx.mapper")
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class AiGradingBaseApplication {
    public static void main(String[] args) {
        SpringApplication.run(AiGradingBaseApplication.class, args);
    }
}
其余方式三(除特殊场景外 其余皆不推荐):在配置文件中声明不需要加载数据源(DataSourceAutoConfiguration)跟方式二同理 只是操作方式不一致
spring:
 autoconfigure:
 	exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
源码追踪:
前言:因 Springboot 旧版及新版中针对于 bootstrap.yml 文件加载进行重构
官方:
Config First Bootstrap
To use the legacy bootstrap way of connecting to Config Server, bootstrap must be enabled via a property or the spring-cloud-starter-bootstrap starter. The property is spring.cloud.bootstrap.enabled=true. It must be set as a System Property or environment variable. Once bootstrap has been enabled any application with Spring Cloud Config Client on the classpath will connect to Config Server as follows: When a config client starts, it binds to the Config Server (through the spring.cloud.config.uri bootstrap configuration property) and initializes Spring Environment with remote property sources.

The net result of this behavior is that all client applications that want to consume the Config Server need a bootstrap.yml (or an environment variable) with the server address set in spring.cloud.config.uri (it defaults to "http://localhost:8888").
翻译:
配置引导方式(Config First Bootstrap)
要使用传统的引导方式连接到 Config Server 必须通过属性或 spring-cloud-starter-bootstrap 启动器来启用引导 相关的属性是 spring.cloud.bootstrap.enabled=true 该属性必须作为系统属性或环境变量进行设置 一旦启用了引导 任何包含 Spring Cloud Config Client 的应用程序都会按照以下方式连接到 Config Server:
当一个配置客户端启动时 它会绑定到 Config Server(通过 spring.cloud.config.uri 引导配置属性)并使用远程属性源初始化 Spring 环境 
这种行为的最终结果是 所有希望使用 Config Server 的客户端应用程序都需要一个包含服务器地址配置的 bootstrap.yml 文件(或者通过环境变量设置) 该配置项为 spring.cloud.config.uri(默认为 "http://localhost:8888") 
简述:

在 Spring Cloud 的早期版本中 bootstrap.yml 是配置 Spring Cloud Config Client 的关键文件 要启用传统的引导方式连接到 Config Server 必须通过 spring.cloud.bootstrap.enabled=true 属性或通过 spring-cloud-starter-bootstrap 启动器来启用引导

传统引导方式:
  • 配置属性: spring.cloud.bootstrap.enabled=true 必须作为系统属性或环境变量设置
  • 配置文件: 客户端应用程序需要一个 bootstrap.yml 文件 在其中设置 spring.cloud.config.uri 属性(默认值是 http://localhost:8888) 指向 Config Server 的地址
  • 初始化过程: 在应用程序启动时 bootstrap.yml 会被加载 并通过 spring.cloud.config.uri 属性连接到 Config Server 以获取远程配置
相关依赖:
<!-- 旧版 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.12.RELEASE</version>
    <relativePath/>
</parent>
<!-- 新版 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.4.1</version>
    <relativePath/>
</parent>
旧版相关源码:
package org.springframework.cloud.bootstrap;

public class BootstrapApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

    // 定义常量:bootstrap 配置源名称
    public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";

    // 定义常量:默认属性名称
    public static final String DEFAULT_PROPERTIES = "springCloudDefaultProperties";
    
    // 省略部分代码...

    // 构造函数
    public BootstrapApplicationListener() {
    }

    // 监听到 ApplicationEnvironmentPreparedEvent 事件时调用的方法
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        // 获取当前环境对象
        ConfigurableEnvironment environment = event.getEnvironment();
        
        // 新版 - 检查是否启用了 bootstrap 或使用了传统的处理方式
	    // if (PropertyUtils.bootstrapEnabled(environment) || PropertyUtils.useLegacyProcessing(environment)) {
        // 旧版 - 检查是否启用了 bootstrap
        if ((Boolean) environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
        
            // 如果环境中没有包含名为 "bootstrap" 的属性源
            if (!environment.getPropertySources().contains("bootstrap")) {
                ConfigurableApplicationContext context = null;
                // 解析配置的 bootstrap 名称 默认为 "bootstrap"
                String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
                // 获取 Spring 应用的初始化器列表
                Iterator var5 = event.getSpringApplication().getInitializers().iterator();
                // 省略部分代码...
            }
        }
    }
}
新版相关源码:
package org.springframework.cloud.util;

import org.springframework.core.env.Environment;
import org.springframework.util.ClassUtils;

public abstract class PropertyUtils {
    
    // 定义常量:表示是否启用了 bootstrap 配置
    public static final String BOOTSTRAP_ENABLED_PROPERTY = "spring.cloud.bootstrap.enabled";
    
    // 定义常量:用于标记类是否存在
    public static final boolean MARKER_CLASS_EXISTS = ClassUtils.isPresent("org.springframework.cloud.bootstrap.marker.Marker", (ClassLoader)null);

    // 判断是否启用了 bootstrap 配置的方法
    public static boolean bootstrapEnabled(Environment environment) {
        // 获取 "spring.cloud.bootstrap.enabled" 配置的值 默认为 false
        return (Boolean) environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, false) || MARKER_CLASS_EXISTS;
    }

    // 省略部分代码...
}
相关核心代码解释:

在旧版本的 Spring Cloud 中 BootstrapApplicationListener 类会监听 ApplicationEnvironmentPreparedEvent 事件 在环境配置准备好后 检查是否启用了引导(spring.cloud.bootstrap.enabled)并进行初始化

  • 代码检查 spring.cloud.bootstrap.enabled 属性是否为 true 如果是 则进行 bootstrap 上下文的初始化
  • 如果环境中没有找到 "bootstrap" 配置源 则创建该配置源 并进行相应的配置初始化

在新版 Spring Cloud 中 PropertyUtils 类提供了一个新的方法来判断是否启用了 bootstrap 配置

bootstrapEnabled 方法: 检查是否启用了 spring.cloud.bootstrap.enabled 配置 或者 org.springframework.cloud.bootstrap.marker.Marker 类是否存在 这个类的存在标记了某些特定功能或版本的启用

  • spring.cloud.bootstrap.enabled 配置项通常表示是否启用 Spring Cloud Config Client 的引导功能
  • MARKER_CLASS_EXISTS 用来检查类 org.springframework.cloud.bootstrap.marker.Marker 是否存在 通常在新的 Spring Cloud 版本中 这个类的存在表示是否启用某些特定的配置或功能
  • MARKER_CLASS_EXISTS 常量用于检查 Marker 类是否存在 这是新版 Spring Cloud 引导机制的一部分 表示是否启用 bootstrap 配置

这个方法的核心目的是根据配置和类是否存在来判断是否启用 bootstrap 配置源


关键常量

  • MARKER_CLASS_EXISTS 常量用于检查 Marker 类是否存在 这是新版 Spring Cloud 引导机制的一部分 表示是否启用 bootstrap 配置
传统与新版的主要区别:
传统引导方式(旧版):
  1. spring.cloud.bootstrap.enabled:需要显式配置为 true 否则不会启用 bootstrap 配置
  2. bootstrap.yml:需要手动配置 spring.cloud.config.uri 和其他属性
  3. 代码流程:BootstrapApplicationListener 直接在事件监听时检查是否启用 bootstrap 并根据条件进行初始化
新版引导方式:
  1. bootstrapEnabled 方法:在新版 Spring Cloud 中 PropertyUtils.bootstrapEnabled 方法提供了一种更加灵活的方式来检查是否启用了 bootstrap 不仅仅通过 spring.cloud.bootstrap.enabled 属性 还通过检查 Marker 类的存在来进行判断
  2. Marker 类:新版加入了 org.springframework.cloud.bootstrap.marker.Marker 类 作为配置的标记类来表示是否启用某些功能
  3. 灵活性提高:新版代码通过 PropertyUtils 类提供的逻辑来判断是否启用引导功能 允许更灵活的配置和判断

总结:
  • 旧版 Spring Cloud: bootstrap.yml 是必需的配置文件 spring.cloud.bootstrap.enabled 用于控制是否启用引导配置 启用后 Spring Cloud Config Client 会自动连接到 Config Server
  • 新版 Spring Cloud: 引入了通过检查 Marker 类的存在来动态启用 bootstrap 配置的机制 使得配置加载更加灵活 并支持更多的使用场景 通过检查 spring.cloud.bootstrap.enabledMarker 类的存在 Spring Cloud 可以决定是否加载 bootstrap.yml 文件
  • PropertyUtils 类: 提供了简化的方法来判断是否启用 bootstrap 配置 它通过判断 spring.cloud.bootstrap.enabled 属性的值以及 Marker 类的存在来决定是否启用 bootstrap 配置 从而使新版代码更加简洁、灵活和可扩展
  • 加载 bootstrap.yml 文件: 在新版 Spring Cloud 中 加载 bootstrap.yml 文件依赖于 spring.cloud.bootstrap.enabled 的配置值和 Marker 类的存在 当启用该属性时 Spring Cloud 会自动加载 bootstrap.yml 文件 无需手动指定文件路径 配置可以通过 ConfigurableEnvironmentApplicationContext 来访问 如果需要更复杂的控制 开发者可以通过自定义 ApplicationContextInitializerApplicationListener 来实现定制化的配置加载过程

这种引导方式的演变表明 Spring Cloud 在不断优化和简化配置的同时 也保持了对旧版配置的兼容性

Spring Boot应用通常通过`application.properties`或`application.yml`配置文件来管理环境变量和应用设置,其中`.yml`文件用于YAML格式的配置,它更便于阅读和编写,尤其是在包含嵌套结构的复杂配置时。 要在Spring Boot项目中读取`bootstrap.yml`文件,你需要遵循以下步骤: 1. **创建配置文件**:首先,在项目的`src/main/resources`目录下,创建一个名为`bootstrap.yml`的文件,并添加你需要的配置项。 ```yaml server: port: ${PORT:-8080} # 默认端口 logging: level: org.springframework.boot: DEBUG ``` 这里的`${PORT:-8080}`表示如果环境变量`PORT`存在,则使用其值;否则,默认为8080。 2. **声明配置源**:在`application.yml`或者其他Spring Boot默认查找的配置文件(如`application.properties`)中,指定加载`bootstrap.yml`: ```yaml spring.profiles.active: dev spring.config.location: classpath:/config/,classpath:/bootstrap.yml ``` 这行配置告诉Spring Boot同时加载`config`目录下的所有配置(默认情况),以及`bootstrap.yml`文件。 3. **访问配置**:在Spring Boot的配置类中(通常是`ConfigProperties`接口的实现类),你可以使用@Autowired注解从@Configuration类或@Value注解直接获取配置信息。 ```java @Configuration public class AppConfig { @Value("${server.port}") private int serverPort; @Bean public MyService myService() { return new MyServiceImpl(serverPort); } } ``` 4. **运行应用**:启动Spring Boot应用,配置会根据环境自动加载并生效。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值