SLF4J源码分析

2023-05-16

介绍

官网:http://www.slf4j.org/

github:https://github.com/qos-ch/slf4j

SLF4J(Simple Logging Facade for Java),它为Java的日志系统提供了一套统一的接口(门面),即:作为各种日志框架(java.util.logging,logback,log4j)的抽象。

通过引入SLF4J,可以使项目与logging具体的实现分离,在提供了一致的接口的同时,提供了灵活选择logging实现的能力。(引入SLF4J的库/应用意味着仅添加一个强制性依赖项slf4j-api.jar)

1、为什么要设计出一个日志接口的抽象层?

我们都知道,日志对于一个系统来说非常重要。同样,我们在开发出了一个库时,也需要打印一些调试或者运行日志,而我们系统往往会引入大量的第三方库。这是,就会遇到一个问题:假设我们系统使用的是Log4j日志框架,引入了RMQ库使用的是Logback框架,这时系统就出现了两个日志框架,维护起来非常麻烦。

解决这个问题的方法是引入一个适配层。例如:

如果我们都是通过SLF4J这种统一的接口,那么RMQ库在发布时就无需带着具体日志框架的实现,这样我们系统引入RMQ后,仍然使用的是我们系统中引入的日志实现了,这样就方便了维护。

slf4j只做两件事情:

  • 提供日志接口
  • 提供获取具体日志对象的方法

说明:这种抽象的思想,在软件开发中很常见。

2、SLF4J和JCL区别:

在SLF4J之前,Apache Common Logging(即Jakarta Commons Logging,简称JCL)也提供了类似的功能(即:统一的日志接口)。它与SLF4J的区别在于:

  • JCL即提供了统一的接口,也提供了一套默认的实现;SLF4J则只提供了接口层
  • JCL采用运行时绑定,通过Classloader体系加载相应的logging实现;SLF4J采用了静态绑定
  • SLF4J在接口易用性上更有优势,大大减少了不必要的日志拼接:
    • JCL为了避免无效的字符串拼接,一般需要通过if判断:
    • SLF4J则提供了占位符"{}",只在必要的情况下才会进行日志字符串处理和拼接:
//JCL
if (log.isInfoEnabled()){
  log.info("testid:"+id+",cont:"+JSON.toJSONString(jsonstr));
}

//slf4j
log.info("testid:{},cont:{}",id,JSON.toJSONString(jsonstr));

推荐使用slf4j中占位符原因主要有两点:

  • 当设置的日志级别高于某条代码中的日志级别时,使用占位符可以免掉字符串拼接操作;
  • 占位符底层使用的是StringBuilder进行的拼接,性能比“+”要好;

注:在SLF4J和JCL中,推荐使用前者。

3、SLF4J使用:

SLF4J的使用非常简单:

  • 引入SLF4J依赖 (slf4j-api.jar)
  • 引入一种SLF4J的实现,比如:logback、log4j...

然后:

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

public MyClass {
  Logger logger = LoggerFactory.getLogger(MyClass.class);

  puhblic void method() {
    logger.info("hello world...");
  }
}

注:从1.6.0开始,如果在类路径上未找到绑定,则SLF4J将默认为无操作实现;

下图从SLF4J官网中找到的一个图,表示了各种实现类和SLF4J的关系:

 

总之,SLF4J接口及其各种适配器非常简单,不依赖任何类加载器,所以SLF4J不会遇到类加载器问题或Commons Logging(JCL)所观察​​到的内存泄漏。实际上,每个SLF4J绑定在编译时都进行了硬连线,以使用一个且仅一个特定的日志记录框架。

静态绑定原理

和Apache Common Logging不同,SLF4j采用了静态绑定来确定具体日志库。静态绑定就是:

  • 每一个具体的日志库定义一个包名和类名都相同的类: org.slf4j.impl.StaticLoggerBinder,这个类的功能就是调用具体的日志库,该类存放在Adaptation layer(适配层)或者native implementation of slf4j-api(实现包)的jar包中;(该类在slf4j-api打成jar包时被mvn移除)
  • SLF4j的使用者只要把具体日志库对应的Adaptation layer或者native implementation of slf4j-api的jar包放入classpath中,SLF4j便会装载(load)对应版本的org.slf4j.impl.StaticLoggerBinder,从而调用具体的日志库;
  • slf4j-api.jar中通过classLoader.getResources("org/slf4j/impl/StaticLoggerBinder.class")来加载classpath中具体的日志库中的StaticLoggerBinder类;

