SLF4J门面日志框架源码探索 | 京东云技术团队

news2024/11/19 7:44:57

1 SLF4J介绍

SLF4J即Simple Logging Facade for Java,它提供了Java中所有日志框架的简单外观或抽象。因此,它使用户能够使用单个依赖项处理任何日志框架,例如:Log4j,Logback和JUL(java.util.logging)。通过在类路径中插入适当的 jar 文件(绑定),可以在部署时插入所需的日志框架。如果要更换日志框架,仅仅替换依赖的slf4j bindings。比如,从java.util.logging替换为log4j,仅仅需要用slf4j-log4j12-1.7.28.jar替换slf4j-jdk14-1.7.28.jar。

2 SLF4J源码分析

我们通过代码入手,层层加码,直观感受SLF4J打印日志,并跟踪代码追本溯源。主要了解,SLF4J是如何作为门面和其他日志框架进行解耦。

2.1 pom只引用依赖slf4j-api,版本是1.7.30

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

2.1.1 执行一个Demo

public class HelloSlf4j {

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

2.1.2 日志提示信息

绑定org.slf4j.impl.StaticLoggerBinder失败。如果在类路径上没有找到绑定,那么 SL​​F4J 将默认为无操作实现

2.1.3 跟踪源码

点开方法getLogger(),可以直观看到LoggerFactory使用静态工厂创建Logger。通过以下方法,逐步点击,报错也很容易找到,可以在bind()方法看到打印的异常日志信息。

org.slf4j.LoggerFactory#getLogger(java.lang.Class<?>)
org.slf4j.LoggerFactory#getLogger(java.lang.String)
org.slf4j.LoggerFactory#getILoggerFactory
org.slf4j.LoggerFactory#performInitialization
org.slf4j.LoggerFactory#bind

private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class "org.slf4j.impl.StaticLoggerBinder".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        } finally {
            postBindCleanUp();
        }
    }

进一步分析绑定方法findPossibleStaticLoggerBinderPathSet(),可以发现在当前ClassPath下查询了所有该路径的资源“org/slf4j/impl/StaticLoggerBinder.class”,这里可能没有加载到任何文件,也可能绑定多个,对没有绑定和绑定多个的场景进行了友好提示。这里通过路径加载资源的目的主要用来对加载的各种异常场景提示。

再往下代码StaticLoggerBinder.getSingleton()才是实际的绑定,并且获取StaticLoggerBinder的实例。这里如果反编译,你会发现根本没有这个类StaticLoggerBinder。

如果没有加载到文件,正如上边demo执行的结果一样,命中NoSuchMethodError异常,并打印没有绑定场景的提示信息。

方法findPossibleStaticLoggerBinderPathSet()的源码如下,可以发现类加载器通过路径获取URL资源。

            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }

2.2 pom引用依赖logback-classic

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

2.2.1 执行demo

可以看到正常的打印日志信息,并且没有任何异常

2.2.2 跟踪源码

这个时候如果再点击进入方法StaticLoggerBinder.getSingleton(),发现类StaticLoggerBinder是由包logback-classic提供的,并且实现了SLF4J中的接口LoggerFactoryBinder。StaticLoggerBinder的创建用到了单例模式,该类主要目的返回一个创建Logger的工厂。这里实际返回了ch.qos.logback.classic.LoggerContext的实例,再由该实例创建ch.qos.logback.classic.Logger。

UML类图如下:

2.3 pom再引入log4j-slf4j-impl

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.9.1</version>
        </dependency>

2.3.1 执行demo

打印日志如下,提示绑定了两个StaticLoggerBinder.class,但最终实际绑定的是ch.qos.logback.classic.util.ContextSelectorStaticBinder。这里边也验证了一旦一个类被加载之后,全局限定名相同的类就无法被加载了。这里Jar包被加载的顺序直接决定了类加载的顺序。

