slf4j门面层、适配层与实现详解

【1】slf4j概述

Simple Logging Facade for Java (slf4j) ,java的简单日志门面。作为java各种日志框架(例如,java.util.logging,logback,log4j)的门面或者抽象层,允许在部署时自由插入实际需要的日志jar。

门面,你可以理解为接口。没有日志框架的实际实现(除了有限的几个类,其他都是借口),通过该门面使用打印日志,你只需要调用slf4j的方法使用即可,无需理会底层使用的是log4j还是logging。

打个比方,slf4j作为一个入口(或者通道),一端是你的代码,一端是底层实际日志框架。

示例如下(此时你无需理会底层使用的是何种日志框架):

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

项目使用中,SLF4J有唯一一个强制性依赖-slf4j-api.jar,如果没有实际日志框架被绑定(没有添加实际日志 jar),将会默认一个无操作的实现,此时,将不会有日志被打印。

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

添加实际日志框架,上面警告信息将会消失。


日志相关包 slf4j打印日志必须的三个依赖包。slf4j**假设**使用log4j做为底层日志工具,运行以上程序需要三个包:

  • log4j-1.2.xx.jar、
  • slf4j-api-x.x.x.jar、
  • slf4j-log4j12-x.x.x.jar

Maven依赖如下:

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-log4j12</artifactId>
	<version>1.7.21</version>
</dependency>
<!--链接slf4j-api和log4j中间的适配器-->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-log4j12</artifactId>
	<version>1.7.21</version>
</dependency>

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.21</version>
</dependency>


【2】SLF4J与其他日志框架结合

SLF4J does not rely on any special class loader machinery.

To switch logging frameworks, just replace slf4j bindings on your class path.

For example, switch from java.util.logging to log4j, just replace slf4j-jdk14-1.8.0-alpha2.jar with slf4j-log4j12-1.8.0-alpha2.jar.

结合示意图如下:

这里写图片描述

浅蓝色为门面层,深蓝色为具体实现层(slf4j自身实现层),绿色为适配层,灰色为具体实现层(slf4j自身实现,中间需要适配层桥接)。


实际上,每一个SLF4J 绑定的对象都只会使用唯一的一个日志框架。例如 slf4j-log4j12-1.8.0-alpha2.jar 在项目编译的时候就只会绑定log4j。不要在你的项目中放置一个以上的绑定对象。

如同时放置log4j和logback将会抛异常(pom.xml中去掉log4j依赖):

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/Janus/.m2/repository/org/slf4j/slf4j-log4j12/1.6.1/slf4j-log4j12-1.6.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/Janus/.m2/repository/ch/qos/logback/logback-classic/1.1.2/logback-classic-1.1.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]

如果你使用了slf4j+logback,但是项目中却是用到了log4j怎么办?

请看第三部分!

这里写图片描述


【3】SLF4J桥接遗留的API

经常发现,有些你使用的容器(框架)依赖与log4j而不是slf4j。你会假想这些容器并不会转向slf4j在不就的将来。如果此时你使用slf4j和其他日志框架且不能丢掉你正在使用的容器,怎么办?

slf4j针对这种情况也做出了处理。

slf4j使用了"桥接模块"使得你对log4j, JCL 和 java.util.logging 的调用可以同样发生在slf4j上。看起来好像他们已经被slf4j所替代。

桥接示意图如下:

这里写图片描述


举个例子,如第二部分所说,项目中使用slf4j+logback,但是部分容器依赖于log4j。

这时需要在项目pom.xml中加入以下配置:

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.7</version>
</dependency>

这里写图片描述


【4】为什么使用slf4j##

除了无需理会底层日志框架,slf4j最重要的一个优势是:"{}",即占位符。

占位符是一个非常类似于在String的format()方法中的%s,因为它会在运行时被某个提供的实际字符串所替换。

这不仅降低了你代码中字符串连接次数,而且还节省了新建的String对象。即使你可能不需要那些对象,但上述依旧成立(取决于你的生产环境的日志级别)。

例如在DEBUG或者INFO级别的字符串连接。

因为String对象是不可修改的并且它们建立在一个String池中,它们消耗堆内存( heap memory)而且大多数时间他们是不被需要的。

当你的应用程序在生产环境以ERROR级别运行时候,一个String使用在DEBUG语句就是不被需要的。

如下面日志无论是否被打印,都将构建一个string对象:

logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

通过使用SLF4J,你可以在运行时延迟字符串的建立,这意味着只有需要的String对象才被建立。

而如果你已经使用log4j,那么你可以使用if条件中使用debug语句这种变通方案(SLF4J的占位符比这个好用得多)。

示例一如下:

if(logger.isDebugEnabled()) {
  logger.debug("Entry name: " + userName);
}

在SLF4J,我们不需要字符串连接而且不会导致暂时不需要的字符串消耗。取而代之的,我们在一个以占位符和参数传递实际值的模板格式下写日志信息。

After evaluating whether to log or not, and only if the decision is affirmative,
will the logger implementation format the message and replace the '{}' pair 
with the string value of entry. 

In other words, this form does not incur the cost of parameter construction 
in case the log statement is disabled.


示例二如下:

logger.debug("Entry name: {}" , userName);

你可能会在想很多个参数怎么办?

那么你可以选择使用变量参数版本的日志方法或者用以Object数组传递。

这是一个相当的方便和高效方法的打日志方法。在生产最终日志信息的字符串之前,这个方法会检查一个特定的日志级别是不是打开了,这不仅降低了内存消耗而且预先降低了CPU去处理字符串连接命令的时间。

示例三如下:

logger.debug("Value {} was inserted between {} and {}.", newVal, below, above);

三种日志方式输出对比:

①
logger.debug("The new entry is "+entry+".");


②
if(logger.isDebugEnabled()) {
  logger.debug("The new entry is "+entry+".");
}


③
logger.debug("The new entry is {}.", entry);

从性能来说,②③甚优良于1,③优良于②。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:白松林 返回首页