Spring Boot基础-3:Spring Boot 4.x 配置文件全攻略与多环境切换

0 阅读13分钟

📌 系列说明:本文是《Spring Boot从入门到底层原理》20篇系列的第三篇

  • 前置知识:已完成前两篇《Spring Boot 101》和《Starter深度拆解》
  • 核心目标:彻底掌握Spring Boot 4.x配置文件的所有用法,包括多环境切换、配置加密等
  • 版本基准Spring Boot 4.0.x + JDK 21+
  • 预计耗时:阅读40分钟 + 实操90分钟

一、为什么配置文件如此重要?

1.1 从痛点说起:硬编码的灾难

在没有配置文件的时代,应用参数是这样写的:

// ❌ 硬编码方式(绝对不要这样做!)
public class DatabaseConfig {
    private String url = "jdbc:mysql://localhost:3306/mydb";
    private String username = "root";
    private String password = "123456";
    private int port = 8080;
}

问题

  • 🔴 环境切换困难:开发、测试、生产环境需要修改代码重新编译
  • 🔴 安全风险:数据库密码等敏感信息直接暴露在代码中
  • 🔴 维护成本高:每次修改配置都需要重新部署

1.2 Spring Boot 4.x的解决方案:外部化配置

Spring Boot 4.x在3.x的基础上进一步优化了配置体验:

// ✅ Spring Boot 4.x 配置分离方式
@RestController
public class ConfigController {
    
    @Value("${app.name}")
    private String appName;
    
    @Autowired
    private AppProperties appProperties;
}

Spring Boot 4.x 配置新特性

  • 模块化自动配置:47个轻量模块,按需加载
  • JSpecify空安全集成:编译期空指针检查
  • 增强的Profile支持:更灵活的多环境管理
  • 配置绑定性能提升:AOT优化,启动更快

二、配置文件基础:properties vs yml

2.1 两种配置文件格式对比

Spring Boot 4.x继续支持两种配置文件格式:

特性application.propertiesapplication.yml
语法Key-Value键值对YAML层级结构
可读性一般优秀(层级清晰)
空格敏感是(缩进必须一致)
注释符号##
官方推荐支持推荐
Spring Boot 4.x增强-支持YAML锚点引用

2.2 实战:创建完整的多模块配置项目

步骤1:创建Maven父项目

在IDEA中:

File → New → Project → Maven → Next

填写项目信息:

字段
Namespring-boot-config-demo
Location选择你的工作目录
GroupIdcom.example
ArtifactIdspring-boot-config-demo
Version1.0.0
Packagingpom

步骤2:配置父项目 pom.xml

<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>spring-boot-config-demo</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    <name>Spring Boot Config Demo Parent</name>
    <description>Spring Boot 4.x配置文件演示项目(父项目)</description>

    <modules>
        <module>config-app</module>
    </modules>

    <properties>
        <java.version>21</java.version>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>4.0.0</spring-boot.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.13.0</version>
                    <configuration>
                        <source>21</source>
                        <target>21</target>
                        <encoding>UTF-8</encoding>
                        <compilerArgs>
                            <arg>-parameters</arg>
                        </compilerArgs>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

⚠️ Spring Boot 4.x 关键变化

  1. Java版本:最低要求 Java 17,推荐 Java 21+
  2. Maven插件版本:spring-boot-maven-plugin 需与Spring Boot版本一致
  3. 编译器配置:建议添加 -parameters 参数支持方法名反射

步骤3:创建子模块

右键父项目 → NewModule → Maven → Next

填写模块信息:

字段
Nameconfig-app
GroupIdcom.example
ArtifactIdconfig-app
Version1.0.0
Packagingjar

步骤4:配置子模块 pom.xml

<?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>com.example</groupId>
        <artifactId>spring-boot-config-demo</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>config-app</artifactId>
    <packaging>jar</packaging>
    <name>Config App</name>
    <description>Spring Boot 4.x配置文件演示应用</description>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Actuator(查看配置信息) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- Spring Boot Configuration Processor(配置元数据) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Lombok(简化代码) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- JSpecify空安全注解(Spring Boot 4.x新特性) -->
        <dependency>
            <groupId>org.jspecify</groupId>
            <artifactId>jspecify</artifactId>
            <version>1.0.0</version>
            <optional>true</optional>
        </dependency>

        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

💡 Spring Boot 4.x 新增依赖

  • jspecify:空安全注解,编译期检查空指针

2.3 application.properties 完整示例

路径config-app/src/main/resources/application.properties

# ===========================================
# 应用基础配置
# ===========================================
spring.application.name=config-app

# ===========================================
# 服务器配置
# ===========================================
server.port=8080
server.servlet.context-path=/api

# ===========================================
# 数据库配置
# ===========================================
spring.datasource.url=jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# ===========================================
# 自定义配置
# ===========================================
app.name=我的应用
app.version=1.0.0
app.description=Spring Boot 4.x配置文件演示应用
app.owner=开发团队
app.contact.email=support@example.com

# ===========================================
# 日志配置
# ===========================================
logging.level.root=INFO
logging.level.com.example=DEBUG
logging.file.name=logs/app.log
logging.file.max-size=10MB
logging.file.max-history=30

# ===========================================
# Actuator 监控配置
# ===========================================
management.endpoints.web.exposure.include=health,info,configprops,env
management.endpoint.health.show-details=always

2.4 application.yml 完整示例

