SpringBoot2基础配置详解

Posted 亮点菌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot2基础配置详解相关的知识,希望对你有一定的参考价值。

前言

    入门案例做完了,下面就要研究SpringBoot的用法了。通过入门案例,各位小伙伴能够感知到一个信息,SpringBoot没有具体的功能,它在辅助加快Spring程序的开发效率。我们发现现在几乎不用做任何的配置,功能就有了,确实很好用。但是仔细想想,没有做配置意味着什么?意味着配置已经做好了,不用你自己写了。但是新的问题又来了,如果不想用已经写好的默认配置,该如何干预呢?

    ​如果我们想修改默认的配置i,这个信息应该写在什么位置呢?目前我们接触的入门案例中一共有3个文件,第一是pom.xml文件,设置项目的依赖的,这个没什么好研究的,第二是引导类,这个是执行SpringBoot程序的入口,也不像是做配置的地方,其实还有一个信息,就是在resources目录下面有一个空白的文件,叫做application.properties。一看就是个配置文件,本文来说说配置文件怎么写,能写什么,怎么干预SpringBoot的默认配置,修改成自己的配置。


文章目录


一、属性配置

​    SpringBoot通过配置文件application.properties就可以修改默认的配置,那咱们就先找个简单的配置下手,当前访问tomcat的默认端口是8080,但是不便于书写,我们先改成80,通过这个操作来熟悉一下SpringBoot的配置格式是什么样的

​那该如何写呢?properties格式的文件书写规范是key=value

name=itheima

​这个格式肯定是不能颠覆的,那就尝试性的写就行了,改端口,写port。当你输入port后,神奇的事情就发生了,会有提示出现。

根据提示敲回车,输入80端口

server.port=80

    ​下面就可以直接运行程序,测试效果了。

    ​我们惊奇的发现SpringBoot这玩意儿狠啊,以前修改端口在哪里改?tomcat服务器的配置文件中改,现在呢?SpringBoot专用的配置文件中改,是不是意味着以后所有的配置都可以写在这一个文件中呢?是的,简化开发者配置的书写位置,集中管理。妙啊,妈妈再也不用担心我找不到配置文件了。

​其实到这里我们应该得到如下三个信息:

  1. SpringBoot程序可以在application.properties文件中进行属性配置
  2. application.properties文件中只要输入要配置的属性关键字就可以根据提示进行设置
  3. SpringBoot将配置信息集中在一个文件中写,不管你是服务器的配置,还是数据库的配置,总之都写在一起,逃离一个项目十几种配置文件格式的尴尬局面

总结: SpringBoot默认配置文件是application.properties

​做完了端口的配置,趁热打铁,再做几个配置,目前项目启动时会显示一些日志信息,就来改一改这里面的一些设置。

关闭运行日志图表(banner)

spring.main.banner-mode=off

设置运行日志的显示级别

logging.level.root=debug

​    你会发现,现在这么搞配置太舒服了,以前你做配置怎么做?不同的技术有自己专用的配置文件,文件不同格式也不统一,现在呢?不用东奔西走的找配置文件写配置了,统一格式了。

​    我们现在配置了3个信息,但是又有新的问题了。这个配置是随便写的吗?什么都能配?有没有一个东西显示所有能配置的项呢?此外这个配置和什么东西有关呢?会不会因为我写了什么东西以后才可以写什么配置呢?比如我现在没有写数据库相关的东西,能否配置数据呢?先说第一个问题,都能配置什么。

​    打开SpringBoot的官网,找到SpringBoot官方文档,打开查看附录中的Application Properties就可以获取到对应的配置项了,网址奉上:https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties

​    能写什么的问题解决了,再来说第二个问题,这个配置项和什么有关。在pom中注释掉导入的spring-boot-starter-web,然后刷新工程,你会发现配置的提示消失了。闹了半天是设定使用了什么技术才能做什么配置。也合理,不然配置的东西都没有使用对应技术,配了也是白配。

温馨提示:

​    所有的starter中都会依赖下面这个starter,叫做spring-boot-starter。这个starter是所有的SpringBoot的starter的基础依赖,里面定义了SpringBoot相关的基础配置,关于这个starter我们到开发应用篇和原理篇中再深入讲解。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.5.4</version>
    <scope>compile</scope>
</dependency>

总结

  1. SpringBoot中导入对应starter后,提供对应配置属性
  2. 书写SpringBoot配置采用关键字+提示形式书写

二、配置文件分类

​    现在已经能够进行SpringBoot相关的配置了,但是properties格式的配置写起来总是觉得看着不舒服,所以就期望存在一种书写起来更简便的配置格式提供给开发者使用。SpringBoot除了支持properties格式的配置文件,还支持另外两种格式的配置文件。分别如下:

  • properties格式
  • yml格式
  • yaml格式

一看到全新的文件格式,各位小伙伴肯定想,这下又要学习新的语法格式了。怎么说呢?从知识角度来说,要学,从开发角度来说,不用学。为什么呢?因为SpringBoot的配置在Idea工具下有提示啊,跟着提示走就行了。下面列举三种不同文件格式配置相同的属性范例

  • application.properties(properties格式)
server.port=80
  • application.yml(yml格式)
server:
  port: 81
  • application.yaml(yaml格式)
server:
  port: 82

​    仔细看会发现yml格式和yaml格式除了文件名后缀不一样,格式完全一样,是这样的,yml和yaml文件格式就是一模一样的,只是文件后缀不同,所以可以合并成一种格式来看。那对于这三种格式来说,以后用哪一种比较多呢?记清楚,以后基本上都是用yml格式的在企业开发过程中用这个格式的机会也最多,一定要重点掌握。

