Java微服务(二):2.0 SpringBoot - 为简化开发而生


1. [SpringBoot 启航] 快速入门与核心原理

摘要: 本章将引导您迈出 Spring Boot 开发的第一步。我们将使用官方脚手架快速搭建并运行一个 Web 服务,理解其便捷部署的原理,并深入剖析 Spring Boot 两大核心特性——启动器 (Starter)自动配置 (Auto-configuration)

1.1. Spring Boot 快速部署

在开始动手之前,我们先花一分钟了解现代 Web 应用的主流架构模式,这将帮助我们理解为何 Spring Boot 的设计如此契合当下的开发需求。

1.1.1. 现代 Web 架构:前后端分离简介

现代 Web 开发大多采用 前后端分离 的模式。在这种架构下,前端(通常由 Vue, React, Angular 等框架构建)和后端(我们的 Spring Boot 应用)是两个独立的项目。

  • 后端 (Backend): 不再负责渲染 HTML 页面。它的核心职责是提供一套 API 接口(通常是 RESTful API),处理业务逻辑,操作数据库,并以统一的数据格式(如 JSON)返回结果。
  • 前端 (Frontend): 负责用户界面的展示和交互。它通过调用后端的 API 接口获取数据,然后将数据动态地渲染到页面上。

这种模式带来了职责清晰、开发高效、技术栈灵活等诸多好处。我们的教程将完全基于这种前后端分离的模式进行。


1.1.2. [实践] 使用 Spring Initializr 创建第一个 Web 应用

现在,我们来实现第一个需求:创建一个 Web 服务,当用户访问 http://localhost:8080/hello 时,页面显示 “Hello, Spring Boot!”

创建 Spring Boot 项目最快、最标准的方式是使用官方的 Spring Initializr (俗称“脚手架”)。

1. 使用 Idea 生成项目

  • 配置选项:
    • Project: Maven
    • Language: Java
    • Spring Boot: 3.3.3 (或更高稳定版)
    • Project Metadata:
      • Group: com.example
      • Artifact: spring-boot-demo
      • Packaging: Jar
      • Java: 17 (Spring Boot 3.x 要求 JDK 17+)
    • Dependencies: 点击 “ADD DEPENDENCIES…”,搜索并添加 Spring Web

配置完成后,点击 “GENERATE” 按钮,下载项目压缩包并用 IDEA 打开。

image-20250814091918843

2. 剖析项目结构与 pom.xml

IDEA 加载完成后,您会看到一个标准的 Maven 项目结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
. 📂 spring-boot-demo
├── 📄 pom.xml <- 核心依赖管理
└── 📂 src
├── 📂 main
│ ├── 📂 java
│ │ └── 📂 com/example/springbootdemo/
│ │ └── 📄 SpringBootDemoApplication.java <- 启动类
│ └── 📂 resources
│ └── 📄 application.properties <- 核心配置文件
└── 📂 test
└── 📂 java
└── 📂 com/example/springbootdemo/
└── 📄 SpringBootDemoApplicationTests.java

我们重点关注 pom.xml 中的两个核心配置:

文件路径: spring-boot-demo/pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

1.1.3. [实践] 编写 @RestController 并运行服务

1. 编写 Controller

文件路径: src/main/java/com/example/springbootdemo/controller/HelloController.java (新增文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.springbootdemo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @RestController 是一个组合注解,相当于 @Controller + @ResponseBody
* @Controller: 声明这是一个处理 HTTP 请求的控制器 Bean
* @ResponseBody: 告诉 Spring MVC,这个类中所有方法的返回值都应直接写入 HTTP 响应体中
* (对于前后端分离项目,通常就是返回 JSON 或纯文本)
*/
@RestController
public class HelloController {

/**
* @GetMapping("/hello"): 将此方法映射到处理 GET 方式的 "/hello" 请求
*/
@GetMapping("/hello")
public String sayHello() {
return "Hello, Spring Boot!";
}
}

重要: 我们自己创建的类(如 Controller, Service)必须放在 启动类 SpringBootDemoApplication.java 所在的包或其子包 下,才能被 Spring Boot 默认的组件扫描机制发现。

2. 运行与验证

现在,找到 SpringBootDemoApplication.java,直接右键运行其 main 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
// SpringBootDemoApplication.java
package com.example.springbootdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
// 启动 Spring Boot 应用
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}

当控制台输出 Tomcat started on port(s): 8080 (http) 时,表示服务已成功启动。

打开浏览器,访问 http://localhost: 8080/hello。