SLF4J相比JCL的一大优势是采用了静态绑定,避免了在OSGI等场景中通过classloader动态绑定造成的困扰。

参考:https://blog.csdn.net/weixin_34248023/article/details/91891106

1、1.7.25版本的slf4j-api.jar静态绑定过程分析:

1.1)源码分析:

在demo中可知,使用SLF4J的LoggerFactory.getLogger(Class<?>)方法获取一个Logger对象,这个过程完成了和具体日志实现类的绑定。通过slf4j-api.jar源码,SLF4J是调用bind()方法实现的绑定。

1)bind()方法:

  1. 调用findPossibleStaticLoggerBinderPathSet()方法获取classpath上所有的org/slf4j/impl/StaticLoggerBinder.class,用来报告(没有找到也不会报错);
  2. 执行StaticLoggerBinder.getSingleton()实现静态绑定,如果没有日志实现框架,则抛出异常;
  3. 执行reportActualBinding()方法

 

2)findPossibleStaticLoggerBinderPathSet()方法:

通过jdk提供的ClassLoader.getStstemResources()方法获取指定资源的URI。

 

可以发现,在slf4j-api.jar包中根本没有org.slf4j.impl.StaticLoggerBinder 这个类,所以,如果没有具体的日志实现库,那么在执行到StaticLoggerBinder.getSingleton()方法时就会抛出NoClassDeffoundException

 

3)日志实现库:

slf4j-log4j12库中的org.slf4j.impl.StaticLoggerBinder

1.2)疑问:

通过slf4j源码,LoggerFactory.java文件有一行import org.slf4j.impl.StaticLoggerBinder; 但是上面我们发现在slf4j-api.jar中居然没有该org.slf4j.impl.StaticLoggerBinder类,也就是说slf4j-api这个工程是无法编译通过的,又是如何打成slf4j-api.jar的呢?

写一个工程A,类似sfl4j-api,然后把StaticLoggerBinder类删掉,工程虽然报错,但是可以通过mvn install打包成功;

写一个工程B,引入A.jar,然后调用其中方法,会发现报错:Unresolved compilation problem: 从A.jar包中查看相应的LoggerFatory类,居然是这样的:

可以发现:虽然上面可以用mvn打包成功,但是由于A工程是一个编译有问题的工程,反编译字节码文件可以看到方法全都抛出异常,这说明在打包时,LoggerFactory类生成的字节码文件是不完整的,带有错误的。

通过slf4j-api源码可以发现,其实在slf4j-api工程中是有org.slf4j.impl.StaticLoggerBinder.java类的,只是在mvn打包的时候通过ant插件,将org.slf4j.impl.StaticLoggerBinder.class移除掉了。骗过了jdk,使得LoggerFactory.class是一个完整的,可以校验通过的字节码文件。

1.3)总结:

先来明确一下 Java 的绑定(Binding)的概念,Java 本身只支持静态(static)绑定与运行时(runtime)绑定,直到与 JDK 1.6 版本一起发布的 JSR269 才能进行编译时绑定,编译时绑定最有代表的是lomok 在编译过程中修改字节码。

1.7.25版本的SFL4j 的 logger 实例是 new 出来的(通过StaticLoggerBinder单例),绑定 LogContext 的 StaticLoggerBinder(中介类) 是写死的,编译时并没有处理任何逻辑,也谈不上什么编译时绑定,而且翻遍了 SLF4j 文档也没有找到任何有关编译时绑定的材料,官方只提到了 “static binding”, 所以,SLF4j使用的是 Convention over Configuration(CoC)– 惯例优于配置原则,不管是什么日志框架,只加载org.slf4j.impl.StaticLoggerBinder。这完美契合了软件设计的 KISS(Keep It Simple, Stupid)原则。