路径config-app/src/main/resources/application.yml

# ===========================================
# 应用基础配置
# ===========================================
spring:
  application:
    name: config-app

# ===========================================
# 服务器配置
# ===========================================
server:
  port: 8080
  servlet:
    context-path: /api

# ===========================================
# 数据库配置
# ===========================================
  datasource:
    url: jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

# ===========================================
# 自定义配置
# ===========================================
app:
  name: 我的应用
  version: 1.0.0
  description: Spring Boot 4.x配置文件演示应用
  owner: 开发团队
  contact:
    email: support@example.com
    phone: 400-888-8888

# ===========================================
# 日志配置
# ===========================================
logging:
  level:
    root: INFO
    com.example: DEBUG
  file:
    name: logs/app.log
    max-size: 10MB
    max-history: 30

# ===========================================
# Actuator 监控配置
# ===========================================
management:
  endpoints:
    web:
      exposure:
        include: health,info,configprops,env
  endpoint:
    health:
      show-details: always

2.5 Spring Boot 4.x YAML新特性:锚点引用

Spring Boot 4.x 增强了对YAML锚点的支持,可以复用配置:

# ===========================================
# 使用YAML锚点复用配置
# ===========================================
defaults: &defaults
  timeout: 30000
  retry: 3
  enabled: true

app:
  service-a:
    <<: *defaults
    name: 服务A
    timeout: 60000  # 可覆盖默认值
  
  service-b:
    <<: *defaults
    name: 服务B
    # 继承所有defaults配置

三、配置文件读取的三种方式

3.1 方式一:@Value 注解(简单值注入)

适用场景:读取单个配置项

步骤1:创建测试Controller

路径:config-app/src/main/java/com/example/config/controller/ConfigController.java

package com.example.config.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 配置文件读取演示 - @Value方式
 * Spring Boot 4.x 支持更灵活的SpEL表达式
 */
@RestController
public class ConfigController {

    /**
     * 读取简单配置项
     * 语法:${配置键}
     */
    @Value("${app.name}")
    private String appName;

    @Value("${app.version}")
    private String appVersion;

    @Value("${app.description:默认描述}")
    private String appDescription;

    @Value("${app.unknown:默认值}")
    private String unknownConfig;

    @Value("${server.port}")
    private Integer serverPort;

    /**
     * 读取列表配置
     */
    @Value("${app.features:}")
    private String[] features;

    /**
     * 测试@Value读取配置
     * GET /api/config/value
     */
    @GetMapping("/config/value")
    public Map<String, Object> getConfigByValue() {
        Map<String, Object> result = new HashMap<>();
        result.put("appName", appName);
        result.put("appVersion", appVersion);
        result.put("appDescription", appDescription);
        result.put("unknownConfig", unknownConfig);
        result.put("serverPort", serverPort);
        result.put("features", features);
        return result;
    }

    /**
     * 读取SpEL表达式
     * Spring Boot 4.x 增强SpEL支持
     */
    @Value("#{T(java.lang.Math).random() * 100}")
    private Double randomValue;

    @Value("#{systemProperties['user.name']}")
    private String systemUser;

    @GetMapping("/config/random")
    public Map<String, Object> getRandomValue() {
        Map<String, Object> result = new HashMap<>();
        result.put("randomValue", randomValue);
        result.put("systemUser", systemUser);
        return result;
    }
}

步骤2:添加列表配置到application.yml

app:
  features:
    - 用户管理
    - 订单管理
    - 支付管理
    - 报表统计

步骤3:测试接口

# 测试@Value读取
curl http://localhost:8080/api/config/value

# 测试随机值
curl http://localhost:8080/api/config/random

预期响应

{
  "appName": "我的应用",
  "appVersion": "1.0.0",
  "appDescription": "Spring Boot 4.x配置文件演示应用",
  "unknownConfig": "默认值",
  "serverPort": 8080,
  "features": ["用户管理", "订单管理", "支付管理", "报表统计"]
}

3.2 方式二:@ConfigurationProperties(类型安全配置)

适用场景:读取一组相关配置,Spring Boot 4.x 推荐方式

步骤1:创建配置属性类

路径:config-app/src/main/java/com/example/config/properties/AppProperties.java

package com.example.config.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * 应用配置属性类
 * 
 * Spring Boot 4.x 增强:
 * 1. 更好的IDE提示支持
 * 2. 编译期空安全检查(配合JSpecify)
 * 3. 配置绑定性能提升(AOT优化)
 */
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {

    /**
     * 应用名称
     */
    private String name;

    /**
     * 应用版本
     */
    private String version;

    /**
     * 应用描述
     */
    private String description;

    /**
     * 应用所有者
     */
    private String owner;

    /**
     * 联系信息
     */
    private Contact contact = new Contact();

    /**
     * 功能列表
     */
    private List<String> features = new ArrayList<>();

    /**
     * 数据库配置
     */
    private Database database = new Database();

    // ========== 内部类:联系信息 ==========

    public static class Contact {
        private String email;
        private String phone;

        public String getEmail() { return email; }
        public void setEmail(String email) { this.email = email; }

        public String getPhone() { return phone; }
        public void setPhone(String phone) { this.phone = phone; }
    }

    // ========== 内部类:数据库配置 ==========

    public static class Database {
        private String host;
        private Integer port;
        private String name;

        public String getHost() { return host; }
        public void setHost(String host) { this.host = host; }

        public Integer getPort() { return port; }
        public void setPort(Integer port) { this.port = port; }

        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    }

    // ========== Getter 和 Setter ==========

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public String getVersion() { return version; }
    public void setVersion(String version) { this.version = version; }

    public String getDescription() { return description; }
    public void setDescription(String description) { this.description = description; }

    public String getOwner() { return owner; }
    public void setOwner(String owner) { this.owner = owner; }

    public Contact getContact() { return contact; }
    public void setContact(Contact contact) { this.contact = contact; }

    public List<String> getFeatures() { return features; }
    public void setFeatures(List<String> features) { this.features = features; }

    public Database getDatabase() { return database; }
    public void setDatabase(Database database) { this.database = database; }

    @Override
    public String toString() {
        return "AppProperties{" +
                "name='" + name + '\'' +
                ", version='" + version + '\'' +
                ", description='" + description + '\'' +
                ", owner='" + owner + '\'' +
                ", contact=" + contact +
                ", features=" + features +
                ", database=" + database +
                '}';
    }
}