1.2. 便捷的部署与执行

Spring Boot 极大地简化了应用的部署过程,其核心就是 可执行 JAR 包

1.2.1. [实践] 打包为可执行 Fat JAR

在 IDEA 的 Maven 窗口中,执行 package 命令。

打包成功后,在 target 目录下会生成一个 spring-boot-demo-0.0.1-SNAPSHOT.jar 文件。将这个 JAR 包复制到 任何安装了 Java 17+ 环境 的机器上,在命令行中执行:

1
java -jar spring-boot-demo-0.0.1-SNAPSHOT.jar
1
2
3
4
5
6
7
8
9
10
11
12
  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.3.3)

... (省略日志)
...
2025-08-14T21:30:00.123Z INFO 12345 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
...

服务再次启动!这证明了 Spring Boot 应用的 可移植性,无需外部的 Tomcat 服务器。


1.2.2. Spring Boot JAR 与普通 JAR 的区别

为什么 Spring Boot 的 JAR 包可以直接运行,而普通的 JAR 包不行?

面试官深度辨析
今天 晚上 9:45

Spring Boot 打包的“Fat JAR”和我们平时用的普通 JAR,在结构上有什么本质区别?

求职者

最大的区别在于内容。普通 JAR 只包含我们自己编写的 .class 文件和资源。而 Spring Boot 的 Fat JAR 除了包含我们自己的代码,还内置了三样关键东西:

求职者

1.所有运行时依赖 : 所有的第三方 JAR 包(如 Spring MVC, Tomcat, Jackson)都被打包进来了。

求职者

2.内嵌的 : Tomcat 服务器的 JAR 包也被包含在内。

求职者

3.一个特殊的启动器类 :Spring Boot 提供了一个 JarLauncher 类,它知道如何正确地加载所有内嵌的依赖并启动应用。

非常好。所以,可执行性就是这么来的?

求职者

是的。Fat JAR 的 MANIFEST.MF 文件中,Main-Class 被指向了这个 JarLauncher。所以当我们执行 java -jar 时,实际上是 JarLauncher 在工作,它创建了一个特殊的类加载器来加载 BOOT-INF/lib/ 下的依赖,然后才调用我们自己写的 main 方法。而普通 JAR 没有这个机制。


1.3. 自动配置核心揭秘

我们已经体验到了 Spring Boot 的便捷,现在是时候揭开其背后的魔法了。

1.3.1. [面试题] SpringApplication.run() 背后发生了什么?

面试官深度剖析
昨天 上午 10:00

当我们执行 SpringApplication.run(MyApplication.class, args) 这一行代码时,Spring Boot 在背后都做了哪些核心工作?

求职者

这是一个经典的启动流程问题。概括来说,它主要做了四件大事:

求职者

1.推断应用类型 : Spring Boot 会检查类路径,判断这是一个标准的 Servlet Web 应用,还是一个响应式的 WebFlux 应用,或是一个非 Web 应用。

求职者

2.加载自动配置类 : 这是最核心的一步。它会从所有依赖的 JAR 包中,找到 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,加载里面定义的所有自动配置类。

求职者

3.创建并准备IoC容器 : 创建一个 ApplicationContext 实例,然后执行一系列的准备工作,比如加载我们的主配置类 (MyApplication.class),将命令行参数 args 注册为 Bean 等。

求职者

4.刷新容器并启动组件 : “刷新” 容器是 Spring 的一个术语,它会触发所有 Bean 的实例化、依赖注入、初始化等过程。在这个过程中,之前加载的自动配置类会根据条件 (@ConditionalOn...) 判断是否生效。对于 Web 应用,此阶段还会启动内嵌的 Tomcat 服务器。


1.3.2. 魔法的起点:@SpringBootApplication 注解剖析

我们可以在 SpringBootDemoApplication 里面找到这个注解,那么这个注解的作用是什么?

SpringApplication.run() 的所有行为,都源于启动类上的 @SpringBootApplication 注解。它是一个复合注解,由三个核心注解组成:

  • @SpringBootConfiguration:
    • 它本身被 @Configuration 注解,所以 Spring Boot 的主启动类本质上就是一个 Java 配置类。这意味着我们可以在启动类中直接定义 @Bean
  • @EnableAutoConfiguration:
    • 启用自动配置。这是 Spring Boot 最神奇的地方。它会触发上面提到的“加载自动配置类”的流程。
  • @ComponentScan:
    • 启用组件扫描。它告诉 Spring 去哪里查找我们自己定义的 Bean(如 @Component, @Service, @RestController 等)。默认的扫描路径是 启动类所在的包及其所有子包