SLF4J: Found binding in [jar:file:/D:/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.9.1/log4j-slf4j-impl-2.9.1.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 [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
18:19:43.521 [main] INFO com.cj.HelloSlf4j - Hello World info

2.4 log4j-slf4j-impl和logback-classic的引入位置变换

如果Pom文件先引入log4j-slf4j-impl,再引入logback-classic

2.4.1 执行demo

根据日志打印结果,可以看到实际绑定的是org.apache.logging.slf4j.Log4jLoggerFactory;但是没有正常打印出日志,需要进行log4j2的日志配置。说明实际绑定的是og4j-slf4j-impl包中的org/slf4j/impl/StaticLoggerBinder.class文件;这里也验证了如果有引入了多个桥接包,实际绑定的是先加载到的文件;

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.9.1/log4j-slf4j-impl-2.9.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.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.apache.logging.slf4j.Log4jLoggerFactory]
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. Set system property 'log4j2.debug' to show Log4j2 internal initialization logging.

2.5 类加载方式的变化

2.5.1 slf4j-api-1.7.30版本的打包技巧

反编译看slf4j-api-1.7.30-sources.jar,发现压根没有这个类org.slf4j.impl.StaticLoggerBinder,他怎么会编译成功呢?猜想是不是打包的时候把这个类排除掉了呢?通过git下载源码发现slf4j源码其实是有这个文件的,org/slf4j/impl/StaticLoggerBinder.class;这里使用了一个小技巧,打包的时候把实现类排除掉了,虽然不太优雅,但是思路很巧妙。

2.5.2 slf4j-api-2.0.0版本引入SPI(Service Provider Interface)

该版本通过使用SPI方式进行实现类的加载,感觉比之前的实现方式优雅了很多。桥接包只需要在这个位置:META-INF/services/,定义一个文件org.slf4j.spi.SLF4JServiceProvider(命名为SLFJ4提供的接口名),并且文件中指定实现类。只要引入这个桥接包,就可以适配到对应实现的日志框架。

以下是SPI方式加载的源码

private static List<SLF4JServiceProvider> findServiceProviders() {
        ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
        List<SLF4JServiceProvider> providerList = new ArrayList();
        Iterator var2 = serviceLoader.iterator();

        while(var2.hasNext()) {
            SLF4JServiceProvider provider = (SLF4JServiceProvider)var2.next();
            providerList.add(provider);
        }

        return providerList;
     }

2.5.3 类加载方式对比

2.6 SLF4J官方已经实现绑定的日志框架

slf4j已经提供了常用日志框架的桥接包,以及详细的文档描述,使用起来非常简单。
下图是SLF4J官网中提供的,表示了各种日志实现框架和SLF4J的关系:

2.7 总结

  • SLF4J API旨在一次绑定一个且仅一个底层日志框架。而且引入SLF4J后,不管是否可以加载到StaticLoggerBinder,或者加载到多个StaticLoggerBinder,都进行友好提示,用户体验上考虑都很周到。如果类路径上存在多个绑定,SLF4J 将发出警告,列出这些绑定的位置。当类路径上有多个绑定可用时,应该选择一个希望使用的绑定,然后删除其他绑定。
  • 单纯看SLF4J源码,其实整体设计实现上都很简单明确,定位非常清楚,就是做好门面。
  • 鉴于 SLF4J 接口及其部署模型的简单性,新日志框架的开发人员应该会发现编写 SLF4J 绑定非常容易。
  • 对于目前比较主流的日志框架都通过实现适配进行兼容支持。只要用户选择了SLF4J,就可以确保以后变更日志框架的自由。

3 SLF4J设计模式的使用

在slf4j中用到了一些经典的设计模式,比如门面模式、单例模式、静态工厂模式等,我们来分析以下几种设计模式。

3.1 门面模式(Facade Pattern)

1)解释

门面模式,也叫外观模式,要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。使用了门面模式,使客户端调用变得更加简单。

Slf4j制定了log日志的使用标准,提供了高层次的接口, 我们编码过程只需要依赖接口Logger和工厂类 LoggerFactory就可以实现日志的打印,完全不用关心日志内部的实现细节是logback实现的方式,还是log4j的实现方式。

2)图解

        Logger logger = LoggerFactory.getLogger(HelloSlf4j.class);
        logger.info("Hello World info");

3)优点

解耦,减少系统的相互依赖。所有的依赖都是对门面对象的依赖,与子系统无关,业务层的开发不需要关心底层日志框架的实现及细节,在编码的时候也不需要考虑日后更换框架所带来的成本。
接口和实现分离,屏蔽了底层的实现细节,面向接口编程。

3.2 单例模式(Singleton Pattern)

1)解释

