SpringBoot笔记

Spring Boot使用slf4j进行日志记录

在开发中,我们经常使用 System.out.println() 来打印一些信息,但是这样不好,因为大量的使用 System.out 会增加资源的消耗。我们实际项目中使用的是 slf4j 的 logback 来输出日志,效率挺高的,Spring Boot 提供了一套日志系统,logback 是最优的选择。

1.简介
官网:https://www.slf4j.org/manual.html
(1)简单日记门面(simple logging Facade for Java)SLF4J是为各种loging APIs提供一个简单统一的接口。

(2)slf4j并不是一种具体的日志系统,而是一个用户日志系统的facade。

(3)在部署时,选择不同的日志系统包,即可自动转换到不同的日志系统上。
如:选择JDK自带的日志系统,则只需要将slf4j-api-XXX.jar和slf4j-jdkXXX.jar放置到classpath中即可,若想换成log4j的日志系统,仅需要用slf4j-log4jXXx.jar替换slf4j-jdkXXX.jar即可

(4)slf4j和log4j比较:
①log4j看成是一个完整的日志库;而slf4j是一个日志库的规范接口,可以根据不同的日志包使用不同的日志库。
②日志中需要传入参数时,log4j一般是使用字符串进行拼接的方式;
slf4j使用占位符,比字符串拼接更加高效。如logger.error(“sql为 {} “,sql)。

(5)slf4j日志级别
Slf4j日志级别,级别由低到高,设置的级别约低,打印的日志越多
①trace: 一般不会使用,在日志里边也不会打印出来,最低的一个日志级别。
②debug: 一般放于程序的某个关键点的地方,用于打印一个变量值或者一个方法返回的信息之类的信息
③info 一般处理业务逻辑的时候使用,就跟 system.err打印一样,用于说明此处是干什么的。
④warn:警告,不会影响程序的运行,但是值得注意。
⑤error: 用户程序报错,必须解决的时候使用此级别打印日志。

2.使用教程

官网上最新稳定版是1.7.32,前阵子log4j出重大漏洞了,这里我们配合logback(log4j的改良版,性能更好)使用。
(1)在pom.xml引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!--slf4j依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<!-- logback 依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

(2)在springBoot项目中添加一段controller代码

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.springtest.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class VisualController {

private Logger logger = LoggerFactory.getLogger(VisualController.class);

@RequestMapping(value="/test")
@ResponseBody
public String test(){
String msg = "fucking good";
logger.info("slf4j print info msg:{}",msg);
logger.debug("slf4j print debug msg:{}",msg);
return msg;
}

}

这里通过http://localhost:8080/test访问可以看到控制台输出如下,可以看到info的输出了,debug没有,因为springboot内部集成了slf4j,默认是info级别:

(3)只在控制台打印,application.yml文件配置如下:

1
2
3
4
5
6
7
# slf4j日志配置
logging:
# 配置级别
level:
root: info
#分包配置级别,即不同的目录下可以使用不同的级别
com.example.springtest.controller: debug

改完之后发现输出如下:

在resource下新建logback.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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<!--info日志文件-->
<appender name="INFO_FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件相对路径和名称-->
<file>./apilogs/info/xxxx_-${HOSTNAME}.log</file>
<append>true</append>
<!--根据日志文件的大小来滚动(即创建新的)日志文件-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./apilogs/info/archive/xxxx_-${HOSTNAME}-%d.%i.log</fileNamePattern>
<!--只保留最近30天的日志-->
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!--超过10MB便会滚动日志文件-->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 配置日志的输出格式 -->
<encoder>
<pattern>%date %level [%thread] %logger{10} %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!--异步处理-->
<appender name="ASYNC_INFO_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<appender-ref ref="INFO_FILE" />
</appender>

<!--debug日志文件-->
<appender name="DEBUG_FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件相对路径和名称-->
<file>./apilogs/debug/xxxx_-${HOSTNAME}.log</file>
<append>true</append>
<!--根据日志文件的大小来滚动(即创建新的)日志文件-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./apilogs/debug/archive/xxxx_-${HOSTNAME}-%d.%i.log</fileNamePattern>
<!--只保留最近30天的日志-->
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!--超过10MB便会滚动日志文件-->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 配置日志的输出格式 -->
<encoder>
<pattern>%date %level [%thread] %logger{10} %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!--异步处理-->
<appender name="ASYNC_DEBUG_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<appender-ref ref="DEBUG_FILE" />
</appender>

<!--控制台输出-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--输出格式-->
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!--异步处理-->
<appender name="ASYNC-STDOUT" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<appender-ref ref="STDOUT" />
</appender>

<!--在info级别下,将包名所在org.springframework的日志全部打印到控制台-->
<!--additivity被设置为'false',这意味着日志事件不会传递给父级记录器-->
<logger name="org.springframework" level="info" additivity="false">
<appender-ref ref="ASYNC-STDOUT" />
</logger>