1.3.3. 按需加载:条件注解 (@ConditionalOn...) 的作用

@EnableAutoConfiguration 虽然加载了所有可能的自动配置类,但并非所有都会生效。每个自动配置类都被一系列的 条件注解 (@ConditionalOn...) 所保护。

工作原理:
Spring Boot 在处理自动配置类时,会检查其上的条件注解。只有当所有条件都满足时,这个配置类才会生效

常用条件注解:

注解作用
@ConditionalOnClass当类路径下 存在 指定的类时,配置生效。 (这是最常用的)
@ConditionalOnMissingClass当类路径下 不存在 指定的类时,配置生效。
@ConditionalOnBean当 IoC 容器中 存在 指定的 Bean 时,配置生效。
@ConditionalOnMissingBean当 IoC 容器中 不存在 指定的 Bean 时,配置生效。 (常用于提供默认 Bean)
@ConditionalOnPropertyapplication.properties存在 指定的配置项时,配置生效。
@ConditionalOnWebApplication仅当当前应用是 Web 应用 时,配置生效。

示例:
WebMvcAutoConfiguration (Spring MVC 的自动配置) 的部分源码:

1
2
3
4
5
6
7
8
// 只有当应用是 Servlet Web 应用,并且类路径下存在 Servlet, DispatcherServlet, WebMvcConfigurer 时,
// 并且容器中还没有用户自定义的 WebMvcConfigurationSupport Bean,此配置才会生效。
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {
// ...
}

2. [核心配置] Spring Boot 外部化配置详解

摘要: 本章将深入探讨 Spring Boot 强大而灵活的配置管理机制。我们将对比 propertiesyml 两种主流格式,并重点掌握 @Value@ConfigurationProperties 两种读取配置的方式,最后学习如何通过 Profiles 实现多环境的配置隔离。

2.1. 外部化配置的核心思想与加载顺序

2.1.1. “痛点”场景:为什么配置需要与代码分离?

想象一下,在我们刚刚完成的 Hello, World! 项目中,我们需要连接一个数据库。一个初级的做法可能是将数据库连接信息硬编码在代码里:

1
2
3
4
5
6
7
8
public class DatabaseConnector {
public void connect() {
String url = "jdbc:mysql://localhost:3306/dev_db";
String username = "root";
String password = "dev_password";
// ... 连接逻辑
}
}

这段代码存在一个致命问题:配置与代码高度耦合。当我们需要将应用从开发环境部署到测试环境,再到生产环境时,数据库的地址、用户名和密码必然会改变。难道我们每次部署都要去修改 Java 源代码,然后重新编译、打包吗?这显然是低效且极易出错的。

解决方案:
将这些易变的配置信息从代码中抽离出来,存放到代码外部的文件中。应用程序在启动时去读取这些外部文件,获取配置。这就是外部化配置的核心思想。


2.1.2. Spring Boot 外部化配置的优势

Spring Boot 将外部化配置思想发挥到了极致,带来了诸多好处:

优势说明
灵活性同一份应用程序代码,无需重新打包,即可通过切换不同的配置文件,在不同环境中运行。
易于维护配置信息集中管理,非开发人员(如运维)也可以安全地修改配置,只需重启应用即可生效。
安全性可以将数据库密码、API密钥等敏感信息存储在生产服务器的特定文件中,而不会泄露在代码仓库里。
标准化Spring Boot 提供了一套标准化的配置机制,使得配置管理变得简单且有据可循。

2.1.3. 关键:Spring Boot 配置的加载优先级顺序

Spring Boot 可以在很多不同的地方查找配置,并且有一套严格的优先级顺序。高优先级的配置会覆盖低优先级的配置。了解这个顺序对于排查“配置为何没生效”的问题至关重要。

部署结构示例

假设我们的应用打包后,部署结构如下:

1
2
3
4
5
. 📂 /opt/app/
├── 📄 my-app-1.0.0.jar # 应用程序 JAR 包
├── 📄 application.yml # JAR 包外部的配置文件
└── 📂 config/ # JAR 包外部的 config 目录
└── 📄 application.yml # config 目录下的配置文件

在这个结构中,my-app-1.0.0.jar 内部的 resources/ 目录下也包含一个 application.yml 文件。

