一、前言

在N年前整理过 Spring Boot 的入门教程,当时还是 1.x 的内容。如今 Spring Boot 已经升级到 3.x 版本,不过版本之间的使用差距不大,此次发布文章仅当作常规知识以及新版本功能的补充。

如果你已经掌握 SpringSpringMVC 知识,但还不熟 Spring Boot 内容的读者,您可以尝试阅读本篇文章,如有不清楚的地方,可以留言评论,笔者看到自会补充说明。

二、快速入门

开发环境: jdk >= 17, maven >= 3.6.3

场景:简单实现 web 项目,浏览器发起请求,服务端做出响应

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 常用注解

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 组件注册

在传统的 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 条件注册

通过 @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 文件格式

其中,固定名称的配置文件为 application.propertiesapplication.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 自定义配置

其中上边的配置参数是 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 复杂配置

如果封装的对象中定义复杂属性如下:

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

其中, firstIncomesecondIncome 为自定义,表示 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
  • 配置文件中的属性值为字符串,可以直接书写,也可以加单引号或双引号,其中单引号不会转义,双引号则会

四、日志

Spring 底层自己定义了 commons-logging 作为内部日志接口,其支持 julLog4jLog4j2Logback 等实现。

Spring Boot 3 已不再支持 Log4j,默认使用 Logback 作为日志实现方案。

默认情况下,日志会按照一定的格式输出到控制台中展示。如果想要修改日志输出格式或输出位置,那么需要修改日志配置。

4.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.sqllogging.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 文件,然后配置该日志相关配置即可。

4.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 级别的日志。

4.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 堆栈、配置文件的选择等),具体内容自行网上查阅。

六、参考资料

Spring Boot 官网logback 官网log4j2官网