<!--在debug级别下,会打印记录sql的相关日志,将包名为org.apache.ibatis下的日志输出到debug日志文件-->
<logger name="org.apache.ibatis" level="debug" additivity="false">
<appender-ref ref="ASYNC_DEBUG_FILE" />
</logger>

<!--所有业务相关的info日志,打印到控制台,并输出到info日志文件-->
<root level="info">
<appender-ref ref="ASYNC-STDOUT" />
<appender-ref ref="ASYNC_INFO_FILE" />
</root>
</configuration>

可以看到控制台和日志文件都成功输出了:

3.常见报错解决(持续更新)

(1)yml配置报错如:
“Failed to bind properties under ‘logging.level’”
可能跟版本有关,参考yml配置上面的使用教程的第(3)步

(2)启动时报错:
“class path contains multiple slf4j bindings
found binding in …”
不要忽视这种警告,若有多个jar包类冲突,需要把加载jar包顺序调整,正确的那个,比如slf4j-log4j12jar包放在最上面。


Spring Boot中的MVC支持

Spring Boot 的 MVC 支持主要来介绍实际项目中最常用的几个注解,包括 @RestController@RequestMapping@PathVariable@RequestParam 以及 @RequestBody

1. @RestController

@RestController 是 Spring Boot 新增的一个注解,我们看一下该注解都包含了哪些东西。

1
2
3
4
5
6
7
8
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
String value() default "";
}

可以看出, @RestController 注解包含了原来的 @Controller@ResponseBody 注解,使用过 Spring 的朋友对 @Controller 注解已经非常了解了,这里不再赘述, @ResponseBody 注解是将返回的数据结构转换为 Json 格式。所以 @RestController 可以看作是 @Controller@ResponseBody 的结合体,相当于偷个懒,我们使用 @RestController 之后就不用再使用 @Controller 了。但是需要注意一个问题:如果是前后端分离,不用模板渲染的话,比如 Thymeleaf,这种情况下是可以直接使用@RestController 将数据以 json 格式传给前端,前端拿到之后解析;但如果不是前后端分离,需要使用模板来渲染的话,一般 Controller 中都会返回到具体的页面,那么此时就不能使用@RestController了,比如:

1
2
3
public String getUser() {
return "user";
}

其实是需要返回到 user.html 页面的,如果使用 @RestController 的话,会将 user 作为字符串返回的,所以这时候我们需要使用 @Controller 注解。这在下一节 Spring Boot 集成 Thymeleaf 模板引擎中会再说明。

2. @RequestMapping

@RequestMapping 是一个用来处理请求地址映射的注解,它可以用于类上,也可以用于方法上。在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上,表示类中的所有响应请求的方法都是以该地址作为父路径;在方法的级别表示进一步指定到处理方法的映射关系。

该注解有6个属性,一般在项目中比较常用的有三个属性:value、method 和 produces。

  • value 属性:指定请求的实际地址,value 可以省略不写

  • method 属性:指定请求的类型,主要有 GET、PUT、POST、DELETE,默认为 GET

  • produces属性:指定返回内容类型,如 produces = “application/json; charset=UTF-8”

@RequestMapping 注解比较简单,举个例子:

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping(value = "/test", produces = "application/json; charset=UTF-8")
public class TestController {

@RequestMapping(value = "/get", method = RequestMethod.GET)
public String testGet() {
return "success";
}
}

这个很简单,启动项目在浏览器中输入 localhost:8080/test/get 测试一下即可。

针对四种不同的请求方式,是有相应注解的,不用每次在 @RequestMapping 注解中加 method 属性来指定,上面的 GET 方式请求可以直接使用 @GetMapping("/get") 注解,效果一样。相应地,PUT 方式、POST 方式和 DELETE 方式对应的注解分别为 @PutMapping@PostMappingDeleteMapping

3. @PathVariable

@PathVariable 注解主要是用来获取 url 参数,Spring Boot 支持 restfull 风格的 url,比如一个 GET 请求携带一个参数 id 过来,我们将 id 作为参数接收,可以使用 @PathVariable 注解。如下:

1
2
3
4
5
@GetMapping("/user/{id}")
public String testPathVariable(@PathVariable Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}

这里需要注意一个问题,如果想要 url 中占位符中的 id 值直接赋值到参数 id 中,需要保证 url 中的参数和方法接收参数一致,否则就无法接收。如果不一致的话,其实也可以解决,需要用 @PathVariable 中的 value 属性来指定对应关系。如下:

1
2
3
4
5
@RequestMapping("/user/{idd}")
public String testPathVariable(@PathVariable(value = "idd") Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}

对于访问的 url,占位符的位置可以在任何位置,不一定非要在最后,比如这样也行:/xxx/{id}/user。另外,url 也支持多个占位符,方法参数使用同样数量的参数来接收,原理和一个参数是一样的,例如:

