一、前言 {#一、前言}
在N年前整理过 Spring Boot 的入门教程,当时还是 1.x 的内容。如今 Spring Boot 已经升级到 3.x 版本,不过版本之间的使用差距不大,此次发布文章仅当作常规知识以及新版本功能的补充。
如果你已经掌握 Spring 和 SpringMVC 知识,但还不熟 Spring Boot 内容的读者,您可以尝试阅读本篇文章,如有不清楚的地方,可以留言评论,笔者看到自会补充说明。
二、快速入门 {#二、快速入门}
开发环境: jdk >= 17, maven >= 3.6.3
场景:简单实现 web 项目,浏览器发起请求,服务端做出响应
2.1 搭建项目 {#2.1-搭建项目}
- 使用IDEA创建一个空项目
- 打开 pom.xml 文件引入 Spring Boot 3 依赖
|------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!-- spring boot 项目最根本的依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.3</version> </parent> <dependencies> <!-- 实现 web 功能的依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
|
- 创建启动类
|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7
| @SpringBootApplication public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } }
|
- 创建控制器
|-------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8
| @RestController public class HelloController { @RequestMapping("/hello") public String home() { return "Hello World!"; } }
|
- 启动项目
在 MainAppliction 类中右键点击 Run 'MainAppliction' 即可启动。
- 测试
通过查看IDEA的控制台,我们可以知道启动的 web 项目默认使用 8080 端口,请求的的根路径为 /。
因此,我们使用浏览器访问 http://localhost:8080/hello 即可得到 Hello World 的响应。
至此,一个简单的 web 项目就搭建成功了。
2.2 常用注解 {#2.2-常用注解}
Spring Boot 摈弃的 XML 配置方式,改用全注解驱动。
为了方便看懂下文的案例,在这里提前介绍 Spring Boot 常用注解。
@SpringBootApplication:只能标记在一个类上,表示该类中声明的 Main 方法为项目启动入口
@ComponentScan:标记在类上,可以配置需要被 Spring 容器扫描的路径
@Configuration:标记在类上,表示该类为配置类
@Bean:标记在方法上,表示方法返回的对象会被 Spring 容器管理成为一个 bean,需要配合 @Configuration 使用
@Import:标记在类上,可以配置其类的 class,表示将其他类与当前配置类一起被 Spring 容器扫描和管理
@EnableAutoConfiguration:标记在类上,表示开启自动配置
@EnableConfigurationProperties:标记在类上,表示启动配置属性功能
@ConfigurationProperties:标记在类上,表示该类会根据绑定关系将配置文件的属性封装到类对象中,需要配合 @EnableConfigurationProperties 使用
@ConditionalOnClass:标记在类/方法上,如果类路径中存在这个类,则触发指定行为
@ConditionalOnMissingClass:标记在类/方法上,如果类路径中不存在这个类,则触发指定行为
@ConditionalOnBean:标记在类/方法上,如果容器中存在这个Bean(组件),则触发指定行为
@ConditionalOnMissingBean:标记在类/方法上,如果容器中不存在这个Bean(组件),则触发指定行为
2.3 组件注册 {#2.3-组件注册}
在传统的 Spring 项目中,如果需要注册组件,我们通常会在类上标记 @Service
,@Repository
,@Component
这类注解,然后配置需要扫描的类路径即可让 Spring 容器管理这些组件。
而在 Spring Boot 中同样可以上边的方式使用,不过已经不使用 XML 作为主配置文件来配置扫描路径,而是使用 @ComponentScan
来代替。
其中 @SpringBootApplication
作为一个组合注解,它底层就包含 @ComponentScan
注解,其默认扫描范围是被 @SpringBootApplication
标记的类路径以及其子路径。
换句话说,除了启动类会被 Spring 管理,启动类所在的路径和子路径都会被 Spring 扫描,被扫描到的类标记了 @Service
,@Repository
,@Component
这类注解都会被 Spring 容器管理。
然而实际开发中,我们并不单单自己实现功能,更多时候会使用第三方的依赖。将第三方库引入项目后,我们是没法在他们的类上标记注解让 Spring 管理。
因此 Spring 团队提供了一些注解来解决这类问题,以下提供两个方案实现对第三方组件的注册:
- 使用
@Configuration
和@Bean
注册
|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9
| @Configuration public class MyConfiguration { @Bean public UserService userService() { // 假设 UserService 为第三方库的 class return new UserService(); } }
|
- 使用
@Configuration
和@Import
注册
|---------------------|------------------------------------------------------------------------------------|
| 1 2 3 4 5 6
| @Configuration @Import(UserService.class) public class MyConfiguration { }
|
2.4 条件注册 {#2.4-条件注册}
通过 @ComponentScan
扫描包路径的类并创建和管理 Bean 非常简单,但是如果被扫描的类很多,维护的 Bean 就多,进而会影响项目的启动以及内存资源。
如果只希望个别的类被 Spring 容器管理,那么就可以采用条件注册方式声明 Bean。
场景:如果项目引入 druid 数据库连接池,那就注册 MyBatis 相关组件;如果没有引入,则注册 JDBC 组件
|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Configuration public class DaoConfiguration { @ConditionalOnClass(name="com.alibaba.druid.DruidRuntimeException") @Bean public MyBatisService myBatisService() { // 假设该类为 MyBatis 的相关类 return new MyBatisService(); } @ConditionalOnMissingClass(value="com.alibaba.druid.DruidRuntimeException") @Bean public JDBCService jdbcService() { // 假设该类为 JDBC 相关类 return new JDBCService(); } }
|
其中,com.alibaba.druid.DruidRuntimeException
为 druid 依赖中的一个异常类。如果系统加载到该类,那么 Spring 就会创建 MyBatisService ,否则创建 JDBCService。
同理,如果是根据是否存在某个 Bean 来判断注册哪个组件,我们可以使用 @ConditionalOnBean
或 @ConditionalOnMissingBean
。
三、配置文件 {#三、配置文件}
为了方便统一维护配置数据(应用名称、端口、数据库连接等),避免硬编码,Spring Boot 也需要额外的配置文件解决前边的问题。
3.1 文件格式 {#3.1-文件格式}
其中,固定名称的配置文件为 application.properties 或 application.yml
配置文件通常是放在项目中的 src/main/resources 目录中。
- 如果使用 .properties 格式的配置文件,数据内容格式如下:
|-----------------|----------------------------------------------------------------------------|
| 1 2 3 4
| #应用名称 spring.application.name=spring-boot-01 #端口号 server.port=9090
|
- 如果使用 .yml 格式的配置文件,数据内容格式如下:
|-----------------------|---------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7
| #应用名称 spring: application: name: spring-boot-01 #端口号 server: port: 9090
|
细节:两个配置文件可以同时存在,系统优先加载 yml 文件后加载 properties 文件,因此如果两种配置文件都定义相同的配置,那么 properties 文件中的配置会覆盖 yml 文件中的配置。
3.2 自定义配置 {#3.2-自定义配置}
其中上边的配置参数是 Spring Boot 给我们封装好的,其实我们可以在配置文件中自定义配置。
我们以 application.properties 文件为例:
|---------------|----------------------------------------------------|
| 1 2 3
| user.name=jack user.age=66 user.gender=man
|
自定义好配置后,我们可以使用三种方式获取配置:
- 使用 @Value 注解
|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @RestController public class CustomDataController { @Value("${user.name}") private String name; @Value("${user.age}") private Integer age; @Value("${user.gender}") private String gender; @RequestMapping("/customData") public String customData() { return name + "-" + age + "-" + gender; } }
|
- 使用 Environment API
|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @RestController public class CustomDataController { @Resource private Environment environment; @RequestMapping("/customData") public String customData() { String name = environment.getProperty("user.name"); String age = environment.getProperty("user.age"); String gender = environment.getProperty("user.gender"); return name + "-" + age + "-" + gender; } }
|
- 使用对象封装
|---------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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
| @Component @EnableConfigurationProperties @ConfigurationProperties(prefix = "user") public class UserProperties { private String name; private Integer age; private String gender; // setter 和 getter 省略 } @RestController public class CustomDataController { @Resource private UserProperties userProperties; @RequestMapping("/customData") public String customData() { String name = userProperties.getName(); Integer age = userProperties.getAge(); String gender = userProperties.getGender(); return name + "-" + age + "-" + gender; } }
|
其中,需要在 @ConfigurationProperties
中设置好配置的前缀,类中则声明配置.
最后的属性名称。
3.3 复杂配置 {#3.3-复杂配置}
如果封装的对象中定义复杂属性如下:
|---------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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 30 31 32 33 34 35 36 37
| @Component @EnableConfigurationProperties @ConfigurationProperties(prefix = "person") public class Person { // 姓名 private String name; // 年龄 private Integer age; // 爱好 private List<String> likes; // 住址 private List<Address> address; // 收入来源 private Map<String, Income> income; // 省略 setter 和 getter } public class Address { // 省份 private String province; // 城市 private String city; // 省略 setter 和 getter } public class Income { private String name; private Integer money; // 省略 setter 和 getter }
|
那么配置文件应该如下书写如下。
application.properties 文件格式:
person.name=张三
person.age=66
person.likes[0]=唱
person.likes[1]=跳
person.address[0].province=江苏省
person.address[0].city=苏州市
person.address[1].province=浙江省
person.address[1].city=杭州市
person.income.firstIncome.name=rap
person.income.firstIncome.money=2
person.income.secondIncome.name=篮球
person.income.secondIncome.money=2
其中, firstIncome 和 secondIncome 为自定义,表示 Map 的 key,或者可以理解为指向 Income 对象的引用名。
application.yml 文件格式:
person:
name: 张三
age: 66
likes: ['唱', '跳']
address:
- province: 江苏省
city: 苏州市
- province: 浙江省
city: 杭州市
income:
firstIncome:
name: rap
money: 2
secondIncome:
name: 篮球
money: 2
或者
person:
name: 张三
age: 66
likes[0]: 唱
likes[1]: 跳
address[0]:
province: 江苏省
city: 苏州市
address[1]:
province: 浙江省
city: 杭州市
income:
firstIncome:
name: rap
money: 2
secondIncome:
name: 篮球
money: 2
yml文件配置细节:
- 针对封装属性名为驼峰标识,建议 yml 文件中使用 - 拼接,如: registerDay 属性在配置文件中定义为 registery-day
- 配置文件中的属性值为字符串,可以直接书写,也可以加单引号或双引号,其中单引号不会转义,双引号则会
四、Profiles {#四、Profiles}
Spring Profiles 提供一种隔离配置的方式,使其仅在特定环境生效。
上文介绍了 application.properties 配置文件的用法,但在实际开发中我们会搭建开发、测试、生产环境,不同的环境配置各不相同,因此需要多个配置文件来维护参数。
4.1 指定环境 {#4.1-指定环境}
Spring Profiles 针对配置文件提供了 application-{profile}.properties 的文件命名规范来区分不同环境的配置。
比如我们在开发中创建四个配置文件,application.properties, application-dev.properties, application-testroperties, application-prod.properties。
其中,application.properties 用于设置公共配置,其他三个文件分别用于设置开发,测试,生产环境下的配置。
注意:profile 为任意名称,只要确保唯一即可。
这些文件可以共存,在项目启动过程中,application.properties 一定会被加载使用,而系统是如何知道加载剩余的哪个配置文件呢?
4.2 激活配置文件 {#4.2-激活配置文件}
Spring Boot 提供了两种方式:
- 方式一:
在 application.properties 中设置激活的环境配置
spring.profiles.active=dev
- 方式二:
在启动项目时设置命令参数:
java -jar your-application.jar --spring.profiles.active=dev
使用任意一种方式启动项目,都会加载 application.properties 和 application-dev.properties 两个文件。
如果两个配置文件中设置了相同的配置(如:端口号),那么 {profiles} 的配置会覆盖 application.properties 的配置。
- 效果图:
4.3 激活组件 {#4.3-激活组件}
上文的配置是告诉系统使用哪个环境的配置文件,如果我们希望不同的环境加载不同的组件是否可以实现呢?答案是肯定的。
Spring 提供了 @Profile 配合 @Component 或 @Configuration 使用可以实现不同环境的加载。
- 实战演练
组件类:
|-----------------------|-------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7
| public class Animal { private String name; public Animal(String name) { this.name = name; } }
|
|---------------------|--------------------------------------------------------------------------------------|
| 1 2 3 4 5 6
| public class Cat extends Animal { public Cat(String name) { super(name); } }
|
|---------------------|--------------------------------------------------------------------------------------|
| 1 2 3 4 5 6
| public class Dog extends Animal { public Dog(String name) { super(name); } }
|
配置类:
|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Configuration public class MyConfig { @Bean @Profile("dev") public Cat cat() { return new Cat("咪咪"); } @Bean @Profile("test") public Dog dog() { return new Dog("大黄"); } }
|
配置类中,我们希望当激活 dev 环境时创建 Cat 组件,当激活 test 环境时创建 Dog 组件。
- 效果图:
五、日志 {#五、日志}
Spring 底层自己定义了 commons-logging 作为内部日志接口,其支持 jul ,Log4j , Log4j2 ,Logback 等实现。
而 Spring Boot 3 已不再支持 Log4j ,默认使用 Logback 作为日志实现方案。
默认情况下,日志会按照一定的格式输出到控制台中展示。如果想要修改日志输出格式或输出位置,那么需要修改日志配置。
5.1 常见配置 {#5.1-常见配置}
#控制台日志输出格式
logging.pattern.console=
#日志文件路径
logging.file.path=
#日志文件名(路径+文件名),如果不带路径,最终会在项目的同级目录中创建对应名称的日志
logging.file.name=
#打印sql相关日志
logging.level.sql=
#打印web相关日志
logging.level.web=
#打印 com.light.boot 包路径下的日志,该路径为项目中的路径
logging.level.com.light.boot=
#logback日志存档的文件名格式,默认值为 ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz
logging.logback.rollingpolicy.file-name-pattern=
#logback日志存档前,每个日志文件的最大大小,默认值为 10M
logging.logback.rollingpolicy.max-file-size=
#logback日志文件被删除之前,可以容纳的最大大小,默认值为 0B
logging.logback.rollingpolicy.total-size-cap=
`#logback日志文件保存的最大天数,默认值为 7 天
logging.logback.rollingpolicy.max-history=
`
其中,logging.level.sql
和 logging.level.web=
由 SpringBoot 封装。
logging.level.sql
会打印如下路径中的日志:
org.springframework.jdbc.core,
org.hibernate.SQL,
org.jooq.tools.LoggerListener
logging.level.web=
会打印如下路径中的日志:
org.springframework.core.codec,
org.springframework.http,
org.springframework.web,
org.springframework.boot.actuate.endpoint.web,
org.springframework.boot.web.servlet.ServletContextInitializerBeans
通过上边的配置可以很好的对项目中想要的日志进行打印输出。
当然如果你对外部的日志文件配置更加熟悉,我们也可以整合使用,而且实际项目中更多时候也是使用外部的日志文件配置。
如果你使用 Logback 作为日志文件实现方案,只需在 src/main/resources 目录下创建名为 logback-spring.xml 文件,然后配置该日志相关配置即可。
如果你使用 Log4j2 作为日志文件实现方案,需要完成 2 个步骤:
- 添加依赖
|------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
|
- 在 src/main/resources 目录下创建名为 log4j2-spring.xml 文件,然后配置该日志相关配置即可。
5.2 打印日志 {#5.2-打印日志}
配制好日志相关参数后,我们只需要在代码中创建日志对象,根据需求打印即可。
|------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @RestController public class LogController { private Log log = LogFactory.getLog(LogController.class); @RequestMapping("/logging") public String logging() { log.debug("=====这是 debug 日志======"); log.info("=====这是 info 日志======"); log.warn("=====这是 warn 日志======"); log.error("=====这是 error 日志======"); return "打印成功"; } }
|
由于 SpringBoot3 默认使用 INFO 日志级别,因此最终结果只会打印 INFO,WARN,ERROR 级别的日志。
5.3 日志级别 {#5.3-日志级别}
日志级别由低到高分别是:ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF;
ALL:打印所有日志
TRACE:追踪框架详细流程日志,一般不使用
DEBUG:开发调试细节日志
INFO:关键、感兴趣信息日志
WARN:警告但不是错误的信息日志,比如:版本过时
ERROR:业务错误日志,比如出现各种异常
FATAL:致命错误日志,比如jvm系统崩溃
OFF:关闭所有日志记录
日志级别需要配合 logging.level
使用。如果使用外部日志文件,则需要在其文件中配置。
比如我们想打印项目中 service 层的代码日志,包名为 com.light.boot.service:
logging.level.com.light.boot.service=INFO
这样配置即可实现打印 INFO 级别的效果。
补充:如果设置使用某个级别日志,那么最终会打印该级别以及它之后更高级别的日志。
比如,使用 INFO 级别,最终打印 INFO, WARN, ERROR 级别日志。使用 WARN 级别,最终打印 WARN, ERROR 级别日志。
由于 Spring Boot 只是整合通用的日志接口,而具体的日志实现由对应的厂商完成,因此如果想要了解更多日志内容以及配置内容,请自行到对应的官网查阅内容。
六、打包部署 {#六、打包部署}
项目功能开发完成就需要打包部署到服务器上运行,而 Spring Boot 也提供了自己的打包插件。
在 pom.xml 文件中引入:
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8
| <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
|
使用IDEA右侧的Maven功能项或者在终端手动命令 mvn clean pacakge
即可打出一个可执行的 jar 包。
如果想启动项目,则使用 java -jar xxx.jar
即可启动。
当然,在实际项目中启动 jar 包还需要更多的参数(jvm 堆栈、配置文件的选择等),具体内容自行网上查阅。