总结: SpringBoot提供了3种配置文件的格式

  • properties(传统格式/默认格式)
  • yml(主流格式)
  • yaml

思考:现在我们已经知道使用三种格式都可以做配置了,万一我三个都写了,他们三个谁说了算呢?

1、配置文件优先级

​    其实三个文件如果共存的话,谁生效说的就是配置文件加载的优先级别。我们就让三个配置文件书写同样的信息,比如都配置端口,然后我们让每个文件配置的端口号都不一样,最后启动程序后看启动端口是多少就知道谁的加载优先级比较高了。

  • application.properties(properties格式)
server.port=80
  • application.yml(yml格式)
server:
  port: 81
  • application.yaml(yaml格式)
server:
  port: 82

​启动后发现目前的启动端口为80,把80对应的文件删除掉,然后再启动,现在端口又改成了81。现在我们就已经知道了3个文件的加载优先顺序是什么

application.properties  >  application.yml  >  application.yaml

    ​虽然得到了一个知识结论,但是我们实际开发的时候还是要看最终的效果为准。也就是你要的最终效果是什么自己是明确的,上述结论只能帮助你分析结论产生的原因。这个知识了解一下就行了,因为以后同时写多种配置文件格式的情况实在是较少。

​最后我们把配置文件内容给修改一下

  • application.properties(properties格式)
server.port=80
spring.main.banner-mode=off
  • application.yml(yml格式)
server:
  port: 81
logging: 
  level: 
    root: debug
  • application.yaml(yaml格式)
server:
  port: 82

​    我们发现不仅端口生效了,最终显示80,同时其他两条配置也生效了,看来每个配置文件中的项都会生效,只不过如果多个配置文件中有相同类型的配置会优先级高的文件覆盖优先级的文件中的配置。如果配置项不同的话,那所有的配置项都会生效。

总结

  1. 配置文件间的加载优先级 properties(最高)> yml > yaml(最低)
  2. 不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留

2、自动提示功能消失解决方案

    可能有些小伙伴会基于各种各样的原因导致配置文件中没有提示,这个确实很让人头疼,所以下面给大家说一下如果自动提示功能消失了怎么解决。

​    先要明确一个核心,就是自动提示功能不是SpringBoot技术给我们提供的,是我们在Idea工具下编程,这个编程工具给我们提供的。明白了这一点后,再来说为什么会出现这种现象。其实这个自动提示功能消失的原因还是蛮多的,如果想解决这个问题,就要知道为什么会消失,大体原因有如下2种:

  1. Idea认为你现在写配置的文件不是个配置文件,所以拒绝给你提供提示功能
  2. Idea认定你是合理的配置文件,但是Idea加载不到对应的提示信息

这里我们主要解决第一个现象。解决方式如下:

步骤①:打开设置,【Files】→【Project Structure…】

步骤②:在弹出窗口中左侧选择【Facets】,右侧选中Spring路径下对应的模块名称,也就是你自动提示功能消失的那个模块

步骤③:点击Customize Spring Boot按钮,此时可以看到当前模块对应的配置文件是哪些了。如果没有你想要称为配置文件的文件格式,就有可能无法弹出提示

步骤④:选择添加配置文件,然后选中要作为配置文件的具体文件就OK了

​到这里就做完了,其实就是Idea的一个小功能

总结: 指定SpringBoot配置文件

  • Setting → Project Structure → Facets
  • 选中对应项目/工程
  • Customize Spring Boot
  • 选择配置文件