而 Commons-logging 魔法(magic)一样的动态加载虽然设计很高大上,在应用领域却直接被打脸,低效率、与 OSGi 共同使用所导致的 ClassLoader 问题更是火上浇油,所以员外与大家共勉,写代码切勿炫技。

参考:

https://juejin.im/post/6844903574116237326

https://www.jianshu.com/p/b562b7ff499f

2、1.8版本的slf4j-api.jar静态绑定过程分析:

SLF4J 1.8中最大的改进就是摒弃了之前的hard code的代码绑定(要求具体实现日志框架中必须要有一个org.slf4j.impl.StaticLoggerBinder.java),而是使用了更加优雅、耦合更松的SPI方式进行服务发现。我们看看1.8版本slf4j-api中对日志绑定的改进:

  • 提供了org.slf4j.spi.SLF4JServiceProvider服务接口用于SPI绑定
  • 改进了org.slf4j.LoggerFactory.bind()的实现,采用SPI方式进行SLF4JServiceProvider服务发现和绑定
  • 不再支持1.8版本以前的按照约定的类型StaticXxxBinder约定类名进行绑定的方式

由此可见,1.8版本和之前的版本是不兼容的(http://www.slf4j.org/codes.html#version_mismatch)。而且1.8往上的版本都是beta,没有一个是stable/release的。

说明:(官网)

从客户端的角度来看,slf4j-api的所有版本都是兼容的。只需要确保绑定的版本与slf4j-api.jar的版本匹配即可。在初始化时,如果SLF4J怀疑可能存在sfl4j-api与绑定版本不匹配的问题,它将发出有关可疑不匹配的警告。

1.1)源码分析:

1)bind方法:

2)findServiceProviders()方法:

3)日志实现库:

slf4j-api:1.8.0-beta-2版本,对应的logback-classic版本为logback-classic:1.3.0-alpha4。为了兼容1.8的SLF4J,logback-classic提供了SPI服务配置文件,如下图。这样,在启动阶段,SLF4J就可以通过ServiceLoader找到logback-classic并进行注册了。

同时,最新版的logback也去掉了org.slf.impl包,彻底摒弃了老版本SLF4J的支持。

同样,在slf4j-log4j12-1.8版本中,也是去掉了org.slf.impl包,提供了SPI服务配置文件:

总结:

slf4j-api1.8版本整个流程和1.7的基本一致,除了采用了更优雅的服务发现机制,在其他方面,SLF4J 1.8与之前版本差别很小。

参考:

https://www.jianshu.com/p/6cf21fb18639

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SLF4J源码分析 的相关文章