配置加载优先级

  1. jar 包内部的 application.propertiesapplication.yml (优先级最低)
  2. jar 包外部的 application.propertiesapplication.yml (与 jar 包同级目录下)
  3. jar 包外部的 config/ 目录下的 application.propertiesapplication.yml
  4. 操作系统环境变量
  5. Java 系统属性 (-Dkey=value)
  6. 通过命令行参数传入的配置 (--key=value) (优先级最高)

核心理念: 这个设计允许运维人员在不触碰任何代码包的情况下,通过命令行参数外部配置文件来覆盖应用打包时的默认配置,实现了完美的“运维友好”。


2.2. 主流配置方式:.properties vs .yml

Spring Boot 主要支持两种格式的配置文件:.properties.yml

2.2.1. application.properties 语法与用法

这是 Java 中传统的配置文件格式,以简单的键值对形式存在。

文件路径: src/main/resources/application.properties

1
2
3
4
5
6
# 服务器端口
server.port=8080

# 数据库连接信息
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root

2.2.2. application.yml 的层级结构与语法优势

YAML (.yml) 是一种对人类阅读更友好的数据序列化语言。它通过缩进来表示层级关系,结构非常清晰。

文件路径: src/main/resources/application.yml

1
2
3
4
5
6
7
8
9
# 服务器配置
server:
port: 8080

# 数据库连接信息
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root

YAML 语法关键:

  1. 使用空格进行缩进,严禁使用 Tab 键
  2. : 冒号后面必须至少有一个空格

2.2.3. 两种格式的优先级与选择建议

特性.properties 文件.yml 文件
格式扁平的键值对 (spring.datasource.url=...)层级的树状结构,更清晰
可读性配置多时可读性差非常适合描述复杂的、有层级的配置数据
优先级高于 .yml低于 .properties
建议推荐使用 .yml,因其结构化能力远超 .properties仅在需要覆盖 .yml 中某个特定值时少量使用

resources 目录下同时存在 application.propertiesapplication.yml 时,Spring Boot 会两个都加载。如果两个文件中有相同的配置项,.properties 文件中的值会覆盖 .yml 文件中的值。


2.3. 读取配置:@Value vs @ConfigurationProperties

“痛点”场景:

好了,我们已经在 application.yml 中定义了配置,但我们的 Java 代码如何才能获取到这些值呢?

Spring Boot 提供了两种主流的注解来解决这个问题:@Value@ConfigurationProperties

2.3.1. [实践] 使用 @Value 读取单个配置 (含默认值)

@Value 注解非常适合用于注入单个简单的配置值。

文件路径: src/main/resources/application.yml

1
2
3
4
app:
name: "My Awesome App"
owner: "Prorise"
# 我们故意不设置 port