步骤2:添加数据库配置到application.yml

app:
  database:
    host: localhost
    port: 3306
    name: demo_db

步骤3:创建使用配置属性的Controller

路径:config-app/src/main/java/com/example/config/controller/AppPropertiesController.java

package com.example.config.controller;

import com.example.config.properties.AppProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 使用@ConfigurationProperties读取配置
 */
@RestController
public class AppPropertiesController {

    @Autowired
    private AppProperties appProperties;

    /**
     * 获取所有应用配置
     * GET /api/config/properties
     */
    @GetMapping("/config/properties")
    public Map<String, Object> getAppProperties() {
        Map<String, Object> result = new HashMap<>();
        result.put("name", appProperties.getName());
        result.put("version", appProperties.getVersion());
        result.put("description", appProperties.getDescription());
        result.put("owner", appProperties.getOwner());
        result.put("contact", appProperties.getContact());
        result.put("features", appProperties.getFeatures());
        result.put("database", appProperties.getDatabase());
        return result;
    }

    /**
     * 获取联系信息
     * GET /api/config/contact
     */
    @GetMapping("/config/contact")
    public Map<String, String> getContact() {
        Map<String, String> result = new HashMap<>();
        result.put("email", appProperties.getContact().getEmail());
        result.put("phone", appProperties.getContact().getPhone());
        return result;
    }

    /**
     * 获取数据库配置
     * GET /api/config/database
     */
    @GetMapping("/config/database")
    public Map<String, Object> getDatabase() {
        Map<String, Object> result = new HashMap<>();
        result.put("host", appProperties.getDatabase().getHost());
        result.put("port", appProperties.getDatabase().getPort());
        result.put("name", appProperties.getDatabase().getName());
        return result;
    }
}

步骤4:启用配置属性绑定(Spring Boot 4.x)

路径:config-app/src/main/java/com/example/config/ConfigApplication.java

package com.example.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import com.example.config.properties.AppProperties;

