我一直以来用的都是最传统的System.out.println()来打印一些错误信息时,其实我也面临过一些问题。这样做的话,错误信息和别的输出结果混在一起,我一直都很难从结果里面找到我调试出来的问题。而且我用这种传统的没有明确的标识和格式,也总是花太多时间。
控制台打印日志的话,就比如像下面这样:
而且我也知道,实际开发肯定会有很多的输出信息,所以接下来最近的学习我就专门从一些优秀博客中了解到了slf4j日志,发现别人这样用效率真的很高,我也得知,SpringBoot提供了一套日志系统,最好的就是那个,logback。
1.1 外观模式
作为一种设计模式,当然是我应该了解的一个重点,我还是接着看了许多博客,看完大概十来篇吧,也是基本都已经了解了。
它的核心思想就是将子系统的复杂性进行抽象和封装,客户端就能通过一个简单的接口,来访问子系统,不需要了解子系统的具体实现细节。
我理解就是:
外观模式可以想象成一个房子的门面,门面是整个房子的外观,我光通过门面来访问房子,而不需要了解房子内部的具体实现和结构。门面向外提供了一个简单的接口,我只用知道如何使用这个接口访问房子内部。
它的编码实现如下:
interface Shape { public void draw(); }
class Circle implements Shape{ @Override public void draw() { System.out.println("绘制一个圆形"); } } class Square implements Shape{ @Override public void draw() { System.out.println("绘制一个正方形"); } }
class ShapeFacade{ private Circle circle; private Square square; public ShapeFacade(){ circle = new Circle(); square = new Square(); } public void drawCircle(){ circle.draw(); } public void drawSquare(){ square.hashCode(); } public void drawCircleAndSquare(){ circle.draw(); square.draw(); } }
再比如说, 电脑的例子其实更能理解一点,
电脑整机是CPU、内存、硬盘的外观。有了外观以后,启动电脑和关闭电脑都简化了。直接new一个电脑。在new电脑的同时把cpu、内存、硬盘都初始化好并且接好线。对外暴露方法(启动电脑,关闭电脑)。
启动电脑(按一下电源键):启动CPU、启动内存、启动硬盘
关闭电脑(按一下电源键):关闭硬盘、关闭内存、关闭CPU
1.2 slf4j简介
SLF4J查了一下单词,他是Simple Logging Facade for Java)其实我觉得每次有专有名词,就查一下完整的英文,更能帮助我理解并且记住这个新知识。
它是一个简单的Java日志门面框架,提供通用的接口,用于访问不同的日志实现,有Log4j、Logback、JCL等。SLF4J的目标是为不同的日志实现提供一个统一的接口,这样我以后就可以在不同的日志实现之间进行无缝切换了。
还有个Log4j,博客我看了一些,还是选择再多看看视频,虽然知道这个是在干啥,但是我习惯了传统的输出方式,对于这个点再多学一下就可以懂的,他说是Log4j可以将日志信息输出到控制台、而且它还支持很多不同的日志格式,像什么HTML、XML、JSON这些。
下面这句话是我看到的大佬总结:在项目中使用了slf4j记录日志,并且绑定了log4j,即导入相应的依赖,则日志会以log4j的风格输出;后期需要改为以logback的风格输出日志,只需要将log4j替换成logback即可,不用修改项目中的代码。
但是不止查到了它的知识,我还发现网上有人说之前log4j2在大概2021年出现过一件大事,特别大的漏洞,也有很多大佬对这个漏洞做了分析。因为这个漏洞太严重,很多厂商都发布了安全公告,提醒用户立刻去升级别的版本。
1.3 通过slf调用log4j
org.slf4j slf4j-log4j122.0.7
在我那个resources目录里面创建log4j的配置文件log4j.properties。
然后我来分析一下这个配置信息吧
//根日志记录器,参数1为需要输出的日志等级,参数2为日志输出的目标地名称stuout log4j.rootLogger=DEBUG,stdout,logFile //设置stdout是控制台输出 log4j.appender.stdout=org.apache.log4j.ConsoleAppender //配置日志输出的格式 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n log4j.appender.logFile=org.apache.log4j.FileAppender log4j.appender.logFile.ImmediateFlush=true log4j.appender.logFile.Append=true log4j.appender.logFile.File=D:/logs/log.log4j log4j.appender.logFile.layout=org.apache.log4j.PatternLayout log4j.appender.logFile.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
Log4j输出的目的地
org.apache.log4j.ConsoleAppender(控制台)
org.apache.log4j.FileAppender(文件)
org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
下面这两行一定要注意,很容易导错包,因为我自己犯这个错误了,所以还是必须记下来,当时也是不够细心了。
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
package com.xu.action; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/tests") public class TestController { private static final Logger logger = LoggerFactory.getLogger(TestController.class); @RequestMapping("/logs") public String log(){ logger.debug("=====测试日志debug级别打印======"); logger.info("=====测试日志info级别打印======"); logger.error("=====测试日志error级别打印======"); logger.warn("=====测试日志warn级别打印======"); System.out.println("logs..."); String str1 = "blog.xu.com"; String str2 = "blog.csdn.net/xxxx"; logger.info("=====徐苗苗的个人博客:{}; 徐苗苗的CSDN博客:{}",str1,str2); return "success"; } }
1.4 SpringBoot输出日志
debug:
true 在 SpringBoot 框架启动时自动输出日志信息,同时显示相互之间的依赖关系。仅仅用于开发阶段,产品阶段一定关闭,或者删除该配置。
切记,在实际项目中,使用 slf4j 作为自己的日志框架。使用 slf4j 记录日
志非常简单,直接使用 LoggerFactory 创建即可。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Test { private static final Logger logger = LoggerFactory.getLogger(Test.class); // …… }
在Spring Boot应用程序中,我们可以使用application.yml配置文件来配置日志信息。在这个文件中,我们可以设置日志级别、输出目的地、输出格式等。
比如,下面是一个简单的应用程序.yml配置
logging: level: root: INFO
其中,logging表示日志配置的主要属性,level表示日志级别,root表示根日志级别。在这个示例中,根日志级别为INFO,表示只输出INFO及以上级别的日志。
如果我们想要将日志输出到文件,可以在配置文件中添加如下配置:
logging: level: root: INFO file: name: myapp.log
这样,日志就会被输出到名为myapp.log的文件中。
logback.xml是一个用于配置logback日志框架的XML文件。在logback.xml中,我们可以指定日志的输出格式、输出目的地、日志级别等等,以满足不同应用程序的日志需求。
总之,logback.xml是一种非常灵活和强大的日志配置文件格式,通过合理配置可以帮助我们更好地了解应用程序的运行情况,以及快速定位和解决问题。
a. 定义日志输出格式和存储路径
使用<编码器>节点来定义日志的输出格式,例如:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
使用<追加器>节点来定义日志的存储路径,例如:
"FILE" >/path/to/logs/myapp.log /path/to/logs/myapp.%d{yyyy-MM-dd}.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
b. 定义控制台输出
${LOG_PATTERN}
在logback.xml文件中,我们可以使用<追加器>节点来设置日志的输出目的地,例如控制台输出。将控制台输出定义为一个名为CONSOLE的appender,类型为ConsoleAppender,表示输出到控制台。为了使输出的日志信息更清晰一点儿和方便阅读,可以使用预定义的输出格式LOG_PATTERN来格式化日志信息,然后通过${}引用该格式即可。
c. 定义日志文件的相关参数
在logback.xml文件中,我们可以使用<追加器>节点来定义日志文件的相关参数
/path/to/logs/myapp.log /path/to/logs/myapp.%d{yyyy-MM-dd}.log 30 %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/test") public class TestController { private final static Logger logger = LoggerFactory.getLogger(TestController.class); @RequestMapping("/log") public String testLog() { logger.debug("=====测试日志 debug 级别打印===="); logger.info("======测试日志 info 级别打印====="); logger.error("=====测试日志 error 级别打印===="); logger.warn("======测试日志 warn 级别打印====="); // 可以使用占位符打印出一些参数信息 String str1 = "blog.yan.com"; String str2 = "blog.csdn.net/yanjun"; // 输出日志内容时,允许使用{}表示一个占位符,后续参数按照位置对应进行赋值 logger.info("======闫峻的个人博客:{};闫峻的 CSDN 博客:{}", str1, str2); return "success"; } }
2.1 情景一:只有少量配置
在logback.xml文件中,如果只需要配置少量的信息,可以使用最简单的配置方式,例如:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
这种简单的配置方式适用于应用程序比较小,只需要输出到控制台并且不需要滚动日志的情形。如果需要更多的配置选项,可以使用更复杂的配置方式。
2.2 情景二:有多个配置
在logback.xml文件中,如果需要配置多个信息,可以使用更复杂的配置方式,例如:```
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n /path/to/logs/myapp.log /path/to/logs/myapp.%d{yyyy-MM-dd}.log 30 %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
这种复杂的配置方式适用于应用程序比较大,需要输出到多个输出目的地并且需要更复杂的日志管理的情形。根据实际需要,可以灵活地添加和修改和
2.3 指定项目配置文件
在实际项目中,一般有两个环境:开发环境和生产环境。
开发环境中的配置和生产环境中的配置是不同的哈,例如有环境、端口、数据库、相关地址等。实际上不可能在开发环境调试好之后,就部署到生产环境后,又要将配置信息全部修改成生产环境上的配置,这样太麻烦了。
我认为最好的解决方法就是开发环境和生产环境都有一套对用的配置信息,然后在开发的时候,指定读取开发环境的配置,把项目部署到服务器上,再指定去读取生产环境的配置。
3.1 @RestController
@RestController 是 Spring Boot 新增加的一个注解,相当于@Controller+@ResponseBody
@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 了。@Controller 则返回的是逻辑地址名。
如果使用@Controller 注解则表示返回一个逻辑地址名 user 字符串,需要依赖于 ViewResovler 组件将逻辑地址名称转换为物理地址。在 Spring Boot 集成 Thymeleaf 模板引擎中会使用。
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.html
如果使用@Controller,方法的返回值是字符串 user,则前后缀自动生效,将逻辑地址名
user 转换为物理地址名/user.html,静态页面默认存储位置可以考虑使用resources/static 目录
3.2 @RequestMapping
@RequestMapping 是一个用来处理请求地址映射的注解,它可以用于类上,也可以用于方法上。在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上,表示类中的所有响应请求的方法都是以该地址作为父路径;在方法的级别表示进一步指定到处理方法的映射关系。
@Controller @RequestMapping("/test") 表示当前类中每个方法映射路径的统一前缀/test public class TestController { @RequestMapping("/get") 针对方法的映射,加上类上的映射配置,则当前方法 的请求地址为/test/get public String get(){ return "user"; } }
3.3 @PathVariable
@PathVariable 注解主要是用来获取 url 参数,Spring Boot 支持 restfull 风格的 url,比如一个 GET 请求携带一个参数 id 过来 localhost:8080/user?id=123,可以将 id 作为参数接收,注解使用@RequestParam。
注意问题:如果想要 url 中占位符中的{id}值直接赋值到参数 id 中,需要保证 url 中的参数和方
法接收参数一致,否则就无法接收。如果不一样的话,其实也可以解决,需要用@PathVariable 中的 value 属性来指定对应关系。
@RequestMapping("/user/{idd}") public String testPathVariable(@PathVariable(value = "idd") Integer id) { System.out.println("获取到的 id 为:" + id); return "success"; }
对于访问的 url,占位符的位置可以在任何位置,不一定非要在最后,比如这样也行/xxx/{id}/user。另外 url也支持多个占位符,方法参数使用同样数量的参数来接收,原理和一个参数是一样的
@GetMapping("/user/{idd}/{name}") 例如对应的请求地址为 localhost:8080/user/123/abc,按照位置对应的 idd=123,name=abc。
public String testPathVariable(@PathVariable(value = "idd") Integer id, @PathVariable String name) { System.out.println("获取到的 id 为:" + id); System.out.println("获取到的 name 为:" + name); return "success"; }
@RequestParam 和 @PathValiable 注解一样也都是获取请求参数的。 @RequestParam 和 @PathVariable不同: @PathValiable 是从 url 模板中获取参数值, 即这种风格的 url 为 http://localhost:8080/user/{id} ; @RequestParam 是从 request 里面获取参数值,即这 种风格的 http://localhost:8080/user?id=1 。 如果参数不是 String 类型,就会自动执行数据类型转换 :3.4 @RequestParam
public String testRequestParam(@RequestParam Integer id) { System.out.println("获取到的 id 为:" + id); return "success"; }可以正确的从控制台打印出 id 信息。 url 上面的参数和方法的参数需要一样,如果不一样,也需要 使用 value 属性来说明。 具体测试: 可以使用 postman 来模拟一下表单提交,测试一下接口。但是如果表单数据很多,不可能在后台 方法中写上很多参数,每个参数还要 @RequestParam 注解。针对这种情况,需要封装一个实体类来接收这些 参数,实体中的属性名和表单中的参数名一致就行了!!!
public class User { private String username; private String password; // set get }使用实体接收的话,不能在前面加 @RequestParam 注解了,直接使用!
@PostMapping("/form2") public String testForm(User user) { System.out.println("获取到的 username 为:" + user.getUsername()); System.out.println("获取到的 password 为:" + user.getPassword()); return "success"; }如果写成 public String testForm(User user , String username) 则提交的请求参数 username 的值会赋值两个 地址,一个 user 中的 username 属性; 另一个是方法的参数 username 可以使用 postman 再次测试一下表单提交,观察一下返回值和控制台打印出的日志即可。 在我见到的实际项目中,一 般都是封装一个实体类来接收表单数据,因为实际项目中表单数据一般都很多。
@RequestBody 注解用于接收前端传来的实体,接收参数也是对应的实体, 使用 @RequestBody 接收会非常方便。3.5 @RequestBody
public class User { private String username; private String password; // set get }
控制器中方法的定义
@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 。 同时看一下后台控制台输出的日志: 获取到的 username 为:徐苗 获取到的 password 为: 123456 可以看出, @RequestBody 注解用于 POST 请求上,接收 json 实体参数。它和上面表单提交有点类似,只不过 参数的格式不同,一个是 json 实体,一个是表单提交。 在实际项目中根据具体场景和需要使用对应的注解就OK!!!!!