三、yaml文件

    SpringBoot的配置以后主要使用yml结尾的这种文件格式,并且在书写时可以通过提示的形式加载正确的格式。但是这种文件还是有严格的书写格式要求的。下面就来说一下具体的语法格式。

    ​YAML(YAML Ain't Markup Language),一种数据序列化格式。具有容易阅读、容易与脚本语言交互、以数据为核心,重数据轻格式的特点。常见的文件扩展名有两种:

  • .yml格式(主流)
  • .yaml格式

对于文件自身在书写时,具有严格的语法格式要求,具体如下:

  1. 大小写敏感
  2. 属性层级关系使用多行描述,每行结尾使用冒号结束
  3. 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
  4. 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
  5. #号 表示注释

上述规则不要死记硬背,按照书写习惯慢慢适应,并且在Idea下由于具有提示功能,慢慢适应着写格式就行了。核心的一条规则要记住,数据前面要加空格与冒号隔开。

​下面列出常见的数据书写格式:

boolean: TRUE  						#TRUE,true,True,FALSE,false,False均可
float: 3.14    						#6.8523015e+5  #支持科学计数法
int: 123       						#0b1010_0111_0100_1010_1110    #支持二进制、八进制、十六进制
null: ~        						#使用~表示null
string: HelloWorld      			#字符串可以直接书写
string2: "Hello World"  			#可以使用双引号包裹特殊字符
date: 2018-02-17        			#日期必须使用yyyy-MM-dd格式
datetime: 2018-02-17T15:02:31+08:00  #时间和日期之间使用T连接,最后使用+代表时区

​此外,yaml格式中也可以表示数组,在属性名书写位置的下方使用减号作为数据开始符号,每行书写一个数据,减号与数据间空格分隔

subject:
	- Java
	- 前端
	- 大数据
enterprise:
	name: itcast
    age: 16
    subject:
    	- Java
        - 前端
        - 大数据
likes: [王者荣耀,刺激战场]			#数组书写缩略格式
users:							 #对象数组格式一
  - name: Tom
   	age: 4
  - name: Jerry
    age: 5
users:							 #对象数组格式二
  -  
    name: Tom
    age: 4
  -   
    name: Jerry
    age: 5			    
users2: [  name:Tom , age:4  ,  name:Jerry , age:5  ]	#对象数组缩略格式

总结

  1. yaml语法规则
    • 大小写敏感
    • 属性层级关系使用多行描述,每行结尾使用冒号结束
    • 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
    • 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
    • #号 表示注释
  2. 注意属性名冒号后面与数据之间有一个空格
  3. 字面值、对象数据格式、数组数据格式

思考:​现在我们已经知道了yaml具有严格的数据格式要求,并且已经可以正确的书写yaml文件了,那这些文件书写后其实是在定义一些数据。这些数据时给谁用的呢?大部分是SpringBoot框架内部使用,但是如果我们想配置一些数据自己使用,能不能用呢?答案是可以的,那如何读取yaml文件中的数据呢?

四、yaml数据读取

    ​对于yaml文件中的数据,其实你就可以想象成这就是一个小型的数据库,里面保存有若干数据,每个数据都有一个独立的名字,如果你想读取里面的数据,肯定是支持的,下面就介绍3种读取数据的方式

1、读取单一数据

    ​yaml中保存的单个数据,可以使用Spring中的注解直接读取,使用@Value可以读取单个数据,属性名引用方式:$一级属性名.二级属性名……

​记得使用@Value注解时,要将该注入写在某一个指定的Spring管控的bean的属性名上方。现在就可以读取到对应的单一数据行了

总结

  1. 使用@Value配合SpEL读取单个数据
  2. 如果数据存在多层级,依次书写层级名称即可

2、读取全部数据

    ​读取单一数据可以解决读取数据的问题,但是如果定义的数据量过大,这么一个一个书写肯定会累死人的,SpringBoot提供了一个对象,能够把所有的数据都封装到这一个对象中,这个对象叫做Environment,使用自动装配注解可以将所有的yaml数据封装到这个对象中.

​数据封装到了Environment对象中,获取属性时,通过Environment的接口操作进行,具体方法时getProperties(String),参数填写属性名即可

总结

  1. 使用Environment对象封装全部配置信息
  2. 使用@Autowired自动装配数据到Environment对象中

3、读取对象数据

    ​单一数据读取书写比较繁琐,全数据封装又封装的太厉害了,每次拿数据还要一个一个的getProperties(),总之用起来都不是很舒服。由于Java是一个面向对象的语言,很多情况下,我们会将一组数据封装成一个对象。SpringBoot也提供了可以将一组yaml对象数据封装一个Java对象的操作

​首先定义一个对象,并将该对象纳入Spring管控的范围,也就是定义成一个bean,然后使用注解@ConfigurationProperties指定该对象加载哪一组yaml中配置的信息。

​这个@ConfigurationProperties必须告诉他加载的数据前缀是什么,这样当前前缀下的所有属性就封装到这个对象中。记得数据属性名要与对象的变量名一一对应啊,不然没法封装。其实以后如果你要定义一组数据自己使用,就可以先写一个对象,然后定义好属性,下面到配置中根据这个格式书写即可。

温馨提示:细心的小伙伴会发现一个问题,自定义的这种数据在yaml文件中书写时没有弹出提示,后面再揭秘如何弹出提示。

总结

  1. 使用@ConfigurationProperties注解绑定配置信息到封装类中
  2. 封装类需要定义为Spring管理的bean,否则无法进行属性注入

4、yaml文件中的数据引用

    ​如果你在书写yaml数据时,经常出现如下现象,比如很多个文件都具有相同的目录前缀

center:
	dataDir: /usr/local/fire/data
    tmpDir: /usr/local/fire/tmp
    logDir: /usr/local/fire/log
    msgDir: /usr/local/fire/msgDir

或者

center:
	dataDir: D:/usr/local/fire/data
    tmpDir: D:/usr/local/fire/tmp
    logDir: D:/usr/local/fire/log
    msgDir: D:/usr/local/fire/msgDir

这个时候你可以使用引用格式来定义数据,其实就是搞了个变量名,然后引用变量了,格式如下:

baseDir: /usr/local/fire
	center:
    dataDir: $baseDir/data
    tmpDir: $baseDir/tmp
    logDir: $baseDir/log
    msgDir: $baseDir/msgDir

​还有一个注意事项,在书写字符串时,如果需要使用转义字符,需要将数据字符串使用双引号包裹起来

lesson: "Spring\\tboot\\nlesson"

总结

  1. 在配置文件中可以使用$属性名方式引用属性值
  2. 如果属性中出现特殊字符,可以使用双引号包裹起来作为字符解析

    到这里有关yaml文件的基础使用就先告一段落,在后边的学习内容中再继续研究更深入的内容。


感谢观看,希望本篇文章对你有所帮助!

SpringBoot Logback 配置详解

一.了解

简单地说,Logback 是一个 Java 领域的日志框架。它被认为是 Log4J 的继承人。
Logback 主要由三个模块组成:

  • logback-core

  • logback-classic

  • logback-access

logback-core 是其它模块的基础设施,其它模块基于它构建,显然,logback-core 提供了一些关键的通用机制。logback-classic 的地位和作用等同于 Log4J,它也被认为是 Log4J 的一个改进版,并且它实现了简单日志门面 SLF4J;而 logback-access 主要作为一个与 Servlet 容器交互的模块,比如说 tomcat 或者 jetty,提供一些与 HTTP 访问相关的功能。

目前 Logback 的使用很广泛,很多知名的开源软件都使用了 Logback作为日志框架,比如说 Akka,Apache Camel 等

Logback 与 Log4J

实际上,这两个日志框架都出自同一个开发者之手,Logback 相对于 Log4J 有更多的优点

  • 同样的代码路径,Logback 执行更快

  • 更充分的测试

  • 原生实现了 SLF4J API(Log4J 还需要有一个中间转换层)

  • 内容更丰富的文档

  • 支持 XML 或者 Groovy 方式配置

  • 配置文件自动热加载

  • 从 IO 错误中优雅恢复

  • 自动删除日志归档

  • 自动压缩日志成为归档文件

  • 支持 Prudent 模式,使多个 JVM 进程能记录同一个日志文件

  • 支持配置文件中加入条件判断来适应不同的环境

  • 更强大的过滤器

  • 支持 SiftingAppender(可筛选 Appender)

  • 异常栈信息带有包信息

二.快速上手

想在 Java 程序中使用 Logback,需要依赖三个 jar 包,分别是 slf4j-api,logback-core,logback-classic。其中 slf4j-api 并不是 Logback 的一部分,是另外一个项目,但是强烈建议将 slf4j 与 Logback 结合使用。要引用这些 jar 包,在 maven 项目中引入以下3个 dependencies

  1.  
    <dependency>
  2.  
    <groupId>org.slf4j</groupId>
  3.  
    <artifactId>slf4j-api</artifactId>
  4.  
    <version>1.7.5</version>
  5.  
    </dependency>
  6.  
    <dependency>
  7.  
    <groupId>ch.qos.logback</groupId>
  8.  
    <artifactId>logback-core</artifactId>
  9.  
    <version>1.0.11</version>
  10.  
    </dependency>
  11.  
    <dependency>
  12.  
    <groupId>ch.qos.logback</groupId>
  13.  
    <artifactId>logback-classic</artifactId>
  14.  
    <version>1.0.11</version>
  15.  
    </dependency>

第一个简单的例子

  1.  
    package io.beansoft.logback.demo.universal;
  2.  
     
  3.  
    import org.slf4j.Logger;
  4.  
    import org.slf4j.LoggerFactory;
  5.  
     
  6.  
    /**
  7.  
    *
  8.  
    *
  9.  
    * @author beanlam
  10.  
    * @date 2017年2月9日 下午11:17:53
  11.  
    * @version 1.0
  12.  
    *
  13.  
    */
  14.  
    public class SimpleDemo {
  15.  
     
  16.  
    private static final Logger logger = LoggerFactory.getLogger(SimpleDemo.class);
  17.  
     
  18.  
    public static void main(String[] args) {
  19.  
    logger.info("Hello, this is a line of log message logged by Logback");
  20.  
    }
  21.  
    }

注意到这里,代码里并没有引用任何一个跟 Logback 相关的类,而是引用了 SLF4J 相关的类,这边是使用 SLF4J 的好处,在需要将日志框架切换为其它日志框架时,无需改动已有的代码。

LoggerFactory 的 getLogger() 方法接收一个参数,以这个参数决定 logger 的名字,这里传入了 SimpleDemo 这个类的 Class 实例,那么 logger 的名字便是 SimpleDemo 这个类的全限定类名:io.beansoft.logback.demo.universal.SimpleDemo

Logger,Appenders 与 Layouts

在 logback 里,最重要的三个类分别是

  • Logger

  • Appender

  • Layout

Logger 类位于 logback-classic 模块中, 而 Appender 和 Layout 位于 logback-core 中,这意味着, Appender 和 Layout 并不关心 Logger 的存在,不依赖于 Logger,同时也能看出, Logger 会依赖于 Appender 和 Layout 的协助,日志信息才能被正常打印出来

分层命名规则

为了可以控制哪些信息需要输出,哪些信息不需要输出,logback 中引进了一个 分层 概念。每个 logger 都有一个 name,这个 name 的格式与 Java 语言中的包名格式相同。这也是前面的例子中直接把一个 class 对象传进 LoggerFactory.getLogger() 方法作为参数的原因。

logger 的 name 格式决定了多个 logger 能够组成一个树状的结构,为了维护这个分层的树状结构,每个 logger 都被绑定到一个 logger 上下文中,这个上下文负责厘清各个 logger 之间的关系。

例如, 命名为 io.beansoft 的 logger,是命名为 io.beansoft.logback 的 logger 的父亲,是命名为 io.beansoft.logback.demo 的 logger 的祖先。

在 logger 上下文中,有一个 root logger,作为所有 logger 的祖先,这是 logback 内部维护的一个 logger,并非开发者自定义的 logger。

可通过以下方式获得这个 logger :

Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

同样,通过 logger 的 name,就能获得对应的其它 logger 实例。

Logger 这个接口主要定义的方法有:

  1.  
    package org.slf4j;
  2.  
    public interface Logger {
  3.  
     
  4.  
    // Printing methods:
  5.  
    public void trace(String message);
  6.  
    public void debug(String message);
  7.  
    public void info(String message);
  8.  
    public void warn(String message);
  9.  
    public void error(String message);
  10.  
    }

日志打印级别

logger 有日志打印级别,可以为一个 logger 指定它的日志打印级别。
如果不为一个 logger 指定打印级别,那么它将继承离他最近的一个有指定打印级别的祖先的打印级别。这里有一个容易混淆想不清楚的地方,如果 logger 先找它的父亲,而它的父亲没有指定打印级别,那么它会立即忽略它的父亲,往上继续寻找它爷爷,直到它找到 root logger。因此,也能看出来,要使用 logback, 必须为 root logger 指定日志打印级别。

日志打印级别从低级到高级排序的顺序是:
TRACE < DEBUG < INFO < WARN < ERROR 
如果一个 logger 允许打印一条具有某个日志级别的信息,那么它也必须允许打印具有比这个日志级别更高级别的信息,而不允许打印具有比这个日志级别更低级别的信息。

举个例子:

  1.  
    package io.beansoft.logback.demo.universal;
  2.  
     
  3.  
    import org.slf4j.Logger;
  4.  
    import org.slf4j.LoggerFactory;
  5.  
     
  6.  
    import ch.qos.logback.classic.Level;
  7.  
     
  8.  
    /**
  9.  
    *
  10.  
    *
  11.  
    * @author beanlam
  12.  
    * @date 2017年2月10日 上午12:20:33
  13.  
    * @version 1.0
  14.  
    *
  15.  
    */
  16.  
    public class LogLevelDemo {
  17.  
     
  18.  
    public static void main(String[] args) {
  19.  
     
  20.  
    //这里强制类型转换时为了能设置 logger 的 Level
  21.  
    ch.qos.logback.classic.Logger logger =
  22.  
    (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
  23.  
    logger.setLevel(Level.INFO);
  24.  
     
  25.  
    Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");
  26.  
     
  27.  
    // 这个语句能打印,因为 WARN > INFO
  28.  
    logger.warn("can be printed because WARN > INFO");
  29.  
     
  30.  
    // 这个语句不能打印,因为 DEBUG < INFO.
  31.  
    logger.debug("can not be printed because DEBUG < INFO");
  32.  
     
  33.  
    // barlogger 是 logger 的一个子 logger
  34.  
    // 它继承了 logger 的级别 INFO
  35.  
    // 以下语句能打印,因为 INFO >= INFO
  36.  
    barlogger.info("can be printed because INFO >= INFO");
  37.  
     
  38.  
    // 以下语句不能打印,因为 DEBUG < INFO
  39.  
    barlogger.debug("can not be printed because DEBUG < INFO");
  40.  
    }
  41.  
    }

打印结果是:

  1.  
    00:27:19.251 [main] WARN com.foo - can be printed because WARN > INFO
  2.  
    00:27:19.255 [main] INFO com.foo.Bar - can be printed because INFO >= INFO

Appender 和 Layout 

在 logback 的世界中,日志信息不仅仅可以打印至 console,也可以打印至文件,甚至输出到网络流中,日志打印的目的地由 Appender 来决定,不同的 Appender 能将日志信息打印到不同的目的地去。

Appender 是绑定在 logger 上的,同时,一个 logger 可以绑定多个 Appender,意味着一条信息可以同时打印到不同的目的地去。例如,常见的做法是,日志信息既输出到控制台,同时也记录到日志文件中,这就需要为 logger 绑定两个不同的 logger。

Appender 是绑定在 logger 上的,而 logger 又有继承关系,因此一个 logger 打印信息时的目的地 Appender 需要参考它的父亲和祖先。在 logback 中,默认情况下,如果一个 logger 打印一条信息,那么这条信息首先会打印至它自己的 Appender,然后打印至它的父亲和父亲以上的祖先的 Appender,但如果它的父亲设置了 additivity = false,那么这个 logger 除了打印至它自己的 Appender 外,只会打印至其父亲的 Appender,因为它的父亲的 additivity 属性置为了 false,开始变得忘祖忘宗了,所以这个 logger 只认它父亲的 Appender;此外,对于这个 logger 的父亲来说,如果父亲的 logger 打印一条信息,那么它只会打印至自己的 Appender中(如果有的话),因为父亲已经忘记了爷爷及爷爷以上的那些父辈了。

打印的日志除了有打印的目的地外,还有日志信息的展示格式。在 logback 中,用 Layout 来代表日志打印格式。比如说,PatternLayout 能够识别以下这条格式:

%-4relative [%thread] %-5level %logger{32} - %msg%n


然后打印出来的格式效果是:

176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.

上面这个格式的第一个字段代表从程序启动开始后经过的毫秒数,第二个字段代表打印出这条日志的线程名字,第三个字段代表日志信息的日志打印级别,第四个字段代表 logger name,第五个字段是日志信息,第六个字段仅仅是代表一个换行符

参数化打印日志

经常能看到打印日志的时候,使用以下这种方式打印日志:

logger.debug("the message is " + msg + " from " + somebody);

这种打印日志的方式有个缺点,就是无论日志级别是什么,程序总要先执行 "the message is " + msg + " from " + somebody 这段字符串的拼接操作。当 logger 设置的日志级别为比 DEBUG 级别更高级别时,DEBUG 级别的信息不回被打印出来的,显然,字符串拼接的操作是不必要的,当要拼接的字符串很大时,这无疑会带来很大的性能白白损耗。

于是,一种改进的打印日志方式被人们发现了:

  1.  
    if(logger.isDebugEnabled()) {
  2.  
    logger.debug("the message is " + msg + " from " + somebody);
  3.  
    }

这样的方式确实能避免字符串拼接的不必要损耗,但这也不是最好的方法,当日志级别为 DEBUG 时,那么打印这行消息,需要判断两次日志级别。一次是logger.isDebugEnabled(),另一次是 logger.debug() 方法内部也会做的判断。这样也会带来一点点效率问题,如果能找到更好的方法,谁愿意无视白白消耗的效率。

有一种更好的方法,那就是提供占位符的方式,以参数化的方式打印日志,例如上述的语句,可以是这样的写法:

logger.debug("the message {} is from {}", msg, somebody);

这样的方式,避免了字符串拼接,也避免了多一次日志级别的判断

logback 内部运行流程

技术图片

当应用程序发起一个记录日志的请求,例如 info() 时,logback 的内部运行流程如下所示

  1. 获得过滤器链条

  2. 检查日志级别以决定是否继续打印

  3. 创建一个 LoggingEvent 对象

  4. 调用 Appenders

  5. 进行日志信息格式化

  6. 发送 LoggingEvent 到对应的目的

有关性能问题

关于日志系统,人们讨论得最多的是性能问题,即使是小型的应用程序,也有可能输出大量的日志。打印日志中的不当处理,会引发各种性能问题,例如太多的日志记录请求可能使磁盘 IO 成为性能瓶颈,从而影响到应用程序的正常运行。在合适的时候记录日志、以更好的方式发起日志请求、以及合理设置日志级别方面,都有可能造成性能问题。
关于性能问题,以下几个方面需要了解

  • 建议使用占位符的方式参数化记录日志

  • logback 内部机制保证 logger 在记录日志时,不必每一次都去遍历它的父辈以获得关于日志级别、Appender 的信息

  • 在 logback 中,将日志信息格式化,以及输出到目的地,是最损耗性能的操作

logback 配置

logback 提供的配置方式有以下几种:

  • 编程式配置

  • xml 格式

  • groovy 格式

logback 在启动时,根据以下步骤寻找配置文件:

  1. 在 classpath 中寻找 logback-test.xml文件

  2. 如果找不到 logback-test.xml,则在 classpath 中寻找 logback.groovy 文件

  3. 如果找不到 logback.groovy,则在 classpath 中寻找 logback.xml文件

  4. 如果上述的文件都找不到,则 logback 会使用 JDK 的 SPI 机制查找 META-INF/services/ch.qos.logback.classic.spi.Configurator 中的 logback 配置实现类,这个实现类必须实现 Configuration 接口,使用它的实现来进行配置

  5. 如果上述操作都不成功,logback 就会使用它自带的 BasicConfigurator 来配置,并将日志输出到 console

logback-test.xml 一般用来在测试代码中打日志,如果是 maven 项目,一般把 logback-test.xml 放在 src/test/resources 目录下。maven 打包的时候也不会把这个文件打进 jar 包里。
logback 启动的时候解析配置文件大概需要 100 毫秒的时间,如果希望更快启动,可以采用 SPI 的方式。

默认的配置

前面有提到默认的配置,由 BasicConfiguator 类配置而成,这个类的配置可以用如下的配置文件来表示:

  1.  
    <configuration>
  2.  
     
  3.  
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  4.  
    <!-- encoders are assigned the type
  5.  
    ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
  6.  
    <encoder>
  7.  
    <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
  8.  
    </encoder>
  9.  
    </appender>
  10.  
     
  11.  
    <root level="debug">
  12.  
    <appender-ref ref="STDOUT" />
  13.  
    </root>
  14.  
    </configuration>

启动时打印状态信息

如果 logback 在启动时,解析配置文件时,出现了需要警告的信息或者错误信息,那 logback 会自动先打印出自身的状态信息。

如果希望正常情况下也打印出状态信息,则可以使用之前提到的方式,在代码里显式地调用使其输出:

  1.  
    public static void main(String[] args) {
  2.  
    // assume SLF4J is bound to logback in the current environment
  3.  
    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
  4.  
    // print logback‘s internal status
  5.  
    StatusPrinter.print(lc);
  6.  
    ...
  7.  
    }

也可以在配置文件中,指定 configuration 的 debug 属性为 true

  1.  
    <configuration debug="true">
  2.  
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  3.  
    <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder
  4.  
    by default -->
  5.  
    <encoder>
  6.  
    <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
  7.  
    </pattern>
  8.  
    </encoder>
  9.  
    </appender>
  10.  
     
  11.  
    <root level="debug">
  12.  
    <appender-ref ref="STDOUT" />
  13.  
    </root>
  14.  
    </configuration>

还可以指定一个 Listener:

  1.  
    <configuration>
  2.  
    <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
  3.  
    ... the rest of the configuration file
  4.  
    </configuration>

重置默认的配置文件位置

设置 logback.configurationFile 系统变量,可以通过 -D 参数设置,所指定的文件名必须以 .xml 或者 .groovy 作为文件后缀,否则 logback 会忽略这些文件

  1.  
    <property name="APP_Name" value="8080-springboot-pro" /> //这里为此项目的日志文件夹名
  2.  
    <property name="log.dir" value="~/Desktop/files"></property> //这里为日志的存储地址

配置文件自动热加载

要使配置文件自动重载,需要把 scan 属性设置为 true,默认情况下每分钟才会扫描一次,可以指定扫描间隔:

  1.  
    <configuration scan="true" scanPeriod="30 seconds" >
  2.  
    ...
  3.  
    </configuration>

注意扫描间隔要加上单位,可用的单位是 milliseconds,seconds,minutes 和 hours。如果只指定了数字,但没有指定单位,这默认单位为 milliseconds。

在 logback 内部,当设置 scan 属性为 true 后,一个叫做 ReconfigureOnChangeFilter 的过滤器就会被牵扯进来,它负责判断是否到了该扫描的时候,以及是否该重新加载配置。Logger 的任何一个打印日志的方法被调用时,都会触发这个过滤器,所以关于这个过滤器的自身的性能问题,变得十分重要。logback 目前采用这样一种机制,当 logger 的调用次数到达一定次数后,才真正让过滤器去做它要做的事情,这个次数默认是 16,而 logback 会在运行时根据调用的频繁度来动态调整这个数目。

输出异常栈时也打印出 jar 包的信息

这个属性默认是关闭,可通过以下方式开启:

  1.  
    <configuration packagingData="true">
  2.  
    ...
  3.  
    </configuration>

也可以通过 LoggerContext 的 setPackagingDataEnabled(boolean) 方法来开启

  1.  
    LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
  2.  
    lc.setPackagingDataEnabled(true);

直接调用 JoranConfigurator

Joran 是 logback 使用的一个配置加载库,如果想要重新实现 logback 的配置机制,可以直接调用这个类 JoranConfigurator 来实现:

  1.  
    package chapters.configuration;
  2.  
     
  3.  
    import org.slf4j.Logger;
  4.  
    import org.slf4j.LoggerFactory;
  5.  
     
  6.  
    import ch.qos.logback.classic.LoggerContext;
  7.  
    import ch.qos.logback.classic.joran.JoranConfigurator;
  8.  
    import ch.qos.logback.core.joran.spi.JoranException;
  9.  
    import ch.qos.logback.core.util.StatusPrinter;
  10.  
     
  11.  
    public class MyApp3 {
  12.  
    final static Logger logger = LoggerFactory.getLogger(MyApp3.class);
  13.  
     
  14.  
    public static void main(String[] args) {
  15.  
    // assume SLF4J is bound to logback in the current environment
  16.  
    LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
  17.  
     
  18.  
    try {
  19.  
    JoranConfigurator configurator = new JoranConfigurator();
  20.  
    configurator.setContext(context);
  21.  
    // Call context.reset() to clear any previous configuration, e.g. default
  22.  
    // configuration. For multi-step configuration, omit calling context.reset().
  23.  
    context.reset();
  24.  
    configurator.doConfigure(args[0]);
  25.  
    } catch (JoranException je) {
  26.  
    // StatusPrinter will handle this
  27.  
    }
  28.  
    StatusPrinter.printInCaseOfErrorsOrWarnings(context);
  29.  
     
  30.  
    logger.info("Entering application.");
  31.  
     
  32.  
    Foo foo = new Foo();
  33.  
    foo.doIt();
  34.  
    logger.info("Exiting application.");
  35.  
    }
  36.  
    }

 配置文件格式

配置 logger 节点

在配置文件中,logger 的配置在<logger> 标签中配置,<logger> 标签只有一个属性是一定要的,那就是 name,除了 name 属性,还有 level 属性,additivity 属性可以配置,不过它们是可选的。
level 的取值可以是 TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF, INHERITED, NULL, 其中 INHERITED 和 NULL 的作用是一样的,并不是不打印任何日志,而是强制这个 logger 必须从其父辈继承一个日志级别。
additivity 的取值是一个布尔值,true 或者 false。

<logger> 标签下只有一种元素,那就是 <appender-ref>,可以有0个或多个,意味着绑定到这个 logger 上的 Appender。

配置 root 节点

<root> 标签和 <logger> 标签的配置类似,只不过 <root> 标签只允许一个属性,那就是 level 属性,并且它的取值范围只能取 TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF
<root> 标签下允许有0个或者多个 <appender-ref>。

 

配置 appender 节点

<appender> 标签有两个必须填的属性,分别是 name 和 class,class 用来指定具体的实现类。<appender> 标签下可以包含至多一个 <layout>,0个或多个 <encoder>,0个或多个 <filter>,除了这些标签外,<appender> 下可以包含一些类似于 JavaBean 的配置标签。

<layout> 包含了一个必须填写的属性 class,用来指定具体的实现类,不过,如果该实现类的类型是 PatternLayout 时,那么可以不用填写。<layout> 也和 <appender> 一样,可以包含类似于 JavaBean 的配置标签。
<encoder> 标签包含一个必须填写的属性 class,用来指定具体的实现类,如果该类的类型是 PatternLayoutEncoder ,那么 class 属性可以不填。
如果想要往一个 logger 上绑定 appender,则使用以下方式:

  1.  
    <logger name="HELLO" level="debug">
  2.  
    <appender-ref ref="FILE" />
  3.  
    <appender-ref ref="STDOUT" />
  4.  
    </logger>

设置 Context Name

  1.  
    <configuration>
  2.  
    <contextName>myAppName</contextName>
  3.  
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  4.  
    <encoder>
  5.  
    <pattern>%d %contextName [%t] %level %logger{36} - %msg%n</pattern>
  6.  
    </encoder>
  7.  
    </appender>
  8.  
     
  9.  
    <root level="debug">
  10.  
    <appender-ref ref="STDOUT" />
  11.  
    </root>
  12.  
    </configuration>

 

变量替换

在 logback 中,支持以 ${varName} 来引用变量

定义变量

可以直接在 logback.xml 中定义变量

  1.  
    <configuration>
  2.  
     
  3.  
    <property name="USER_HOME" value="/home/sebastien" />
  4.  
     
  5.  
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
  6.  
    <file>${USER_HOME}/myApp.log</file>
  7.  
    <encoder>
  8.  
    <pattern>%msg%n</pattern>
  9.  
    </encoder>
  10.  
    </appender>
  11.  
     
  12.  
    <root level="debug">
  13.  
    <appender-ref ref="FILE" />
  14.  
    </root>
  15.  
    </configuration>

也可以通过大D参数来定义

java -DUSER_HOME="/home/sebastien" MyApp2

也可以通过外部文件来定义

  1.  
    <configuration>
  2.  
     
  3.  
    <property file="src/main/java/chapters/configuration/variables1.properties" />
  4.  
     
  5.  
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
  6.  
    <file>${USER_HOME}/myApp.log</file>
  7.  
    <encoder>
  8.  
    <pattern>%msg%n</pattern>
  9.  
    </encoder>
  10.  
    </appender>
  11.  
     
  12.  
    <root level="debug">
  13.  
    <appender-ref ref="FILE" />
  14.  
    </root>
  15.  
    </configuration>

外部文件也支持 classpath 中的文件

  1.  
    <configuration>
  2.  
     
  3.  
    <property resource="resource1.properties" />
  4.  
     
  5.  
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
  6.  
    <file>${USER_HOME}/myApp.log</file>
  7.  
    <encoder>
  8.  
    <pattern>%msg%n</pattern>
  9.  
    </encoder>
  10.  
    </appender>
  11.  
     
  12.  
    <root level="debug">
  13.  
    <appender-ref ref="FILE" />
  14.  
    </root>
  15.  
    </configuration>

外部文件的格式是 key-value 型。

USER_HOME=/home/sebastien

变量的作用域

变量有三个作用域

  • local

  • context

  • system

local 作用域在配置文件内有效,context 作用域的有效范围延伸至 logger context,system 作用域的范围最广,整个 JVM 内都有效。

logback 在替换变量时,首先搜索 local 变量,然后搜索 context,然后搜索 system。

如何为变量指定 scope ?

  1.  
    <configuration>
  2.  
     
  3.  
    <property scope="context" name="nodeId" value="firstNode" />
  4.  
     
  5.  
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
  6.  
    <file>/opt/${nodeId}/myApp.log</file>
  7.  
    <encoder>
  8.  
    <pattern>%msg%n</pattern>
  9.  
    </encoder>
  10.  
    </appender>
  11.  
     
  12.  
    <root level="debug">
  13.  
    <appender-ref ref="FILE" />
  14.  
    </root>
  15.  
    </configuration>

变量的默认值

在引用一个变量时,如果该变量未定义,那么可以为其指定默认值,做法是:

${aName:-golden}

运行时定义变量

需要使用 <define> 标签,指定接口 PropertyDfiner 对应的实现类。如下所示:

  1.  
    <configuration>
  2.  
     
  3.  
    <define name="rootLevel" class="a.class.implementing.PropertyDefiner">
  4.  
    <shape>round</shape>
  5.  
    <color>brown</color>
  6.  
    <size>24</size>
  7.  
    </define>
  8.  
     
  9.  
    <root level="${rootLevel}"/>
  10.  
    </configuration>

条件化处理配置文件

logback 允许在配置文件中定义条件语句,以决定配置的不同行为,具体语法格式如下:

  1.  
    <!-- if-then form -->
  2.  
    <if condition="some conditional expression">
  3.  
    <then>
  4.  
    ...
  5.  
    </then>
  6.  
    </if>
  7.  
     
  8.  
    <!-- if-then-else form -->
  9.  
    <if condition="some conditional expression">
  10.  
    <then>
  11.  
    ...
  12.  
    </then>
  13.  
    <else>
  14.  
    ...
  15.  
    </else>
  16.  
    </if>

示例:

  1.  
    <configuration debug="true">
  2.  
     
  3.  
    <if condition=‘property("HOSTNAME").contains("torino")‘>
  4.  
    <then>
  5.  
    <appender name="CON" class="ch.qos.logback.core.ConsoleAppender">
  6.  
    <encoder>
  7.  
    <pattern>%d %-5level %logger{35} - %msg %n</pattern>
  8.  
    </encoder>
  9.  
    </appender>
  10.  
    <root>
  11.  
    <appender-ref ref="CON" />
  12.  
    </root>
  13.  
    </then>
  14.  
    </if>
  15.  
     
  16.  
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
  17.  
    <file>${randomOutputDir}/conditional.log</file>
  18.  
    <encoder>
  19.  
    <pattern>%d %-5level %logger{35} - %msg %n</pattern>
  20.  
    </encoder>
  21.  
    </appender>
  22.  
     
  23.  
    <root level="ERROR">
  24.  
    <appender-ref ref="FILE" />
  25.  
    </root>
  26.  
    </configuration>

从JNDI 获取变量

使用 <insertFromJNDI> 可以从 JNDI 加载变量,如下所示:

  1.  
    <configuration>
  2.  
    <insertFromJNDI env-entry-name="java:comp/env/appName" as="appName" />
  3.  
    <contextName>${appName}</contextName>
  4.  
     
  5.  
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  6.  
    <encoder>
  7.  
    <pattern>%d ${CONTEXT_NAME} %level %msg %logger{50}%n</pattern>
  8.  
    </encoder>
  9.  
    </appender>
  10.  
     
  11.  
    <root level="DEBUG">
  12.  
    <appender-ref ref="CONSOLE" />
  13.  
    </root>
  14.  
    </configuration>

文件包含

可以使用 ?include> 标签在一个配置文件中包含另外一个配置文件,如下图所示:

  1.  
    <configuration>
  2.  
    <include file="src/main/java/chapters/configuration/includedConfig.xml"/>
  3.  
     
  4.  
    <root level="DEBUG">
  5.  
    <appender-ref ref="includedConsole" />
  6.  
    </root>
  7.  
     
  8.  
    </configuration>

被包含的文件必须有以下格式:

  1.  
    <included>
  2.  
    <appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
  3.  
    <encoder>
  4.  
    <pattern>"%d - %m%n"</pattern>
  5.  
    </encoder>
  6.  
    </appender>
  7.  
    </included>

支持从多种源头包含
从文件中包含

<include file="src/main/java/chapters/configuration/includedConfig.xml"/>

从 classpath 中包含

<include resource="includedConfig.xml"/>

从 URL 中包含

<include url="http://some.host.com/includedConfig.xml"/>

如果包含不成功,那么 logback 会打印出一条警告信息,如果不希望 logback 抱怨,只需这样做:

<include optional="true" ..../>

添加一个 Context Listener

LoggerContextListener 接口的实例能监听 logger context 上发生的事件,比如说日志级别的变化,添加的方式如下所示:

  1.  
    <configuration debug="true">
  2.  
    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
  3.  
    ....
  4.  
    </configuration>

以上是关于SpringBoot2基础配置详解的主要内容,如果未能解决你的问题,请参考以下文章

spring boot 约定大于配置详解

Spring Boot 配置加载顺序详解

SpringBoot2核心技术(基础入门)- 02SpringBoot2入门安装配置

SpringBoot2基础入门 --- 了解自动配置原理

SpringBoot2 整合Nacos组件,环境搭建和入门案例详解

SpringBoot2.0 基础案例(06):引入JdbcTemplate,和多数据源配置