/**
 * 应用启动类
 * 
 * Spring Boot 4.x 变化:
 * 1. 配置类如有@Component,可省略@EnableConfigurationProperties
 * 2. 但显式声明更清晰,推荐保留
 */
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class ConfigApplication {

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

步骤5:测试接口

# 获取所有配置
curl http://localhost:8080/api/config/properties

# 获取联系信息
curl http://localhost:8080/api/config/contact

# 获取数据库配置
curl http://localhost:8080/api/config/database

预期响应

{
  "name": "我的应用",
  "version": "1.0.0",
  "description": "Spring Boot 4.x配置文件演示应用",
  "owner": "开发团队",
  "contact": {
    "email": "support@example.com",
    "phone": "400-888-8888"
  },
  "features": ["用户管理", "订单管理", "支付管理", "报表统计"],
  "database": {
    "host": "localhost",
    "port": 3306,
    "name": "demo_db"
  }
}

3.3 方式三:Environment 接口(编程式读取)

适用场景:动态读取配置、需要更灵活的场景

步骤1:创建Environment测试Controller

路径:config-app/src/main/java/com/example/config/controller/EnvironmentController.java

package com.example.config.controller;

import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;

/**
 * 使用Environment接口读取配置
 */
@RestController
public class EnvironmentController {

    @Autowired
    private Environment environment;

    /**
     * 通过Environment读取配置
     * GET /api/config/env?key=app.name
     */
    @GetMapping("/config/env")
    public Map<String, Object> getConfigByKey(@RequestParam String key) {
        Map<String, Object> result = new HashMap<>();
        result.put("key", key);
        result.put("value", environment.getProperty(key));
        result.put("exists", environment.containsProperty(key));
        return result;
    }

    /**
     * 读取配置(带默认值)
     * GET /api/config/env-with-default?key=app.unknown&default=默认值
     */
    @GetMapping("/config/env-with-default")
    public Map<String, Object> getConfigWithDefault(
            @RequestParam String key,
            @RequestParam(defaultValue = "默认值") String defaultVal) {
        Map<String, Object> result = new HashMap<>();
        result.put("key", key);
        result.put("value", environment.getProperty(key, defaultVal));
        return result;
    }

    /**
     * 获取所有属性源
     * GET /api/config/property-sources
     */
    @GetMapping("/config/property-sources")
    public Map<String, Object> getPropertySources() {
        Map<String, Object> result = new HashMap<>();
        String[] sources = Arrays.stream(environment.getPropertySources())
                .map(source -> source.getName())
                .toArray(String[]::new);
        result.put("propertySources", sources);
        result.put("count", sources.length);
        return result;
    }

    /**
     * 获取活跃的环境Profile
     * GET /api/config/active-profiles
     */
    @GetMapping("/config/active-profiles")
    public Map<String, Object> getActiveProfiles() {
        Map<String, Object> result = new HashMap<>();
        result.put("activeProfiles", Arrays.asList(environment.getActiveProfiles()));
        result.put("defaultProfiles", Arrays.asList(environment.getDefaultProfiles()));
        return result;
    }
}

步骤2:测试接口

# 读取指定配置
curl "http://localhost:8080/api/config/env?key=app.name"

# 读取配置(带默认值)
curl "http://localhost:8080/api/config/env-with-default?key=app.unknown&default=默认值"

# 获取所有属性源
curl http://localhost:8080/api/config/property-sources

# 获取活跃Profile
curl http://localhost:8080/api/config/active-profiles

3.4 三种方式对比总结

特性@Value@ConfigurationPropertiesEnvironment
类型安全❌ 否✅ 是❌ 否
IDE提示❌ 有限✅ 完整❌ 无
松散绑定❌ 不支持✅ 支持❌ 不支持
复杂对象❌ 困难✅ 优秀❌ 困难
JSR380校验❌ 不支持✅ 支持❌ 不支持
Spring Boot 4.x优化-AOT性能提升-
推荐使用简单值复杂配置动态场景

💡 最佳实践

  • 简单配置项 → 使用 @Value
  • 一组相关配置 → 使用 @ConfigurationPropertiesSpring Boot 4.x 强烈推荐
  • 动态/编程式读取 → 使用 Environment

四、配置文件优先级(11层详解)

4.1 为什么需要优先级?

Spring Boot允许配置在多个位置,当同一配置项在多处定义时,优先级高的会覆盖优先级低的

4.2 完整优先级列表(从高到低)

┌─────────────────────────────────────────────────────────────────┐
│  优先级 1:命令行参数                                            │
│  示例:java -jar app.jar --server.port=9000                     │
├─────────────────────────────────────────────────────────────────┤
│  优先级 2:SPRING_APPLICATION_JSON 环境变量                      │
│  示例:SPRING_APPLICATION_JSON='{"server.port":9000}'           │
├─────────────────────────────────────────────────────────────────┤
│  优先级 3:ServletConfig 初始化参数                               │
├─────────────────────────────────────────────────────────────────┤
│  优先级 4:ServletContext 初始化参数                              │
├─────────────────────────────────────────────────────────────────┤
│  优先级 5:JNDI 属性(java:comp/env)                            │
├─────────────────────────────────────────────────────────────────┤
│  优先级 6:Java 系统属性(-D)                                   │
│  示例:java -Dserver.port=9000 -jar app.jar                     │
├─────────────────────────────────────────────────────────────────┤
│  优先级 7:操作系统环境变量                                      │
│  示例:export SERVER_PORT=9000                                  │
├─────────────────────────────────────────────────────────────────┤
│  优先级 8:随机值(random.*)                                    │
├─────────────────────────────────────────────────────────────────┤
│  优先级 9:JAR包外部的 application-{profile}.properties/yml      │
│  示例:./config/application-prod.yml                            │
├─────────────────────────────────────────────────────────────────┤
│  优先级 10:JAR包外部的 application.properties/yml               │
│  示例:./application.properties                                 │
├─────────────────────────────────────────────────────────────────┤
│  优先级 11:JAR包内部的 application-{profile}.properties/yml     │
│  示例:classpath:application-prod.yml                           │
├─────────────────────────────────────────────────────────────────┤
│  优先级 12:JAR包内部的 application.properties/yml               │
│  示例:classpath:application.yml                                │
└─────────────────────────────────────────────────────────────────┘

4.3 实战:验证配置文件优先级

步骤1:创建测试Controller

路径:config-app/src/main/java/com/example/config/controller/PriorityController.java

package com.example.config.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 配置文件优先级测试
 */
@RestController
public class PriorityController {

    @Value("${test.priority:未设置}")
    private String testPriority;

    @Autowired
    private Environment environment;

    /**
     * 查看当前生效的配置
     * GET /api/priority/test
     */
    @GetMapping("/priority/test")
    public Map<String, Object> testPriority() {
        Map<String, Object> result = new HashMap<>();
        result.put("testPriority", testPriority);
        result.put("serverPort", environment.getProperty("server.port"));
        result.put("activeProfiles", environment.getActiveProfiles());
        return result;
    }
}

步骤2:在application.yml中添加测试配置

test:
  priority: 来自classpath/application.yml(优先级最低)

步骤3:测试不同优先级的配置

# 1. 默认启动(读取classpath配置)
curl http://localhost:8080/api/priority/test
# 响应:testPriority = "来自classpath/application.yml(优先级最低)"

# 2. 使用命令行参数(优先级最高)
java -jar config-app.jar --test.priority=来自命令行参数
curl http://localhost:8080/api/priority/test
# 响应:testPriority = "来自命令行参数"

# 3. 使用系统属性
java -Dtest.priority=来自系统属性 -jar config-app.jar
curl http://localhost:8080/api/priority/test
# 响应:testPriority = "来自系统属性"

# 4. 使用环境变量
export TEST_PRIORITY=来自环境变量
java -jar config-app.jar
curl http://localhost:8080/api/priority/test
# 响应:testPriority = "来自环境变量"

4.4 外部化配置目录结构

Spring Boot 4.x 继续支持以下目录加载外部配置文件(优先级从高到低):

应用启动目录/
├── config/                          # 优先级最高
│   ├── application.yml
│   └── application-prod.yml
│
├── config/optional/                 # 可选配置目录
│   └── application.yml
│
├── ./                               # 当前目录
│   ├── application.yml
│   └── application-prod.yml
│
└── classpath:/                      # JAR包内部(优先级最低)
    ├── application.yml
    └── application-prod.yml

💡 生产部署建议:将配置文件放在JAR包外部的config/目录,修改配置无需重新打包。


五、多环境配置(Profile)

5.1 为什么需要多环境?

实际项目中,不同环境需要不同配置:

配置项开发环境(dev)测试环境(test)生产环境(prod)
数据库URLlocalhosttest-dbprod-db
日志级别DEBUGINFOWARN
缓存开关关闭开启开启
API密钥测试密钥测试密钥生产密钥

5.2 创建多环境配置文件

步骤1:创建开发环境配置

路径:config-app/src/main/resources/application-dev.yml

# ===========================================
# 开发环境配置
# ===========================================
spring:
  config:
    activate:
      on-profile: dev

# 服务器配置
server:
  port: 8080

# 数据库配置
  datasource:
    url: jdbc:mysql://localhost:3306/dev_db?useSSL=false
    username: dev_user
    password: dev_password

# 日志配置
logging:
  level:
    root: DEBUG
    com.example: DEBUG
  file:
    name: logs/dev-app.log

# 自定义配置
app:
  name: 我的应用-开发环境
  debug: true
  cache:
    enabled: false

# 第三方服务(使用Mock)
external:
  api:
    url: http://localhost:9999/mock
    timeout: 5000

步骤2:创建测试环境配置

路径:config-app/src/main/resources/application-test.yml

# ===========================================
# 测试环境配置
# ===========================================
spring:
  config:
    activate:
      on-profile: test

# 服务器配置
server:
  port: 8081

# 数据库配置
  datasource:
    url: jdbc:mysql://test-db-server:3306/test_db?useSSL=false
    username: test_user
    password: test_password

# 日志配置
logging:
  level:
    root: INFO
    com.example: INFO
  file:
    name: logs/test-app.log

# 自定义配置
app:
  name: 我的应用-测试环境
  debug: false
  cache:
    enabled: true

# 第三方服务(测试环境)
external:
  api:
    url: https://test-api.example.com
    timeout: 10000

步骤3:创建生产环境配置

路径:config-app/src/main/resources/application-prod.yml

# ===========================================
# 生产环境配置
# ===========================================
spring:
  config:
    activate:
      on-profile: prod

# 服务器配置
server:
  port: 80

# 数据库配置
  datasource:
    url: jdbc:mysql://prod-db-cluster:3306/prod_db?useSSL=true
    username: ${DB_USERNAME}  # 从环境变量读取
    password: ${DB_PASSWORD}  # 从环境变量读取

# 日志配置
logging:
  level:
    root: WARN
    com.example: INFO
  file:
    name: /var/logs/app.log
    max-size: 100MB
    max-history: 90

# 自定义配置
app:
  name: 我的应用
  debug: false
  cache:
    enabled: true
    ttl: 3600

# 第三方服务(生产环境)
external:
  api:
    url: https://api.example.com
    timeout: 30000
    api-key: ${API_KEY}  # 从环境变量读取

步骤4:配置主文件激活的Profile

路径:config-app/src/main/resources/application.yml

# ===========================================
# 主配置文件(公共配置)
# ===========================================
spring:
  application:
    name: config-app
  # 默认激活的Profile
  profiles:
    active: dev

# 服务器公共配置
server:
  servlet:
    context-path: /api

# 公共自定义配置
app:
  version: 1.0.0
  description: Spring Boot 4.x配置文件演示应用
  owner: 开发团队
  contact:
    email: support@example.com
    phone: 400-888-8888

# Actuator 监控配置
management:
  endpoints:
    web:
      exposure:
        include: health,info,configprops,env
  endpoint:
    health:
      show-details: always

5.3 激活Profile的四种方式

方式一:在application.yml中指定(开发方便)

spring:
  profiles:
    active: dev

方式二:命令行参数(部署灵活)

java -jar config-app.jar --spring.profiles.active=prod

方式三:环境变量(容器化推荐)

export SPRING_PROFILES_ACTIVE=prod
java -jar config-app.jar

方式四:IDEA运行配置(本地开发)

Run → Edit Configurations → Active profiles → 输入 dev/test/prod

5.4 创建Profile测试Controller

步骤1:创建Profile测试Controller

路径:config-app/src/main/java/com/example/config/controller/ProfileController.java

package com.example.config.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;

/**
 * Profile环境测试
 */
@RestController
public class ProfileController {

    @Value("${app.name:未知}")
    private String appName;

    @Value("${app.debug:false}")
    private boolean debug;

    @Value("${server.port:8080}")
    private Integer serverPort;

    @Autowired
    private Environment environment;

    /**
     * 查看当前环境信息
     * GET /api/profile/info
     */
    @GetMapping("/profile/info")
    public Map<String, Object> getProfileInfo() {
        Map<String, Object> result = new HashMap<>();
        result.put("appName", appName);
        result.put("debug", debug);
        result.put("serverPort", serverPort);
        result.put("activeProfiles", Arrays.asList(environment.getActiveProfiles()));
        result.put("defaultProfiles", Arrays.asList(environment.getDefaultProfiles()));
        return result;
    }

    /**
     * 查看数据库配置(不同环境不同)
     * GET /api/profile/database
     */
    @GetMapping("/profile/database")
    public Map<String, Object> getDatabaseConfig() {
        Map<String, Object> result = new HashMap<>();
        result.put("url", environment.getProperty("spring.datasource.url"));
        result.put("username", environment.getProperty("spring.datasource.username"));
        return result;
    }

    /**
     * 查看外部API配置
     * GET /api/profile/external-api
     */
    @GetMapping("/profile/external-api")
    public Map<String, Object> getExternalApiConfig() {
        Map<String, Object> result = new HashMap<>();
        result.put("url", environment.getProperty("external.api.url"));
        result.put("timeout", environment.getProperty("external.api.timeout"));
        return result;
    }
}

步骤2:测试不同环境

# 启动开发环境
java -jar config-app.jar --spring.profiles.active=dev
curl http://localhost:8080/api/profile/info
# 响应:activeProfiles = ["dev"], serverPort = 8080

# 启动测试环境
java -jar config-app.jar --spring.profiles.active=test
curl http://localhost:8081/api/profile/info
# 响应:activeProfiles = ["test"], serverPort = 8081

# 启动生产环境
java -jar config-app.jar --spring.profiles.active=prod
curl http://localhost:80/api/profile/info
# 响应:activeProfiles = ["prod"], serverPort = 80

5.5 多Profile同时激活

Spring Boot 4.x支持同时激活多个Profile:

# 同时激活prod和monitoring profile
java -jar config-app.jar --spring.profiles.active=prod,monitoring

# 或使用逗号分隔
java -jar config-app.jar --spring.profiles.active=prod,monitoring,security

创建监控Profile

路径:config-app/src/main/resources/application-monitoring.yml

spring:
  config:
    activate:
      on-profile: monitoring

# 监控特定配置
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    metrics:
      enabled: true
    prometheus:
      enabled: true

六、配置加密与安全存储

6.1 为什么需要配置加密?

生产环境中,数据库密码、API密钥等敏感信息不能明文存储:

# ❌ 不安全:明文密码
spring:
  datasource:
    password: 123456

# ✅ 安全:加密密码
spring:
  datasource:
    password: ENC(加密后的密文)

6.2 使用Jasypt进行配置加密

步骤1:添加Jasypt依赖

config-app/pom.xml中添加:

<!-- Jasypt加密依赖 -->
<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>

步骤2:生成加密密码

创建加密工具类:

路径:config-app/src/test/java/com/example/config/JasyptTest.java

package com.example.config;

import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.junit.jupiter.api.Test;

/**
 * Jasypt加密测试工具
 */
public class JasyptTest {

    @Test
    public void testEncrypt() {
        // 创建加密器
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        
        // 设置密钥(生产环境应从环境变量读取)
        config.setPassword("my-secret-key");
        config.setAlgorithm("PBEWithMD5AndDES");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator");
        config.setStringOutputType("base64");
        
        encryptor.setConfig(config);

        // 加密密码
        String plainPassword = "123456";
        String encryptedPassword = encryptor.encrypt(plainPassword);
        
        System.out.println("原始密码: " + plainPassword);
        System.out.println("加密后: ENC(" + encryptedPassword + ")");
        
        // 验证解密
        String decryptedPassword = encryptor.decrypt(encryptedPassword);
        System.out.println("解密后: " + decryptedPassword);
    }
}

步骤3:运行测试生成加密值

mvn test -Dtest=JasyptTest
# 建议可以直接再IDEA 点左边的箭头来跑,不需要使用命令



输出示例

原始密码:123456
加密后:ENC(xK8j3mN9pL2qR5tY)
解密后:123456

步骤4:在配置文件中使用加密值

路径:config-app/src/main/resources/application-prod.yml

spring:
  datasource:
    url: jdbc:mysql://prod-db:3306/prod_db
    username: prod_user
    password: ENC(xK8j3mN9pL2qR5tY)  # 加密后的密码

步骤5:配置解密密钥

方式一:在application.yml中配置(不推荐生产环境)

jasypt:
  encryptor:
    password: my-secret-key

方式二:命令行参数(推荐)

java -jar config-app.jar --jasypt.encryptor.password=my-secret-key

方式三:环境变量(最安全,推荐)

export JASYPT_ENCRYPTOR_PASSWORD=my-secret-key
java -jar config-app.jar

6.3 创建加密配置测试Controller

路径:config-app/src/main/java/com/example/config/controller/EncryptController.java

package com.example.config.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 加密配置测试
 */
@RestController
public class EncryptController {

    @Value("${spring.datasource.password:未配置}")
    private String dbPassword;

    /**
     * 查看数据库密码(生产环境应脱敏)
     * GET /api/encrypt/db-password
     */
    @GetMapping("/encrypt/db-password")
    public Map<String, Object> getDbPassword() {
        Map<String, Object> result = new HashMap<>();
        // 生产环境不应返回完整密码
        if (dbPassword != null && dbPassword.length() > 4) {
            result.put("password", dbPassword.substring(0, 2) + "****");
        } else {
            result.put("password", "已配置");
        }
        result.put("configured", dbPassword != null && !dbPassword.contains("未配置"));
        return result;
    }
}

七、配置校验(JSR380)

7.1 使用@Validated校验配置

步骤1:添加校验依赖

config-app/pom.xml中添加:

<!-- 参数校验依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

步骤2:创建带校验的配置类

路径:config-app/src/main/java/com/example/config/properties/ValidatedProperties.java

package com.example.config.properties;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Max;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

/**
 * 带校验的配置属性类
 * Spring Boot 4.x 基于 Jakarta EE 11
 */
@Component
@ConfigurationProperties(prefix = "app.security")
@Validated
public class ValidatedProperties {

    /**
     * 管理员邮箱(必须填写,且是有效邮箱)
     */
    @NotBlank(message = "管理员邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String adminEmail;

    /**
     * 会话超时时间(分钟)
     */
    @NotNull(message = "会话超时时间不能为空")
    @Min(value = 5, message = "会话超时时间最小5分钟")
    @Max(value = 1440, message = "会话超时时间最大1440分钟")
    private Integer sessionTimeout;

    /**
     * 最大登录尝试次数
     */
    @NotNull(message = "最大登录尝试次数不能为空")
    @Min(value = 1, message = "最大登录尝试次数最小1次")
    @Max(value = 10, message = "最大登录尝试次数最大10次")
    private Integer maxLoginAttempts;

    // ========== Getter 和 Setter ==========

    public String getAdminEmail() { return adminEmail; }
    public void setAdminEmail(String adminEmail) { this.adminEmail = adminEmail; }

    public Integer getSessionTimeout() { return sessionTimeout; }
    public void setSessionTimeout(Integer sessionTimeout) { this.sessionTimeout = sessionTimeout; }

    public Integer getMaxLoginAttempts() { return maxLoginAttempts; }
    public void setMaxLoginAttempts(Integer maxLoginAttempts) { this.maxLoginAttempts = maxLoginAttempts; }

    @Override
    public String toString() {
        return "ValidatedProperties{" +
                "adminEmail='" + adminEmail + '\'' +
                ", sessionTimeout=" + sessionTimeout +
                ", maxLoginAttempts=" + maxLoginAttempts +
                '}';
    }
}

步骤3:在application.yml中添加配置

app:
  security:
    admin-email: admin@example.com
    session-timeout: 30
    max-login-attempts: 5

步骤4:创建校验测试Controller

路径:config-app/src/main/java/com/example/config/controller/ValidationController.java

package com.example.config.controller;

import com.example.config.properties.ValidatedProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 配置校验测试
 */
@RestController
public class ValidationController {

    @Autowired
    private ValidatedProperties validatedProperties;

    /**
     * 获取安全配置
     * GET /api/validation/security
     */
    @GetMapping("/validation/security")
    public Map<String, Object> getSecurityConfig() {
        Map<String, Object> result = new HashMap<>();
        result.put("adminEmail", validatedProperties.getAdminEmail());
        result.put("sessionTimeout", validatedProperties.getSessionTimeout());
        result.put("maxLoginAttempts", validatedProperties.getMaxLoginAttempts());
        return result;
    }
}

步骤5:测试校验效果

修改配置为无效值:

app:
  security:
    admin-email: invalid-email  # 无效邮箱格式
    session-timeout: 2          # 小于最小值5
    max-login-attempts: 15      # 大于最大值10

启动应用时会报错:

Failed to bind properties under 'app.security' to com.example.config.properties.ValidatedProperties:
    Property: app.security.admin-email
    Value: invalid-email
    Reason: 邮箱格式不正确

八、完整项目结构

spring-boot-config-demo/
├── pom.xml                              # 父项目POM
│
└── config-app/
    ├── pom.xml                          # 子模块POM
    └── src/
        ├── main/
        │   ├── java/com/example/config/
        │   │   ├── ConfigApplication.java
        │   │   ├── properties/
        │   │   │   ├── AppProperties.java
        │   │   │   ├── ValidatedProperties.java
        │   │   │   └── RefreshProperties.java
        │   │   └── controller/
        │   │       ├── ConfigController.java
        │   │       ├── AppPropertiesController.java
        │   │       ├── EnvironmentController.java
        │   │       ├── PriorityController.java
        │   │       ├── ProfileController.java
        │   │       ├── EncryptController.java
        │   │       └── ValidationController.java
        │   │
        │   └── resources/
        │       ├── application.yml              # 主配置文件
        │       ├── application-dev.yml          # 开发环境
        │       ├── application-test.yml         # 测试环境
        │       └── application-prod.yml         # 生产环境
        │
        └── test/
            └── java/com/example/config/
                └── JasyptTest.java              # 加密测试

九、常见坑点与解决方案

❌ 坑点1:YAML缩进错误

现象:启动报错Invalid yaml syntax

原因:YAML对缩进敏感,必须使用空格(不能用Tab)

解决方案

# ❌ 错误:使用Tab或缩进不一致
app:
	name: 测试  # Tab缩进
  version: 1.0  # 空格缩进

# ✅ 正确:统一使用2个空格
app:
  name: 测试
  version: 1.0

❌ 坑点2:@ConfigurationProperties不生效

现象:配置类属性全是null

原因:忘记添加@Component@EnableConfigurationProperties

解决方案

// 方式1:添加@Component
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties { }

// 方式2:启动类添加@EnableConfigurationProperties
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class ConfigApplication { }

❌ 坑点3:Profile未激活

现象:多环境配置未生效

原因:未指定active profile

解决方案

# 在application.yml中指定
spring:
  profiles:
    active: dev

# 或命令行指定
java -jar app.jar --spring.profiles.active=prod

❌ 坑点4:Java版本不兼容

现象:Spring Boot 4.x启动报错

原因:Java版本低于17

解决方案

# 检查Java版本
java -version

# 升级到Java 21(推荐)
# 下载:https://adoptium.net/

❌ 坑点5:加密配置解密失败

现象:启动报错Unable to decrypt property

原因:解密密钥未正确配置

解决方案

# 通过环境变量传递密钥(推荐)
export JASYPT_ENCRYPTOR_PASSWORD=my-secret-key
java -jar app.jar

# 或命令行参数
java -jar app.jar --jasypt.encryptor.password=my-secret-key

十、最佳实践总结

10.1 配置文件组织规范

文件用途提交Git
application.yml公共配置✅ 是
application-dev.yml开发环境✅ 是
application-test.yml测试环境✅ 是
application-prod.yml生产环境❌ 否(敏感信息)
application-local.yml本地个人配置❌ 否(加入.gitignore)

10.2 配置读取方式选择

场景推荐方式
单个简单配置@Value
一组相关配置@ConfigurationProperties
动态/编程式读取Environment
需要校验的配置@ConfigurationProperties + @Validated

10.3 Spring Boot 4.x 安全配置建议

# ✅ 推荐:敏感信息从环境变量读取
spring:
  datasource:
    password: ${DB_PASSWORD}

# ✅ 推荐:使用加密
spring:
  datasource:
    password: ENC(加密密文)

# ❌ 避免:明文密码
spring:
  datasource:
    password: 123456

10.4 Spring Boot 4.x 新特性利用

特性使用方式收益
JSpecify空安全添加jspecify依赖编译期空指针检查
YAML锚点&defaults*defaults配置复用,减少重复
AOT优化Native Image编译启动速度提升5-10倍
模块化自动配置按需引入starter减小JAR包体积

十一、本篇延伸思考

💡 思考题

  1. 为什么@ConfigurationProperties@Value更推荐?
  2. Spring Boot 4.x 对Java版本要求提高的原因是什么?
  3. 生产环境如何安全地管理配置密钥?
  4. 配置文件的11层优先级中,哪几层最常用?
  5. 如何在Docker/K8s环境中管理Spring Boot 4.x配置?

十二、下篇预告

第4篇:《Spring Boot 4.x日志系统全攻略:Logback配置与ELK集成》

将深入探讨:

  • Logback配置文件详解
  • 日志级别与输出格式定制
  • 日志文件滚动策略
  • 集成ELK(Elasticsearch + Logstash + Kibana)
  • 分布式链路追踪(OpenTelemetry)
  • Spring Boot 4.x 可观测性增强

附录:快速测试命令(一般如果使用IDEA 编辑器,都不需要,直接在编辑器里面跑就行,命令行要注意java版本)

# 1. 检查Java版本(需要Java 21+)
java -version

# 2. 构建项目
cd spring-boot-config-demo
mvn clean install

# 3. 启动开发环境
cd config-app
mvn spring-boot:run

# 4. 启动生产环境
java -jar target/config-app-1.0.0.jar --spring.profiles.active=prod

# 5. 测试配置接口
curl http://localhost:8080/api/config/value
curl http://localhost:8080/api/config/properties
curl http://localhost:8080/api/profile/info
curl http://localhost:8080/api/validation/security

# 6. 查看Actuator配置
curl http://localhost:8080/api/actuator/configprops
curl http://localhost:8080/api/actuator/env

# 7. 测试多环境
curl http://localhost:8080/api/profile/info
# 修改profile后重启,再次测试

所有示例代码已上传GitHub:注意(git仓库代码可能跟文章有些小出入,大家可以githup代码为准,如有疑问,可以关注公众号并留言咨询)

主仓库:https://github.com/beatafu/spring-boot-learning.git
本教程:[https://github.com/beatafu/spring-boot-learning/tree/main/03-starter-deep-dive](https://github.com/beatafu/spring-boot-learning/tree/main/03-spring-boot-config-demo)

Spring Boot 4.x 版本对照表

配置项Spring Boot 3.xSpring Boot 4.x
最低Java版本1717(推荐21+)
Spring Framework6.x7.x
Jakarta EE1011
配置元数据spring-configuration-metadata.json增强版(支持JSpecify)
自动配置单体47个轻量模块
AOT支持初步支持全面优化
虚拟线程实验性生产就绪

🎉 恭喜你完成第三篇! 现在你已经:

  • ✅ 掌握properties和yml两种配置格式
  • ✅ 理解配置文件的11层优先级
  • ✅ 能使用三种方式读取配置
  • ✅ 能配置多环境Profile
  • ✅ 能进行配置加密和校验
  • ✅ 了解Spring Boot 4.x新特性

建议:动手完成所有示例代码,特别是多环境配置和加密配置,这是生产环境必备技能!

作者:架构师Beata
日期:2026年3月18日
声明:本文基于网络文档整理,如有疏漏,欢迎指正。转载请注明出处。
互动代码完全经过自测,如有任何问题?欢迎在评论区分享