1
2
3
4
5
6
@GetMapping("/user/{idd}/{name}")
public String testPathVariable(@PathVariable(value = "idd") Integer id, @PathVariable String name) {
System.out.println("获取到的id为:" + id);
System.out.println("获取到的name为:" + name);
return "success";
}

运行项目,在浏览器中请求 localhost:8080/test/user/2/zhangsan 可以看到控制台输出如下信息:

1
2
获取到的id为:2
获取到的name为:zhangsan

所以支持多个参数的接收。同样地,如果 url 中的参数和方法中的参数名称不同的话,也需要使用 value 属性来绑定两个参数。

4. @RequestParam

@RequestParam 注解顾名思义,也是获取请求参数的,上面我们介绍了 @PathValiable 注解也是获取请求参数的,那么 @RequestParam@PathVariable 有什么不同呢?主要区别在于: @PathValiable 是从 url 模板中获取参数值, 即这种风格的 url:http://localhost:8080/user/{id} ;而 @RequestParam 是从 request 里面获取参数值,即这种风格的 url:http://localhost:8080/user?id=1 。我们使用该 url 带上参数 id 来测试一下如下代码:

1
2
3
4
5
@GetMapping("/user")
public String testRequestParam(@RequestParam Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}

可以正常从控制台打印出 id 信息。同样地,url 上面的参数和方法的参数需要一致,如果不一致,也需要使用 value 属性来说明,比如 url 为:http://localhost:8080/user?idd=1

1
2
3
4
5
@RequestMapping("/user")
public String testRequestParam(@RequestParam(value = "idd", required = false) Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}

除了 value 属性外,还有个两个属性比较常用:

  • required 属性:true 表示该参数必须要传,否则就会报 404 错误,false 表示可有可无。

  • defaultValue 属性:默认值,表示如果请求中没有同名参数时的默认值。

从 url 中可以看出,@RequestParam 注解用于 GET 请求上时,接收拼接在 url 中的参数。除此之外,该注解还可以用于 POST 请求,接收前端表单提交的参数,假如前端通过表单提交 username 和 password 两个参数,那我们可以使用 @RequestParam 来接收,用法和上面一样。

1
2
3
4
5
6
@PostMapping("/form1")
public String testForm(@RequestParam String username, @RequestParam String password) {
System.out.println("获取到的username为:" + username);
System.out.println("获取到的password为:" + password);
return "success";
}

我们使用 postman 来模拟一下表单提交,测试一下接口:

那么问题来了,如果表单数据很多,我们不可能在后台方法中写上很多参数,每个参数还要 @RequestParam 注解。针对这种情况,我们需要封装一个实体类来接收这些参数,实体中的属性名和表单中的参数名一致即可。

1
2
3
4
5
public class User {
private String username;
private String password;
// set get
}

使用实体接收的话,我们不能在前面加 @RequestParam 注解了,直接使用即可。

1
2
3
4
5
6
@PostMapping("/form2")
public String testForm(User user) {
System.out.println("获取到的username为:" + user.getUsername());
System.out.println("获取到的password为:" + user.getPassword());
return "success";
}

使用 postman 再次测试一下表单提交,观察一下返回值和控制台打印出的日志即可。在实际项目中,一般都是封装一个实体类来接收表单数据,因为实际项目中表单数据一般都很多。

5. @RequestBody

@RequestBody 注解用于接收前端传来的实体,接收参数也是对应的实体,比如前端通过 json 提交传来两个参数 username 和 password,此时我们需要在后端封装一个实体来接收。在传递的参数比较多的情况下,使用 @RequestBody 接收会非常方便。例如:

1
2
3
4
5
public class User {
private String username;
private String password;
// set get
}
1
2
3
4
5
6
@PostMapping("/user")
public String testRequestBody(@RequestBody User user) {
System.out.println("获取到的username为:" + user.getUsername());
System.out.println("获取到的password为:" + user.getPassword());
return "success";
}

我们使用 postman 工具来测试一下效果,打开 postman,然后输入请求地址和参数,参数我们用 json 来模拟,如下图所有,调用之后返回 success。

同时看一下后台控制台输出的日志:

1
2
获取到的username为:倪升武
获取到的password为:123456

可以看出,@RequestBody 注解用于 POST 请求上,接收 json 实体参数。它和上面我们介绍的表单提交有点类似,只不过参数的格式不同,一个是 json 实体,一个是表单提交。在实际项目中根据具体场景和需要使用对应的注解即可。

6. 总结

本节课主要讲解了 Spring Boot 中对 MVC 的支持,分析了 @RestController@RequestMapping@PathVariable@RequestParam@RequestBody 四个注解的使用方式,由于 @RestController 中集成了 @ResponseBody 所以对返回 json 的注解不再赘述。以上四个注解是使用频率很高的注解,在所有的实际项目中基本都会遇到,要熟练掌握。


Spring Boot 中集成Redis