文件路径: src/main/java/com/example/springbootdemo/config/AppInfo.java (新增文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.springbootdemo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AppInfo {

// 使用 ${...} 占位符来引用配置文件中的 key
@Value("${app.name}")
private String name;

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

// 使用冒号 ":" 来提供一个默认值
// 如果配置文件中找不到 app.port,则会使用 8080
@Value("${app.port:8080}")
private Integer port;

@Override
public String toString() {
return "AppInfo{" +
"name='" + name + '\'' +
", owner='" + owner + '\'' +
", port=" + port +
'}';
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
@SpringBootTest
class ConfigTest {
@Autowired
private AppInfo appInfo;

@Test
void testValueAnnotation() {
System.out.println(appInfo);
}
}
1
AppInfo{name='My Awesome App', owner='Prorise', port=8080}

2.3.2. [推荐] 使用 @ConfigurationProperties 进行类型安全的属性绑定

“痛点”场景:

@Value 注解虽然简单,但如果要注入的配置项非常多(比如一个数据源有十几个配置),在类中写十几个 @Value 注解会显得非常繁琐和重复。而且,它对复杂的嵌套结构或集合配置支持不佳。

解决方案:
@ConfigurationProperties 注解为此而生。它允许我们将配置文件中一个前缀下的所有属性,整体地、类型安全地绑定到一个 Java 对象(POJO)上。

文件路径: src/main/resources/application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
datasource:
mysql:
url: jdbc:mysql://localhost:3306/prod
username: prod_user
password: prod_password
# 集合配置
connection-properties:
- characterEncoding=utf8
- useSSL=false
# 嵌套对象配置
pool-options:
max-active: 20
min-idle: 5

文件路径: src/main/java/com/example/springbootdemo/config/MySQLProperties.java (新增文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.springbootdemo.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;

@Data // Lombok 注解,自动生成 Getter/Setter
@Component
// 关键:将此类与配置文件中 "datasource.mysql" 为前缀的属性进行绑定
@ConfigurationProperties(prefix = "datasource.mysql")
public class MySQLProperties {

private String url;
private String username;
private String password;
private List<String> connectionProperties; // 对应 YAML 中的列表
private PoolOptions poolOptions; // 对应 YAML 中的嵌套对象

@Data
public static class PoolOptions { // 内部静态类用于映射嵌套对象
private int maxActive;
private int minIdle;
}
}

测试代码:

注意: 记得将上一节的ApiInfo删除,因为我们已经修改了yml文件,不删除会找不到bean的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example.springbootdemo;
import com.example.springbootdemo.config.MySQLProperties;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootDemoApplicationTests {


@Autowired
private MySQLProperties mySQLProperties;

@Test
void testConfigProperties() {
System.out.println(mySQLProperties);
}

}

1
2
3
MySQLProperties(url=jdbc:mysql://localhost:3306/prod, username=prod_user, 
password=prod_password, connectionProperties=[characterEncoding=utf8, useSSL=false],
poolOptions=MySQLProperties.PoolOptions(maxActive=20, minIdle=5))

2.3.3. 启用属性类:@Component vs @EnableConfigurationProperties

“痛点”场景:

我们已经创建了一个 MySQLProperties 类,并用 @ConfigurationProperties 标注了它。但是,Spring Boot 默认并不会知道这个普通 Java 类的存在。我们必须通过某种方式告诉 Spring:“嘿,这是一个需要你来管理的 Bean,请在容器启动时创建它,并把配置文件里的值绑定进去!”。那么,我们有几种方式来“激活”或“启用”这个属性类呢?

Spring Boot 提供了两种主流的激活方式。

方案一:使用 @Component (简单、直接)

这是最直接的方式。我们只需在属性类上添加 @Component 注解,它就会被 Spring Boot 的组件扫描 (@ComponentScan) 机制自动发现,并注册为一个 Bean。

文件路径: src/main/java/com/example/springbootdemo/config/MySQLProperties.java

1
2
3
4
5
6
7
8
9
10
11
package com.example.springbootdemo.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; // 1. 引入 Component 注解

@Data
@Component // 2. 将此类声明为一个通用的 Spring 组件
@ConfigurationProperties(prefix = "datasource.mysql")
public class MySQLProperties {
// ... 属性不变
}
  • 优点: 简单明了,所有配置都集中在一个类上。
  • 缺点: 属性类 (MySQLProperties) 与 Spring 框架的 @Component 注解产生了耦合。对于我们自己项目内部的配置类,这通常是可以接受的。

方案二:使用 @EnableConfigurationProperties (集中、解耦)

“进阶痛点”:

如果 MySQLProperties 这个类来源于一个我们无法修改的第三方 jar 包呢?或者,在一个大型项目中,我们希望将所有的配置属性类在一个地方进行集中式的、统一的管理,而不是让它们散落在各个角落,我们该怎么做?

解决方案:
这就是 @EnableConfigurationProperties 的用武之地。它允许我们在一个集中的配置类(通常是我们自己创建的,被 @Configuration 标注的类)中,明确地列出所有需要被激活的属性类。

第一步:移除属性类上的 @Component
MySQLProperties 变回一个纯粹的、不依赖 Spring 框架的 POJO。
文件路径: src/main/java/com/example/springbootdemo/config/MySQLProperties.java (修改)

1
2
3
4
5
6
7
8
9
10
package com.example.springbootdemo.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "datasource.mysql")
// 移除了 @Component 注解
public class MySQLProperties {
// ... 属性不变
}

第二步:创建集中的配置管理类
文件路径: src/main/java/com/example/springbootdemo/config/AppConfig.java (新增文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.springbootdemo.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

// 1. @Configuration 声明这是一个配置管理类
@Configuration
// 2. @EnableConfigurationProperties 明确告诉 Spring Boot
// 去激活并注册 MySQLProperties.class 这个属性类
@EnableConfigurationProperties(MySQLProperties.class)
public class AppConfig {
// 这个类可以是空的,它的主要作用就是通过注解来集中管理配置。
// 当然,我们后续还可以在这里定义 @Bean 等。
}

对比总结

特性方式一: @Component方式二: @EnableConfigurationProperties
使用方式直接在属性类上添加 @Component在一个 @Configuration 配置类上添加 @EnableConfigurationProperties
优点简单、直接、自包含集中管理与框架解耦(属性类本身可以是纯 POJO)
适用场景项目内部自己定义的属性类1. 启用第三方的属性类<br>2. 在项目中集中管理所有属性配置

2.4. 多环境配置管理 (Profiles)

“痛点”场景:

我们的应用开发完成,即将上线。但在部署时遇到了一个典型问题:开发环境用的是本地数据库,测试环境用的是测试库,而生产环境则需要连接生产主库。难道我们每次部署到不同环境,都要手动去修改 application.yml 里的数据库连接信息,然后再重新打包吗?这不仅效率低下,而且极易引发“配错环境”的生产事故。

解决方案:
Spring Profiles 是专门为解决这一问题而设计的。它允许我们为不同的环境创建各自独立的配置文件,在应用启动时,只需通过一个简单的开关,就能激活指定环境的配置,实现“一次打包,多环境灵活部署”。

2.4.1. [实践] application-{profile}.yml 的约定与使用

Spring Boot 约定,特定环境的配置文件命名格式为 application-{profile}.yml (或 .properties),其中 {profile} 就是你自定义的环境名(如 dev, test, prod)。

现在,我们在 src/main/resources 目录下进行如下改造:

  1. application.yml: 作为主配置文件,存放所有环境共享的配置,并指定默认激活的环境。
  2. application-dev.yml: 存放开发环境特有的配置。
  3. application-prod.yml: 存放生产环境特有的配置。

文件路径: src/main/resources/application.yml

1
2
3
4
5
6
7
8
9
10
11
# --------------------
# 主配置文件 (共享配置)
# --------------------
spring:
application:
name: my-app # 比如应用名称,是所有环境共享的

# 关键:指定默认激活的环境为 'dev'
# 如果不通过其他更高优先级的方式指定,应用将默认加载 application-dev.yml
profiles:
active: dev

文件路径: src/main/resources/application-dev.yml

1
2
3
4
5
6
7
8
# --------------------
# 开发环境 (dev)
# --------------------
server:
port: 8080 # 开发时使用 8080 端口
app:
owner: Prorise # 作者名
env: Development Environment

文件路径: src/main/resources/application-prod.yml

1
2
3
4
5
6
7
8
# --------------------
# 生产环境 (prod)
# --------------------
server:
port: 80 # 生产环境通常使用 80 端口
app:
owner: Prorise
env: Production Environment

工作原理: Spring Boot 启动时,会总是先加载主配置文件 application.yml,然后再根据激活的 profile (比如 dev),去加载对应的 application-dev.yml。后加载的配置会覆盖先加载的同名配置。


2.4.2. [实践] 激活特定 Profile 的多种方式

我们有多种方式来激活一个 Profile,它们的优先级各不相同。

方式一:在主配置文件中指定 (开发默认)

这是我们在 application.yml 中已经做过的方式,通过 spring.profiles.active 属性来设置。它通常用于指定本地开发时的默认环境。

1
2
3
spring:
profiles:
active: dev

方式二:通过命令行参数 (生产部署首选)

这是优先级更高生产环境部署最常用的方式。它可以在不修改任何打包文件的情况下,在启动时动态指定环境。

1
2
# 激活生产环境配置来启动应用
java -jar my-app.jar --spring.profiles.active=prod

实践验证

为了验证 Profile 是否生效,我们创建一个简单的 Bean 来读取配置。

文件路径: src/main/java/com/example/springbootdemo/config/AppInfo.java (修改)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.springbootdemo.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component
public class AppInfo {
@Value("${app.owner}")
private String owner;

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

文件路径: src/test/java/com/example/springbootdemo/SpringBootDemoApplicationTests.java (添加新测试)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SpringBootDemoApplicationTests.java

import com.example.springbootdemo.config.AppInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootDemoApplicationTests {

@Autowired
private AppInfo appInfo;

@Test
void testProfile() {
// 打印注入的配置,验证当前激活的是哪个环境
System.out.println(appInfo);
}
}

1. 默认环境测试
直接在 IDEA 中运行 testProfile() 测试。

1
AppInfo(owner=Prorise, env=Development Environment)

2. 切换环境测试
在 IDEA 中,我们可以模拟命令行参数来切换 Profile:

  • 点击 Edit Configurations...
  • Program arguments 中输入 --spring.profiles.active=prod
    再次运行 testProfile() 测试。
1
AppInfo(owner=Prorise, env=Production Environment)

结论: 实验证明,通过激活不同的 Profile,我们成功地让应用加载了不同的配置,而无需修改任何代码。