SpringBoot3-快速入门
# 前置
注意
这里以SpringBoot3为例,SpringBoot2大部分也是通用的
# 前置条件
- Java17
- Spring、SpringMVC
- Maven、Gradle
# 前置知识
首先学习 Spring 相关的知识 Spring (opens new window)、SpringMVC (opens new window)。
不学上面的内容也可以上手 SpringBoot,但是 SpringBoot 的核心知识离不开 Spring 和 SpringMVC,所以还是推荐看看。
# 项目创建
两种创建方式
- 创建一个普通的 Maven 工程,然后增加
<parent>
标签,然后导入 web、test 等需要的依赖。 - 通过 Spring Initializr (opens new window) 创建(如果使用了 IDEA 开发工具也可以在创建项目时选择 Spring Initializr)。
# 自动装配
通过下面的代码可以查看 SpringBoot 在启动的时候加载了多少个类。
public static void main(String[] args) {
//var:java10 局部变量类型的自动推断
var ioc = SpringApplication.run(MainApplication.class, args);
//1、获取容器中所有组件的名字
String[] names = ioc.getBeanDefinitionNames();
//2、挨个遍历:
// dispatcherServlet、beanNameViewResolver、characterEncodingFilter、multipartResolver
// SpringBoot把以前配置的核心组件现在都给我们自动配置好了。
for (String name : names) {
System.out.println(name);
}
}
2
3
4
5
6
7
8
9
10
11
12
# 默认扫描规则
@SpringBootApplication
标注的类就是主程序类。SpringBoot 只会扫描主程序所在的包及其下面的子包,自动的 component-scan 功能
自定义扫描路径
- @SpringBootApplication(scanBasePackages = "com.dfd")
@ComponentScan("com.dfd")
直接指定扫描的路径
# 配置默认值
配置文件的所有配置项是和某个类的对象值进行一一绑定的。
绑定了配置文件中每一项值的类:属性类。简单来说就是能够在 application.properties 或者 application.yml 中配置的属性。
比如:
ServerProperties
绑定了所有 Tomcat 服务器有关的配置MultipartProperties
绑定了所有文件上传相关的配置
参照官方文档 (opens new window):或者参照绑定的属性类。
# 按需加载自动配置
场景启动器除了会导入相关功能依赖,导入一个 spring-boot-starter
,所有的 starter 后缀包都会引入这个依赖。
spring-boot-starter
导入了一个包 spring-boot-autoconfigure
。包里面都是各种场景的 AutoConfiguration
自动配置类
虽然全场景的自动配置都在 spring-boot-autoconfigure
这个包,但是不是全都开启的。具体的逻辑是:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
// 这段代码中会加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports所有的配置项,然后过滤掉没有引用到的配置
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations); //去重
Set<String> exclusions = getExclusions(annotationMetadata, attributes);// 获取排除项
checkExcludedClasses(configurations, exclusions);
// 如果在启动类中配置了排除项,在这里也会将写的排除类移除
configurations.removeAll(exclusions);
// 这里会过滤掉所有未使用到的配置
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
// 这个方法会根据@ConditionalOnxxx注解中的配置来过滤
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
for (AutoConfigurationImportFilter filter : this.filters) {
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
if (logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return result;
}
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
每一个自动配置类,都有条件注解 @ConditionalOnxxx
,只有条件成立,才能生效。
# @SpringBootApplication
点开这个注解后可以看到这个注解的详情
//指定这个注解只能应用于类型(类、接口、枚举)。
@Target(ElementType.TYPE)
//指定这个注解在运行时保留,可以通过反射来访问。
@Retention(RetentionPolicy.RUNTIME)
//表示这个注解应该被 Javadoc 工具记录。
@Documented
//允许子类继承父类的注解。
@Inherited
//指示一个类提供 Spring Boot 应用程序的配置。这是 @Configuration 的特殊形式。
@SpringBootConfiguration
//启用 Spring Boot 的自动配置机制(核心)。它会根据类路径中的 jar 依赖为项目自动配置 Spring 应用程序。
@EnableAutoConfiguration
//自动扫描并注册被 @Component、@Service、@Repository、@Controller 等注解修饰的 Bean。
///这里定义了两个排除过滤器:
////TypeExcludeFilter:用于排除特定类型的 Bean。
////AutoConfigurationExcludeFilter:用于排除自动配置类。
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# XXXAutoConfiguration
在 autoconfigure
下可以看到很多的包下都以 AutoConfiguration 结尾的类,类中基本都使用了 @EnableConfigurationProperties(ServerProperties.class)
这样的注解,这是用来把配置文件中配的指定前缀的属性值封装到 xxxProperties
属性类中,这些都可以在 application.properties
中进行配置。
# 核心组件
# 组件注册
@Configuration、@SpringBootConfiguration
@Bean、@Scope
@Controller、 @Service、@Repository、@Component
@Import
@ComponentScan
步骤:
@Configuration 编写一个配置类。
在配置类中,自定义方法给容器中注册组件。配合 @Bean。
或使用 @Import 导入第三方的组件。
# 条件注解
笔记
如果注解指定的条件成立,则触发指定行为。
# @ConditionalOnXxx
- @ConditionalOnClass:如果类路径中存在这个类,则触发指定行为。
- @ConditionalOnMissingClass:如果类路径中不存在这个类,则触发指定行为。
- @ConditionalOnBean:如果容器中存在这个 Bean(组件),则触发指定行为。
- @ConditionalOnMissingBean:如果容器中不存在这个 Bean(组件),则触发指定行为。
/**
* 来测试不同的@ConditionalOnBean,如果项目中存在一个StringRedis的Bean组件,那么就加载redisTemplate1否则就加载redisTemplate2
* Redis配置类
* @author Fengdong.Duan
* @create 2024/6/25 下午1:43
*/
@Configuration
public class RedisConfig {
@Bean
@ConditionalOnBean(name = "StringRedis")
public RedisTemplate<String, Object> redisTemplate1(RedisConnectionFactory factory) {
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置 RedisConnection 工厂。它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友,可以自己去撸下。
template.setConnectionFactory(factory);
// 使用 String 序列化方式,序列化 KEY 。
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
template.setValueSerializer(buildRedisSerializer());
template.setHashValueSerializer(buildRedisSerializer());
return template;
}
@Bean
@ConditionalOnMissingBean(name = "StringRedis")
public RedisTemplate<String, Object> redisTemplate2(RedisConnectionFactory factory) {
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置 RedisConnection 工厂。它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友,可以自己去撸下。
template.setConnectionFactory(factory);
return template;
}
private RedisSerializer<?> buildRedisSerializer() {
RedisSerializer<Object> json = RedisSerializer.json();
// 解决 LocalDateTime 的序列化
ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
objectMapper.registerModules(new JavaTimeModule());
return json;
}
}
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
38
39
40
41
42
43
44
45
46
47
# 属性绑定
# @ConfigurationProperties
声明组件的属性和配置文件哪些前缀开始项进行绑定。
将容器中任意组件(Bean!!!!!)的属性值和配置文件的配置项的值进行绑定
- 给容器中注册组件(@Component、@Bean)。
- 使用 @ConfigurationProperties 声明组件和配置文件的哪些配置项进行绑定。
## 在application.yml中配置相关属性值
user:
id: 1
name: 东东
age: 18
2
3
4
5
@Data
public class User {
private Long id;
private String name;
private int age;
}
@Configuration
public class AppConfig {
@Bean//或者在User类上注明@Component
@ConfigurationProperties(prefix = "user")//标注在User类上也可
public User user() {
return new User();
}
}
// 在IOC容器中就可以看到包含了自定义属性值的User对象信息了
var ioc = SpringApplication.run(MainApplication.class, args);
User user = ioc.getBean(User.class);
System.out.println(user);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# @EnableConfigurationProperties
快速注册注解。
** 场景:**SpringBoot 默认只扫描自己主程序所在的包。如果导入第三方包,即使组件上标注了 @Component、@ConfigurationProperties 注解也没用。因为组件都扫描不进来,此时使用这个注解就可以快速进行属性绑定并把组件注册进容器。
@Data
@ConfigurationProperties(prefix = "user") //如果User没有放入到Bean容器中这里是会有报错提示的
public class User {
private Long id;
private String name;
private int age;
}
@Configuration
@EnableConfigurationProperties(User.class) // 自动绑定到User对象上
public class AppConfig {
}
2
3
4
5
6
7
8
9
10
11
12
# 复杂条件
@Component
@ConfigurationProperties(prefix = "person")
@Data
public class Person {
private String name;
private Integer age;
private Date birthDay;
private Boolean like;
private Child child; //嵌套对象
private List<Dog> dogs; //数组(里面是对象)
private Map<String,Cat> cats; //表示Map
}
@Data
public class Dog {
private String name;
private Integer age;
}
@Data
public class Child {
private String name;
private Integer age;
private Date birthDay;
private List<String> text; //数组
}
@Data
public class Cat {
private String name;
private Integer age;
}
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
person:
name: 张三
age: 18
birthDay: 2010/10/10 12:12:12
like: true
child:
name: 李四
age: 20
birthDay: 2018/10/10
text: ["abc","def"]
dogs:
- name: 小黑
age: 3
- name: 小白
age: 2
cats:
c1:
name: 小蓝
age: 3
c2: {name: 小绿,age: 2} #对象也可用{}表示
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
提供一种 properties 格式的写法
person.name=张三
person.age=18
person.birthDay=2010/10/12 12:12:12
person.like=true
person.child.name=李四
person.child.age=12
person.child.birthDay=2018/10/12
person.child.text[0]=abc
person.child.text[1]=def
person.dogs[0].name=小黑
person.dogs[0].age=3
person.dogs[1].name=小白
person.dogs[1].age=2
person.cats.c1.name=小蓝
person.cats.c1.age=3
person.cats.c2.name=小灰
person.cats.c2.age=2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 细节
- birthDay 推荐写为 birth-day
点击查看
- 命名约定:在配置文件中,通常推荐使用 > 命名约定:在配置文件中,通常推荐使用 kebab-case(短横线命名法)。这种命名方式被认为更易读,特别是对于长的属性名。
- 一致性:使用 kebab-case 可以保持配置文件中命名风格的一致性。Spring Boot 的许多内置属性也使用这种风格(例如
server.servlet.context-path
)。 - 松散绑定:Spring Boot 支持松散绑定(relaxed binding),这意味着无论你在配置文件中使用
birth-day
、birthDay
还是birth_day
,它都能正确地映射到 Java 类中的birthDay
属性。但是,为了保持一致性和可读性,推荐使用 kebab-case。 - 避免歧义:在某些情况下,camelCase 可能会导致歧义。例如,
userID
和userId
在某些系统中可能被视为不同的属性。使用user-id
可以避免这种潜在的问题。 - 工具支持:一些 YAML 处理工具和编辑器对 kebab-case 的支持更好,可能会提供更好的自动完成和验证功能。
- 国际化考虑: 对于非英语母语的开发者来说,kebab-case 可能更容易理解和记忆。
user:
birth-day: 1990-01-01
first-name: John
last-name: Doe
2
3
4
- 文本
- 单引号不会转义【\n 则为普通字符串显示】。
- 双引号会转义【\n 会显示为换行符】。
message:
single-quote: 'This is a single-quoted string.\nThis will not create a new line.' ### 这个会显示一行
double-quote: "This is a double-quoted string.\nThis will create a new line." ### 这个会显示两行
2
3
- 大文本
|
开头,大文本写在下层,保留文本格式,换行符正确显示。>
开头,大文本写在下层,折叠换行符。
preserve-format: |
This is a multi-line text.
It will preserve line breaks and formatting.
Each line will appear as written here.
fold-newlines: >
This is also a multi-line text.
However, it will fold newlines.
This entire block will appear as a single line,
with spaces where line breaks were.
2
3
4
5
6
7
8
9
10
- 多文档合并
- 使用
---
可以把多个 yaml 文档合并在一个文档中,每个文档区依然认为内容独立。
---
## 文档 1
spring:
profiles: development
server:
port: 8080
---
## 文档 2
spring:
profiles: production
server:
port: 80
---
## 文档 3
logging:
level:
root: INFO
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 日志配置
Spring 使用 commons-logging 作为内部日志,但底层日志实现是开放的。可对接其他日志框架。Spring 5 及以后 commons-logging 被 Spring 直接自己写了。
支持 jul
, log4j2
, logback
(默认)。SpringBoot 提供了默认的控制台输出配置,也可以配置输出为文件。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
2
3
4
# 默认配置
默认使用了 logback + slf4j
组合作为默认底层日志,Spring Boot 使用 LoggingApplicationListener 来配置日志系统。这个监听器在 SpringApplication 启动过程中的早期阶段就会被触发。所以,它不遵循标准的自动配置模式。
默认的日志输出格式
2024-06-25T16:41:59.774+08:00 INFO 13500 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8881 (http) with context path ''
2024-06-25T16:41:59.786+08:00 INFO 13500 --- [ main] com.dfd.spring.demo.MainApplication : Started MainApplication in 3.273 seconds (process running for 4.31)
2
从前到后的顺序是:
- 时间和日期:毫秒级精度。
- 日志级别:ERROR, WARN, INFO, DEBUG, or TRACE。
- 进程 ID。
- ---:消息分割符。
- 线程名:使用 [] 包含。
- Logger 名:通常是产生日志的类名。
- 消息:日志记录的内容。
logback 没有 FATAL 级别,对应的是 ERROR。
通过
logging.pattern.console
属性可以修改日志输出格式。
# 日志级别
由低到高: ALL,TRACE, DEBUG, INFO, WARN, ERROR,FATAL,OFF
;
只会打印指定级别及以上级别的日志,SpringBoot 的默认级别是 INFO (这就意味着,INFO 之上级别是无法输出的),这个可以通 logging.level.root
来修改,root 指的是所有类,如果需要指定个别的包下的类或者是个别类,可以通过 logging.level.包名/类名
来修改。
- ALL:打印所有日志
- TRACE:追踪框架详细流程日志,一般不使用
- DEBUG:开发调试细节日志
- INFO:关键、感兴趣信息日志
- WARN:警告但不是错误的信息日志,比如:版本过时
- ERROR:业务错误日志,比如出现各种异常
- FATAL:致命错误日志,比如 jvm 系统崩溃
- OFF:关闭所有日志记录
# 日志分组
将相关的 logger 分组在一起,统一配置,SpringBoot 也支持。
logging:
group:
component: ### 组名
- com.dfd.spring.demo.componentDEMO
config:
- com.dfd.spring.demo.config
level:
component: debug ### 对不同的组进行级别配置
config: info
2
3
4
5
6
7
8
9
## 提供一个properties格式的参考
logging.group.component=com.dfd.spring.demo.componentDEMO,com.dfd.spring.demo.config
logging.level.component=debug
2
3
SpringBoot 预定义的两个组
Name | Loggers |
---|---|
web | org.springframework.core.codec org.springframework.http org.springframework.web org.springframework.boot.actuate.endpoint.web org.springframework.boot.web.servlet.ServletContextInitializerBeans |
sql | org.springframework.jdbc.core org.hibernate.SQL org.jooq.tools.LoggerListener |
# 文件输出
SpringBoot 默认只把日志写在控制台,如果想额外记录到文件,可以在 application.yml 中添加 logging.file.name or logging.file.path 配置项。
logging.file.name | logging.file.path | 示例 | 效果 |
---|---|---|---|
未指定 | 未指定 | 仅控制台输出 | |
指定 | 未指定 | my.log | 写入指定文件。可以加路径 |
未指定 | 指定 | /var/log | 写入指定目录,文件名为 spring.log |
指定 | 指定 | 以 logging.file.name 为准 |
# 文件归档与滚动切割
归档:每天的日志单独存到一个文档中。
切割:每个文件 10MB,超过大小切割成另外一个文件。
- 每天的日志应该独立分割出来存档。如果使用 logback(SpringBoot 默认整合),可以通过 application.properties/yaml 文件指定日志滚动规则。
- 如果是其他日志系统,需要自行配置(添加 log4j2.xml 或 log4j2-spring.xml)。
- 支持的滚动规则设置如下。
配置项 | 描述 |
---|---|
logging.logback.rollingpolicy.file-name-pattern | 日志存档的文件名格式(默认值:${LOG_FILE}.% d {yyyy-MM-dd}.% i.gz) |
logging.logback.rollingpolicy.clean-history-on-start | 应用启动时是否清除以前存档(默认值:false) |
logging.logback.rollingpolicy.max-file-size | 存档前,每个日志文件的最大大小(默认值:10MB) |
logging.logback.rollingpolicy.total-size-cap | 日志文件被删除之前,可以容纳的最大大小(默认值:0B)。设置 1GB 则磁盘存储超过 1GB 日志后就会删除旧日志文件 |
logging.logback.rollingpolicy.max-history | 日志文件保存的最大天数 (默认值:7) |
# 自定义配置
通常我们配置 application.properties 就够了。当然也可以自定义。比如:
日志系统 | 自定义 |
---|---|
Logback | logback-spring.xml、logback-spring.groovy、logback.xml、logback.groovy |
Log4j2 | log4j2-spring.xml、log4j2.xml |
JDK (Java Util Logging) | logging.properties |
如果是 SpringBoot 环境下,请使用 -spring
变量(例如, logback-spring.xml
而不是 logback.xml
)。具体原因如下:
点击查看
在Spring Boot中,使用logback-spring.xml而不是logback.xml作为Logback配置文件有几个重要原因和区别:
- 加载时机:
logback.xml
:在应用程序上下文加载之前由Logback框架直接加载。logback-spring.xml
:由Spring Boot加载,在Spring环境和上下文创建之后进行处理。
- Spring Boot特性支持:
logback.xml
:不支持Spring Boot的配置特性。logback-spring.xml
:可以使用Spring Boot的特性,如配置文件占位符和Profile-specific配置。
- 自定义启动行为:
logback-spring.xml
允许你自定义Spring Boot的日志启动行为。
- 条件处理:
logback-spring.xml
支持使用Spring Boot的@Profile
注解来根据不同的环境进行条件配置。
- 扩展性:
logback-spring.xml
提供了更好的扩展性,可以与Spring Boot的其他配置更好地集成。
- 错误处理:
- 使用
logback-spring.xml
,如果配置有误,Spring Boot可以提供更友好的错误信息。
- 使用
# 切换日志组合
<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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
log4j2 支持 yaml 和 json 格式的配置文件
格式 | 依赖 | 文件名 |
---|---|---|
YAML | com.fasterxml.jackson.core:jackson-databind + com.fasterxml.jackson.dataformat:jackson-dataformat-yaml | log4j2.yaml + log4j2.yml |
JSON | com.fasterxml.jackson.core:jackson-databind | log4j2.json + log4j2.jsn |
# 最佳实战
- 导入任何第三方框架,先排除它的日志包,因为 Boot 底层控制好了日志。
- 修改
application.properties
配置文件,就可以调整日志的所有行为。如果不够,可以编写日志框架自己的配置文件放在类路径下就行,比如logback-spring.xml
,log4j2-spring.xml
。 - 如需对接专业日志系统,也只需要把 logback 记录的日志灌倒 kafka 之类的中间件,这和 SpringBoot 没关系,都是日志框架自己的配置,修改配置文件即可。