单例模式,确保一个类仅有一个实例,并提供一个访问它的全局访问点。
在SLF4J的适配包中都需要实现类StaticLoggerBinder,而类StaticLoggerBinder的实现就用了单例模式,而且是最简单的实现方法,在静态初始化器中直接new StaticLoggerBinder(),提供全局访问方法获取该实例。

2)UML图

3)优点

在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例
单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。

提供了对唯一实例的受控访问。

在内存里只有一个实例,减少了内存的开销,提高系统的性能。

4 启示

  • 尽管SLF4J整体代码短小但很精炼,可见门面模式运用好的威力。门面模式也为我们提供了对于多版本的实现如何统一定义接口以及兼容提供了参考。
  • SLF4J定义和实现方案对用户都很友好,同时又提供了各种桥接包,进行完善的文档指导使用。总之各项用户体验都很棒,这也许也是SLF4J目前最受欢迎的原因之一吧。
  • 我们要多思考面向接口编程的思想,降低代码耦合度,提高代码扩展性。
  • 使用SPI的方式,优雅的加载扩展实现。
  • 好产品是设计出来的,更是优化迭代出来的。

5 参考资料

  • slf4j官网:https://www.slf4j.org/manual.html
    类加载:
  • https://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html
  • https://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html
  • https://www.ibm.com/docs/en/sdk-java-technology/7.1?topic=cl-parent-delegation-model-1

作者:京东物流 曹俊

来源:京东云开发者社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/669945.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单片机Hard fault 产生原因和错误跟踪的方法

一、单片机 Hard fault产生的原因 Hard fault产生的原因有两方面&#xff0c;硬件方面和软件方面。 ①硬件方面常见原因&#xff1a; 电源设计有错误&#xff0c;造成器件供电不稳&#xff1b; 电源质量不好&#xff0c;纹波&#xff0c;噪声过大&#xff1b; 器件接地不良&…

干货分享|HOOPS Web平台和Polygonica进行增材制造的云CAM服务示例

这篇文章提供了一个示例项目&#xff0c;展示了使用 Machineworks Polygonica 和 HOOPS Web 平台进行增材制造的云 CAM 服务。该项目作为一个示例&#xff0c;说明了如何在服务器端使用 Polygonica 与 HOOPS Communicator 和 Exchange 来开发云服务。 它涵盖了增材制造 CAM 的…

Android问题笔记-Android Studio编译报错:2 files found with path.....

点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&…

看物联网技术ZETA如何帮助场馆实现数智化管理升级?

背景介绍&#xff1a; 江宁足球训练基地位于南京江宁区上坊镇境内,是江苏省女足及青少年足球发展基地。该基地总占地面积为333300平方米&#xff0c;其中房屋建筑面积有19000平方米&#xff0c;健身房350平方米&#xff0c;拥有9个标准足球场&#xff0c;曾承办多场甲级足球赛…

Java多线程与并发

1、JDK版本的选择 选择JDK8、JDK11进行讲解的原因&#xff1a;Oracle长期支持 2、进程和线程的区别 进程和线程的由来 3、进程与线程的区别 进程是资源分配的最小单位,线程是cpu调度的最小单位. 所有与进程相关的资源&#xff0c;都被记录在PCB(进程控制块)中。进程是抢占…

day08 教你用英语过海关

前言 &#x1f3e0;个人主页&#xff1a;我是沐风晓月 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是沐风晓月&#xff0c;阿里云社区博客专家 &#x1f609;&#x1f609; &#x1f495; 座右铭&#xff1a; 先努力成长自己&#xff0c;再帮助更多的人,一起加油进…

基于深度学习FasterRCNN模型Restnet50 的生活垃圾智能分类(准确率达84%)-含python工程全源码

目录 前言总体设计系统整体结构图系统流程图 运行环境1. 硬件环境2. Python 环境 模块实现1. 数据预处理2. 数据加载3. 模型构建4. 模型训练及保存5. 模型加载与调用 系统测试1. 模型准确率2. 分类别准确率 工程源代码下载其它资料下载 前言 本项目基于Faster R-CNN模型&#…

mmrotate框架基本使用

1、如何将类交给mmrotate框架容器管理 容器&#xff1a;框架中现有基本容器包括DATASETS, BACKBONES, LOSSES, DETECTORS。初始化容器&#xff1a;Registry(‘backbone’)中’backbone’为容器初始化配置文件。#/mmdet/models/builder.py 部分代码 from mmcv.utils import Re…