随机推荐

  • C_INCLUDES must be under the source or output directories

    Android项目N版本切到O版本 xff0c 同一个模块去mm xff0c 报C INCLUDES must be under the source or output directories错误 后来才找到原因 xff0c 是因为该模块
  • “MobaXterm X11 proxy: Unsupported authorisation protocol”解决方法

    服务器无法显示GUI图片问题 在解决这个问题的时候 xff0c 可能很多人都搜到了用sudo的方法 xff0c 但是在我们没有管理权限的情况下 xff0c 这个问题也是能解决的 报错信息 我的报错信息 xff1a MobaXterm X11
  • Linux 并发与竞争

    Linux是一个多任务操作系统 xff0c 肯定会存在多个任务共同操作同一段内存或者设备的情况 xff0c 多个任务甚至中断都能访问的资源叫做共享资源 xff0c 就和共享单车一样 在驱动开发中要注意对 共享资源的保护 xff0c 也就是要
  • linux man手册和设置中文版man手册

    http man he net linux 设置中文版man手册
  • win10宽带连接断网自动重连

    文章目录 1 断开网络连接 xff0c 重命名网络连接2 bat代码 xff1a 检测到断线自动重连3 设置开机自动执行3 1 方式一 xff1a 任务计划程序3 2 方式二 xff1a 用vbs代码开机运行bat 1 断开网络连接 xff
  • ubuntu20环境下使用DevStack安装Openstack-Wallaby(单节点、多节点)

    文章目录 一 单节点部署1 环境准备1 1 镜像源1 2 pip源1 3 安装依赖包 2 OpenStack安装 wallaby2 1 添加 96 stack 96 用户2 2 设置代理2 3 下载devstack xff0c 使用 96
  • 【操作系统】页面置换算法

    页面置换算法 在进程运行过程中 xff0c 若需要访问的物理块不在内存中 xff0c 就需要通过一定的方式来将页面载入内存 xff0c 而此时内存很可能已无空闲空间 xff0c 因此就需要一定的算法来选择内存中要被置换的页面 xff0c 这
  • 前端 好看实用的颜色大全(16进制)

  • 解决linux写入ntfs盘时报错:只读文件系统

    2018 10 28 更新 可能因为在挂载wimdows盘后 xff0c 强制关机造成的 xff0c 可使用 sudo ntfsfix dev 来修复 其中 xff0c 为具体哪个盘 xff0c 例如sudo ntfsfix dev sda
  • 【计算机网络】TCP IP通信处理过程

    1 数据包首部 每个分层中都会对所发送的数据附加一个首部 xff0c 其中包含了该层必要的信息 xff0c 如发送端地址 接收端地址以及协议等相关信息 2 发送数据包 1 xff09 应用程序处理 进行编码处理 xff08 相当于表示层功能
  • 【高性能定时器】 时间轮

    时间轮 简述 顾名思义 xff0c 时间轮就像一个轮子 xff0c 在转动的时候外界会指向轮子不同的区域 xff0c 该区域就可以被使用 因此只要将不同时间的定时器按照一定的方法散列到时间轮的不同槽 xff08 即时间轮划分的区域 xff0
  • 系统调用中断(EINTR)与SIGCHLD信号的处理

    一 被中断的系统调用 EINTR 的理解 1 慢系统调用是 xff1f 2 慢系统调用的类别3 EINTR产生的原因5 一般处理方法 二 SIGCHLD信号的处理 1 SIGCHLD信号的产生2 SIGCHLD信号的处理3 不处理SIGCH
  • 定时器与超时的设置

    一 相关时间函数 1 gettimeofday 2 time 3 clock 二 间隔定时器 1 setitimerval 2 getitimerval 3 实时定时器的使用 三 为阻塞操作设置超时 1 alarm 2 给read 设置读超
  • 解决tomcat启动时,端口被占用问题

    有时候我们启动tomcat的时候 xff0c 会提示端口被占用 xff0c 我们可以用下面的方法解决这个问题 1 进入cmd 2 输入netstat ano findstr 8080 xff08 注 xff1a 8080为被占用的端口名 x
  • Maven实战(六)--- dependencies与dependencyManagement的区别

    在上一个项目中遇到一些 jar 包冲突的问题 xff0c 之后还有很多人分不清楚 dependencies 与 dependencyManagement 的区别 xff0c 本篇文章将这些区别总结下来 1 DepencyManagement
  • VSFTP服务器使用retrieveFileStream返回null的问题

    VSFTP服务器使用retrieveFileStream返回null的问题 最近在使用vsftp在文件存储服务 xff0c 发现使用retrieveFileStream获取文件流的时候 xff0c 怎么获取都是空的 xff0c 网上有说返回
  • Android常用的一些make命令

    1 make jX X表示数字 xff0c 这个命令将编译Android系统并生成镜像 xff0c XX表示可以使用到的CPU核数 xff0c 这在配置好的电脑上特别有用 xff0c 公司的16核ubuntu服务器执行make j16只要不
  • 建造者模式详解

    建造者模式 建造者模式 xff08 Bulider Pattern xff09 是将一个复杂对象的构建过程与它的实现表示分离 xff0c 使得同样的构建过程可以创建不同的表示 xff0c 属于创建型模式 使用创建者模式对于用户而言只需要制定
  • LAMP架构简述

    目录 一 LAMP架构简述 二 各组件作用 三 构建LAMP平台 3 1编译安装Apache httpd服务 3 1 1 关闭防火墙 xff0c 将安装Apache所需软件包转到 opt目录下 3 1 2 安装环境依赖包 3 1 3 设置安
  • SLF4J源码分析

    介绍 官网 xff1a http www slf4j org github xff1a https github com qos ch slf4j SLF4J xff08 Simple Logging Facade for Java xff