1.快速开始
用 Idea 可以很容易地创建 SpringBoot 应用:
选择合适的 SpringBoot 版本以及需要的依赖:
目前依然在维护的 2.X 版本是 2.7.15,不同的版本对 java 版本的需求不同,比如 3.X,要求最低 Java 17,2.7.15 要求最低 Java 8,关于 Spring Boot 对环境的要求可以查看对应的。
自动生成框架代码后,我们不需要额外配置,只需要添加一个 Controller 就可以完成最简单的一个 Web 应用示例:
"/books")
(public class BookController {
"/{id}")
( public Book getBookInfo( ("id") Integer id){
Book book = new Book();
book.setId(id);
book.setName("巴顿传");
book.setType("人物传记");
return book;
}
}
当然还有对应的实体类,这里不做展示。
SpringBoot 内部包含一个 Tomcat,所以不需要我们添加 Tomcat Maven 插件,可以直接从入口类启动程序:
当然,通过 Maven 生命周期启动(mvn run)也是可行的。
默认 SpringBoot 监听 8080 端口,所以这里访问 http://localhost:8080/books/2 就能看到接口返回信息。
相比之前已经学习过的 SSH 框架(Spring+SpringMVC+Mybatis),无疑 SpringBoot 更为方便,对于开发一个 Web 应用来说,这种方便体现在:
-
不需要手动添加诸多依赖,比如 spring-mvc、servelet-api 等。
-
不需要创建 Spring 和 SpringMVC 的配置类,以及相应的组件扫描和资源文件引用。
-
不需要添加 Servlet 配置的相关类(向 Servlet 注入 Spring 容器)
-
不需要从 Maven 模版生成框架代码,以及补齐缺失的目录。
-
自身包含 Tomcat,无需额外准备 Tomcat 环境即可启动。
2.Spring Boot Maven 插件
默认生成的 Spring Boot 框架的 pom.xml 配置文件中包含一个插件:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
这个插件的用途是在对 Spring Boot 应用打包时,将其依赖的其它 Jar 包都打包进去,并指定 Spring Boot 的 jar 包作为入口程序。这样打包后的 Jar 包就可以在任何安装了 Java 环境的机器上以 Jar 包方式运行(java -jar xxx)。
默认情况下(没有 Spring Boot Maven)插件,Maven 打包后的 Jar 包是不会包含其它依赖的 jar 包的。
war 包与 jar 包不同,默认情况下打包后的 war 包就会包含所依赖的其它 jar 包。
3.Starter
在之前介绍 Maven 的中,我们已经看到了一个 Maven 模块,可以通过继承的方式从父模块那里继承依赖,也可以通过依赖传递的方式从包含的依赖中获取更多的间接依赖。
实际上 Spring Boot 就是利用这一点来简化 Spring Boot 中的依赖添加。
示例项目的 pom.xml 包含一个父模块配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
我们可以点击查看父模块 spring-boot-starter-parent 的 pom.xml 内容。
3.1.spring-boot-starter-parent
其中定义了使用的 Java 版本与编码:
<properties>
<java.version>1.8</java.version>
<resource.delimiter>@</resource.delimiter>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
开源许可证:
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
</license>
资源过滤器:
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
这个资源过滤器的设置表示项目的资源目录(/resources
)下的配置文件(properties 或 yaml)中可以使用 Maven 变量。
插件管理:
<pluginManagement>
<plugins>
<plugin>
<!-- ... -->
</plugin>
</plugins>
</pluginManagement>
更多的信息保存在其父模块中:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.15</version>
</parent>
查看其父模块 spring-boot-dependencies 的 pom 文件。
3.2.spring-boot-dependencies
可以看到其包含非常多的自定义属性:
<properties>
<activemq.version>5.16.6</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.98</appengine-sdk.version>
<artemis.version>2.19.1</artemis.version>
<aspectj.version>1.9.7</aspectj.version>
<!-- ... -->
</properties>
这些属性用于定义依赖管理:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-amqp</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-blueprint</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>${activemq.version}</version>
</dependency>
<!-- ... -->
</dependencies>
</dependencyManagement>
也就是说,基本上我们在开发 Spring Boot 应用时会使用的依赖,都被 spring-boot-dependencies 管理了起来,我们就不用纠结使用何种版本的依赖以及会不会引发兼容性问题。
与依赖类似,我们可能用到的插件同样被管理起来了:
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${build-helper-maven-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>${flyway.version}</version>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>${git-commit-id-plugin.version}</version>
</plugin>
<!-- ... -->
</plugins>
</pluginManagement>
</build>
我们同样不需要纠结版本的问题。
3.3.spring-boot-starter-test
在我们项目的 pom.xml 中,除了父模块,还默认添加了一个用于测试的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
这个依赖中包含了测试所需的各种依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.29</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>compile</scope>
</dependency>
<!-- ... -->
</dependencies>
如果我们的项目需要添加单元测试,我们只需要添加这一个依赖即可,不需要手动添加多个依赖。
3.4.spring-boot-starter-web
与之类似,因为我们这里是一个 Web 应用,所以在创建 SpringBoot 项目时选择了 Web 功能,这体现在初始化的 pom.xml 中会自动添加一个 spring-boot-starter-web 依赖:
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.7.15</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.29</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.29</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
这其中的依赖 spring-boot-starter-tomcat 包含 SpringBoot 内嵌的 Tomcat 所需的依赖:
<dependencies>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.79</version>
<scope>compile</scope>
</dependency>
<!-- ... -->
</dependencies>
SpringBoot 应用通过入口类启动时,会自动检查包含的依赖,如果有 spring-boot-starter-web 相关依赖,就会以 Web 应用的方式启动内置的 Tomcat 服务。
3.5.总结
也就是说,SpringBoot 通过用父模块对依赖和插件进行版本管理,并且按照功能,将我们可能用到的依赖封装成各种 starter 命名的依赖,我们需要何种功能的相关依赖,只要引用对应的 starter 即可。通过这种方式,SpringBoot 简化了项目开发时的依赖添加和管理。
3.6.案例:使用 Jetty
Jetty 是一个和 Tomcat 用途相同的 Web 服务器软件,它比 Tomcat 更轻量级。利用 Spring Boot 的 starter,我们可以很容易地将 Spring Boot 使用的 Web 服务器软件从 Tomcat 替换为 Jetty。
首先我们需要屏蔽掉 Tomcat 相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
添加 Jetty 相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
和 Tomcat 一样,所有依赖都包含在一个 starter 里,我们只要添加和删除就能增加或移除相应的功能。
这个依赖项同样可以在 spring-boot-dependencies 的 pom 中找到。
启动服务后就能看到 Spring Boot 已经改为使用 Jetty:
main] org.eclipse.jetty.util.log main] o.s.b.w.e.j.JettyServletWebServerFactory main] org.eclipse.jetty.server.Server
4.配置文件
4.1.格式
SpringBoot 可以配置三种类型的配置文件:
-
properties
-
yml
-
yaml
下面以修改应用监听端口的配置说明它们的写法:
properties
server.port=80
yml
server:
port: 81
注意,
:
后属性值之前有一个空格。
yaml
server: port: 82
yaml 与 yml 的格式完全相同。
4.1.1.优先级
一般推荐使用 yml 配置文件,但如果项目中同时存在三种形式的配置文件,其优先级为:properties > yml > yaml。
这点可以通过配置不同的监听端口,并观察哪个配置生效来验证。
4.1.2.自动提示
使用 Idea 编辑配置文件时,会有很方便的自动提示:
能够自动提示的前提是 Idea 要能正确识别到哪些文件是配置文件,如果因为某些原因没有正确识别,也就不会有自动提示。这时候需要我们手动添加对应的文件为项目的配置文件。
具体的添加配置文件的功能在 Project Structure 的 Facets 标签页中:
4.2.YAML格式
YAML(YAML Ain't Markup Language),一种数据序列化格式。这种格式的配置文件在近些年已经占有主导地位。
YAML格式的文件扩展名可以是yml
也可以是yaml
,一般使用yml
。
4.2.1.语法
-
大小写敏感
-
属性层级关系使用多行描述,每行结尾使用冒号结束
-
使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
空格的个数并不重要,只要保证同层级的左侧对齐即可。
-
属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
-
# 表示注释
在 yaml 文件中如果要表示一个属性有多个值(数组):
my:
hobbies:
- music
- draw
- travel
5.读取配置
5.1.@Value
可以用介绍过的 @Value
注解注入的方式读取配置文件中的属性。
假设配置文件中的内容:
author:
name: icexmoon
age: 18
hobbies:
- music
- draw
- travel
用 @Value
读取:
@RestController
@RequestMapping("/config")
public class ConfigController {
@Value("${author.name}")
private String name;
@Value("${author.age}")
private Integer age;
@Value("${author.hobbies[0]}")
private String hobby1;
@GetMapping("/print")
public void print(){
System.out.println(name);
System.out.println(age);
System.out.println(hobby1);
}
}
5.2.Environment
SpringBoot 有一个预设的 Environment
类型的 Bean,可以用它来读取配置文件中的属性:
@RestController
@RequestMapping("/config")
public class ConfigController {
@Autowired
Environment environment;
@GetMapping("/print")
public void print(){
System.out.println(environment.getProperty("author.name", String.class));
System.out.println(environment.getProperty("author.age", Integer.class));
System.out.println(environment.getProperty("author.hobbies[0]", String.class));
}
}
5.3.@ConfigurationProperties
还可以使用@ConfigurationProperties
注解定义的 Bean 读取配置:
@Component
@ConfigurationProperties(prefix = "author")
@Data
public class AuthorProperties {
String name;
Integer age;
List<String> hobbies;
}
之后只要注解注入这个 Bean 并调用 Getter 方法就可以了:
@RestController
@RequestMapping("/config")
public class ConfigController {
@Autowired
private AuthorProperties authorProperties;
@GetMapping("/print")
public void print(){
System.out.println(authorProperties.getName());
System.out.println(authorProperties.getAge());
System.out.println(authorProperties.getHobbies());
}
}
创建自定义配置类后,Idea 会出现一个错误提示:
提示我们需要创建一个注释处理器(Annotation Processor),你可以点击右上角链接跳转到官方文档复制相关依赖,并加入到你的项目中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
这是 Spring 官方最推荐的方式,原因是:
-
读取后的数据具有良好层次格式
-
可以将数组形式的属性直接加载为一个数组或者序列(List)
-
可以使用 Hibernate Validation 对属性值的合法性进行检验
关于最后一点,这里举一个简单的例子:
@Component
@ConfigurationProperties(prefix = "author")
@Data
@Validated
public class AuthorProperties {
@NotNull
@Length(min = 2,max = 10)
String name;
@NotNull
@Min(0)
@Max(150)
Integer age;
@NotNull
@Size(min = 1, max = 10)
List<String> hobbies;
}
这里使用了 Hibernate Validation 相关的注解(通过添加 spring-boot-starter-validation 依赖引入),这样 Spring 在将配置中的属性读入AuthorProperties
这个类后,会按照相应注解的规则进行合法性校验。如果不通过,比如配置文件中设置的年龄大于150,应用启动时就会报错:
Property: author.age Value: "200" Origin: class path resource [application.yml] - 4:8 Reason: 最大不能超过150
这有助于严格约束配置文件中的属性值,并且尽早发现因为属性值设置错误导致的bug。
6.多环境开发
一般我们开发都需要将多个环境下的配置做区分,比如开发、测试、生产要连接不同的数据库。
6.1.yml
在 SpringBoot 中,很容易可以在 yml 配置中区分多个环境下的配置:
# 默认配置
author:
name: icexmoon
age: 18
hobbies:
- music
- draw
- travel
spring:
profiles:
active: dev
# 开发环境
---
spring:
config:
activate:
on-profile: dev
server:
port: 80
# 测试环境
---
spring:
config:
activate:
on-profile: test
server:
port: 81
# 生产环境
---
spring:
config:
activate:
on-profile: prd
server:
port: 82
这里用---
可以将一个 yml 文件拆分成多个配置文件,每个配置文件的标识用spring.config.activate.on-profile
属性定义。
配置文件标识也可以用
spring.profiles
属性定义,不过该属性已经被废弃。
最上方的部分为默认属性,启用哪个配置文件由其中的spring.profiles.active
属性决定。
6.2.properties
如果使用 properties 配置,需要使用多个 properties 文件保存不同环境下的配置信息:
-
application.properties ,主配置文件
-
application-dev.properties,开发环境配置文件
-
application-test.properties,测试环境配置文件
-
application-prd.properties,生产环境配置文件
同样的,具体加载哪个环境的配置文件由主配置文件中的spring.profiles.active
属性决定。
6.3.命令行启动
当 SpringBoot 应用打成 jar 包并通过命令行的方式启动时,可以添加命令行参数覆盖配置中的属性值。
比如,通常打成的 jar 包都是加载开发环境的配置文件,如果要加载其它环境,可以:
java -jar .\boot-demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=test
可以覆盖多个配置属性:
java -jar .\boot-demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=test --server.port=8080
实际上 SpringBoot 应用使用的属性来源是很多的,有默认属性、有来自配置文件、还有来自系统环境变量等等,这些来源是有优先级的,如果多个来源都定义了同一个名称的属性,优先级高的生效。
关于属性的优先级,可以查看。
6.4.Maven 属性
在介绍过如何在配置文件中使用 Maven 属性,我们可以利用这点来通过 Maven 控制那个环境的配置文件生效:
<profiles>
<profile>
<id>dev</id>
<properties>
<profiles.active>dev</profiles.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>test</id>
<properties>
<profiles.active>test</profiles.active>
</properties>
</profile>
<profile>
<id>prd</id>
<properties>
<profiles.active>prd</profiles.active>
</properties>
</profile>
</profiles>
这里定义了三个 Maven “配置文件”,分别命名为 dev、test、prd,并且都包含一个 Maven 自定义属性 profiles.active,这个属性的值对应 yml 文件中的不同环境的配置文件名称。
在 yml 文件中使用上边定义的 Maven 属性:
spring:
profiles:
active: ${profiles.active}
这样还不能让 yml 文件中的 Maven 属性生效。如果编译项目,就会在 jar 包中看到 yml 的实际内容是:
spring:
profiles:
active: ${profiles.active}
要让配置文件中的 Maven 属性生效,还需要在 POM 文件中添加一个插件:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>utf-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
</configuration>
</plugin>
在这里与之前的做法有所不同,没有使用 Resource 标签,因为这里不是处理子模块配置中的 Maven 属性。
再次编译后就能看到 yml 文件的内容已经被 Maven 属性替换:
spring:
profiles:
active: dev
使用 Maven 属性控制生效的环境配置有一个好处:可以更灵活地为特定环境生成 jar 包。
比如,在当前示例中,我们生成的 jar 包如果不加命令行参数,默认使用开发环境的配置运行。我们可以在打包时添加 Maven 参数来生成一个默认使用测试环境配置运行的 jar 包:
mvn package --activate-profiles test
用同样的方式你可以生成一个默认使用生产环境配置运行的包。
这样做的好处是在运行是不需要再使用--spring.profiles.active=xxx
来修改加载的环境配置。
6.5.分层配置
最常见的配置文件(比如 application.yml)是放在项目的 /resources 目录下的,其实除了那里,还可以放在多个地方。
假设默认的 /resources/application.yml 中的一个自定义属性设置如下:
# 默认配置
author:
name: icexmoon
age: 18
hobbies:
- music
- draw
- travel
我们打算将其值重新定义,但又不想直接修改这个 yml 文件(这么做的一个可能的理由是这个文件在代码版本库中)。那么我们就可以添加一个 /resources/config/application.yml 文件:
# 默认配置
author:
name: jackchen
age: 25
hobbies:
- music
- draw
- travel
现在运行程序并进行测试就能看到实际上是 /resources/config/application.yml 配置在生效:
jackchen 25 [music, draw, travel]
在项目被打成 jar 包后运行时,我们也可以用同目录下添加的配置文件来覆盖属性。
比如在生成的 jar 包所在的目录添加 application.yml:
# 默认配置
author:
name: BrusLee
age: 30
hobbies:
- music
- draw
- travel
运行程序并进行测试,输出:
BrusLee 30 [music, draw, travel]
利用这种特性,我们可以在部署 jar 包的机器上配置一些和环境相关的特殊配置信息。
比如为前端测试人员编写一个专用的配置文件,以让 jar 包可以运行在前端人员的电脑上。
与本地开发类似,jar 包所在的目录下同样可以添加一个 ./config/application.yml 配置文件,该配置文件的优先级是最高的,可以覆盖掉同目录下的配置文件。
总结一下,我们可以在四个地方存放配置文件:
-
项目的 /resources 目录
-
项目的 /resources/config 目录
-
jar 包所在的目录
-
jar 包所在目录的 ./config 目录
优先级是依次提高的。
我觉得这种“分层配置”功能最实用的一个应用是,我们可以在开发环境 jar 包所在的目录添加一个 yml:
spring:
profiles:
active: test
因为在工作中我遇到过几次因为打的 jar 包使用的默认配置不是环境对应的配置,或者在运行是没有添加 --spring.profiles.active=xxx
参数引发的莫名其妙的 bug。有了上面这个“本地配置文件”,就可以减少这种情况的出现概率。
The End,谢谢阅读。
本文的完整示例可以从获取。
文章评论