【Lisp】【Python】在CAD中用插件获取选中字块的文字,在rhino中批量生成图层

文章目录 1 get_selected_text.lsp1.1 使用方法LISP代码解析1.2 动图 2 Rhino中使用PythonScript批量建立图层.py2.1 直接生成2.2 带颜色生成 2.3 动图展示 1 get_selected_text.lsp 1.1 使用方法 用记事本复制以下代码&#xff0c;改文件名为get_selected_text.lsp (defun c:…

springboot高校宿舍报修管理系统-计算机毕设 附源码83946

springboot高校宿舍报修管理系统 摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实…

【AI绘图 丨 Stable_diffusion 系列教程四】— Window 环境 | Stable Diffusion入门教程 及安装(全篇)

&#x1f449;腾小云导读 最近&#xff0c;AI图像生成引人注目&#xff0c;它能够根据文字描述生成精美图像&#xff0c;这极大地改变了人们的图像创作方式。Stable Diffusion作为一款高性能模型&#xff0c;它生成的图像质量更高、运行速度更快、消耗的资源以及内存占用更小&a…

MATLAB+JAVA的混合开发

近期项目中需要使用matlab跟java做混合开发。主要记录一下&#xff0c;此次开发遇到的问题点。 环境&#xff1a;使用的matlab版本是 R2018b。 当前状况&#xff1a;MATLAB代码已经编写好&#xff0c;且运行成功。需要打成jar包才可以被java调用。 步骤一&#xff1a; 按照…

OpenJdk 和 oracleJdk

OpenJDK的网站&#xff08;https://jdk.java.net/&#xff09;通常仅显示最新的几个版本&#xff0c;对于更早的版本&#xff0c;可能不再在主页面上列出。这是因为随着时间的推移&#xff0c;Java社区通常会专注于支持和维护最新的版本&#xff0c;并鼓励开发者尽可能地使用最…

开源软件介绍——国内和国际主要开源社区

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来看一看国内和国际上有哪些主要开源社区。 开源社区的定义 开源社区又称为开放源代码社区&#xff0c;一般由拥有共同兴趣爱好的人组成。根据相应的开源软件许可证协议公布软件源代码的网络平台&a…

【深度学习】5-2 与学习相关的技巧 - 权重的初始值

在神经网络的学习中&#xff0c;权重的初始值特别重要。实际上&#xff0c;设定什么样的权重初始值&#xff0c;经常关系到神经网络的学习能否成功。本节将介绍权重初始值的推荐值&#xff0c;并通过实验确认神经网络的学习是否会快速进行。 可以将权重初始值设为0吗 后面我们…

搜索引擎的个性化搜索:为何搜索结果因人而异

&#x1f482; 个人网站:【海拥】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 目录 前言搜索引擎的工作原理…

uniapp uView2 字体加载错误提示处理(字体离线方案)

最近老是收到有人反馈 uView2的字体图标报错&#xff0c;具体错误提示如下图 这个报错的原因有2种情况 at.licdn.com 网站维护&#xff0c;无法加载&#xff1b;国内这些小程序平台的开发工具日常抽风&#xff0c;代码能跑&#xff0c;但就是报错&#xff0c;简直离谱&#x…

漏洞复现 D-Link DCS 密码泄露漏洞

0x01 漏洞描述 D-link DCS是一款网络摄像机&#xff0c;工作温度为0-50℃。D-link DCS系统存在密码泄露漏洞&#xff0c;攻击者通过漏洞可以获取后台权限。 0x02 漏洞复现 fofa&#xff1a;app“D_Link-DCS-4622” 1.使用poc进行账号密码查看&#xff0c;得到密码登录即可 …

网络安全是什么?怎么学

网络安全基础 安全的定义&#xff1a; 1&#xff09;一种能够识别和消除不安全因素的能力&#xff1b; 2&#xff09;安全是一个持续的过程网络安全是一门涉及计算机科学、网络技术、通信技术、密码技术、信息安全技术、应用数学、数论、信息论等多种学科的综合性科学。 网络…

Redis原理 - 数据结构的底层实现

原文首更地址&#xff0c;阅读效果更佳&#xff01; Redis原理 - 数据结构的底层实现 | CoderMast编程桅杆https://www.codermast.com/database/redis/redis-datastruct-underlying-implementation.html 动态字符串SDS #基本概念 Redis 中保存的 Key 是字符串&#xff0c;V…