【Java开发】SLF4J 桥接器及其原理,让你的旧代码也可以起飞

news/2025/2/28 15:10:51/文章来源:https://www.cnblogs.com/o-O-oO/p/18601165

前言

虽然在新的项目中,我们一般使用推荐的SLF4J + 日志实现框架(Logback等)组合方式,但是对于一些旧的项目,已经使用了SLF4J之外的日志框架(如Log4j 1.x等),而且这些旧的代码我们无法直接修改源码,如果我们想使用SLF4J的API,那么就需要使用各种SLF4J的桥接器来实现。

注意,对于可以直接修改源码的项目,应该直接将旧的日志API修改为SLF4J API,而非使用本文介绍的SLF4J桥接器。修改源码的方式可以参考:SLF4J Migrator[1]。本文介绍的桥接器只适用于无法修改项目依赖的包的源码的情形。

为什么要桥接到 SLF4J

可能有的同学会有此疑问,明明之前的日志框架(如log4j 1.x)用得好好的,为什么要桥接到SLF4J门面日志框架呢?

首先我们要从企业级项目的思维来考虑这个问题,如果只是很小的项目记录日志,确实没有必要。但是对于企业级的项目来说,很有必要。
这里给出几个关键的原因:

1、日志门面(Facade)的统一性:SLF4J 作为一个日志门面,提供了一个简单的日志记录抽象。它允许开发者在编写代码时,不必关心底层使用的是哪个具体的日志框架。通过使用这些桥接器,即使项目中已经使用了其他日志框架(如 log4j、JCL、JUL),也可以轻松地将它们适配到 SLF4J 的接口上,从而实现日志记录的统一性。

2、避免日志框架的冲突:在大型项目中,可能会包含多个使用不同日志框架的库或模块。这些库或模块可能会因为日志框架的冲突而导致运行时错误。通过使用桥接器,可以将这些不同的日志框架适配到 SLF4J 上,从而避免冲突。

3、便于日志框架的切换:随着时间的推移,项目的需求可能会发生变化,可能需要切换到另一个日志框架。如果项目中使用了 SLF4J 作为日志门面,并且使用了相应的桥接器,那么切换日志框架将变得非常简单。只需替换桥接器和底层日志框架的依赖,而无需修改大量的日志记录代码。

4、解耦和模块化:桥接器的使用有助于将日志记录逻辑与业务逻辑解耦。通过将日志记录抽象到 SLF4J 层面上,开发者可以更加专注于业务逻辑的实现,而不必担心日志记录的具体实现细节。这有助于提高代码的可读性和可维护性。

5、性能优化:虽然桥接器本身可能会引入一些性能开销(因为需要额外的调用转换),但在某些情况下,它们可以帮助优化性能。例如,如果底层日志框架的性能不佳,但项目又无法立即切换到另一个性能更好的日志框架,那么可以使用桥接器来暂时缓解性能问题,并在后续逐步迁移到更好的日志框架上。

6、兼容性:有些老旧的库或框架可能只支持特定的日志框架(如 log4j 1.x)。为了与这些库或框架兼容,可以使用相应的桥接器来将它们适配到 SLF4J 上,从而允许项目使用更现代、更灵活的日志框架。

这些好处使得SLF4J和相应的桥接器成为许多企业级Java项目中日志记录的首选方案。

桥接器的原理

如果你的项目中已经使用了JCL、Log4j 1.x等老的日志框架,但希望使用SLF4J的API,这时候你可以使用SLF4J桥接器[2]来平滑过渡而不用修改原有代码,只需要修改依赖即可完成。

下面是SLF4J桥接旧的日志框架的原理图:

【图】SLF4J桥接旧的日志框架的原理图

上图表明了对旧的日志框架log4j 1.x、JCL、java.util.logging(JUL)和log4j 2的API的调用迁移到SLF4J的API,需要引入的包:

1、log4j-over-slf4j[3] : 允许Log4j 1.x用户(但不允许Log4j 2.x)将现有应用程序/库迁移到SLF4J,而无需更改原有代码,只需将log4j.jar文件替换为log4j-over-slf4j.jar

2、jcl-over-slf4j[4] :为了简化从JCL到SLF4J的迁移,SLF4J发布了jcl-over-slf4j.jar。这个jar文件实现了 JCL的公共API,但在底层使用了SLF4J,因此命名为 “JCL over SLF4J”。

3、jul-to-slf4j[5] :它路由所有传入的JUL记录到SLF4j API。

4、log4j-to-slf4j[6] :log4j 2迁移到SLF4J API,需要使用log4j-to-slf4j,这个包在上图没有体现,可参见:Log4j 2 与 SLF4J 互转的核心:log4j-slf4j-impl 和 log4j-to-slf4j

注意,这些桥接器的作用是为了将旧的日志API迁移到SLF4J API,在包的依赖时需要搞清楚其作用,不要造成循环。
具体如下:

1、log4j-over-slf4j.jar和slf4j-reload4j.jar(或slf4j-log4j.jar)不能同时存在,否则它们的API会互相重定向到对方API,导致无限循环。
2、jul-to-slf4j.jar和slf4j-jdk14.jar不能同时存在,否则它们的API会互相重定向到对方API,导致无限循环。
3、log4j-to-slf4j和log4j-slf4j-impl不能同时存在,否则它们的API会互相重定向到对方API,导致无限循环。

SLF4J + 日志实现框架组合方式的示例,参见【java开发】一文理清 Java 日志框架的来龙去脉

桥接器与适配器模式

所谓的桥接器本质上来说就是将一套API在不改变调用代码的情况下,将底层实现重定向到另一套API,这也是设计模式中适配器模式的基本原理。

这里,我将结合log4j 1.x的API桥接到SLF4J API的依赖包log4j-over-slf4j的源码讲解适配器模式的应用。

本文源码分析使用的log4j-over-slf4j版本是2.0.16,logback-classic版本是1.5.12,slf4j-api版本是2.0.16。

演示代码

为了演示将log4j 1.x桥接到SLF4J,需要完成以下两步:

1、移除log4j 1.x依赖。
2、添加SLF4J API和实现框架Logback,以及log4j 1.x桥接器依赖。

<!--log4j 1.x/reload4j 使用slf4j接口-->
<dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>2.0.16</version>
</dependency>
<!--logback 实现 slf4j2-->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.5.12</version>
</dependency>
<!--        移除log4j 1.x 的依赖-->
<!--        <dependency>-->
<!--            <groupId>log4j</groupId>-->
<!--            <artifactId>log4j</artifactId>-->
<!--            <version>1.2.17</version>-->
<!--        </dependency>-->

其中log4j-over-slf4j会自动引入依赖slf4j-api。不需要显式引入。

【图】演示代码的依赖

在resources目录下添加Logback配置文件logback.xml,内容如下:

<configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern></encoder></appender><root level="debug"><appender-ref ref="STDOUT" /></root>
</configuration>

测试代码如下:

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;public class HelloWorld {private static final Logger logger = LogManager.getLogger(HelloWorld.class);public static void main(String[] args) {logger.info("Hello, World!");logger.debug("This is a debug message.");logger.error("An error occurred.");}
}

其中,Logger类和LogManager类都是log4j 1.x中的核心类。

运行结果:

2024-11-29 14:27:40 INFO  org.learn.HelloWorld - Hello, World!
2024-11-29 14:27:40 DEBUG org.learn.HelloWorld - This is a debug message.
2024-11-29 14:27:40 ERROR org.learn.HelloWorld - An error occurred.

关于SLF4J获取Logger和打印日志的源码分析不是本文重点,可以参见:【Java开发】SLF4J 门面日志框架原理分析

适配器模式分析

让我们先回顾一下什么是适配器模式。

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的接口协同工作。
适配器模式的主要要素包括:

1、目标接口(Target Interface):这是客户端期望使用的接口。
2、适配者(Adaptee):这是现有的类,它具有不同的接口,但提供了所需的功能。
3、适配器(Adapter):这是一个类,它实现了目标接口,并持有适配者的引用。适配器负责将客户端的请求转换为适配者的请求。

适配器模式分为类适配器模式和对象适配器模式两种:
类适配器模式:通过继承适配者,并实现目标接口。

对象适配器模式:通过组合适配者,并实现目标接口。

关于适配器模式,参见:设计模式--适配器模式

log4j-over-slf4j 中的适配器模式

在log4j-over-slf4j中,org.apache.log4j.Logger类的父类org.apache.log4j.Category 类中实现了适配器模式。
1. 目标接口(Target Interface)

在 Category 类中,目标接口(这里只是类比适配器模式中的目标接口角色)是 Category 本身。这个类定义了日志记录的方法,如 debug, info, warn, error, fatal 等。客户端期望使用这些方法来记录日志。
2. 适配者(Adaptee)

适配者是 SLF4J 的 Logger 接口及其实现类 slf4jLogger。SLF4J 提供了日志记录的功能,但其接口与 log4j 的 Category 类不完全相同。例如,SLF4J 没有 fatal 方法,而是使用带有 FATAL 标记的 error 方法。
3. 适配器(Adapter)
Category 类充当适配器的角色。它持有一个 SLF4J 的 Logger 实例。Category 类通过委托的方式将日志记录的请求转发给 SLF4J 的 Logger 实例。

Category源码分析

1、构造函数:


protected org.slf4j.Logger slf4jLogger;
private org.slf4j.spi.LocationAwareLogger locationAwareLogger;Category(String name) {this.name = name;slf4jLogger = LoggerFactory.getLogger(name);if (slf4jLogger instanceof LocationAwareLogger) {locationAwareLogger = (LocationAwareLogger) slf4jLogger;}
}

构造函数中,Category 实例创建时会初始化一个 SLF4J 的 Logger 实例,并检查是否是 LocationAwareLogger。

2、日志记录方法:


public void debug(Object message) {differentiatedLog(null, CATEGORY_FQCN, LocationAwareLogger.DEBUG_INT, message, null);
}public void info(Object message) {differentiatedLog(null, CATEGORY_FQCN, LocationAwareLogger.INFO_INT, message, null);
}public void warn(Object message) {differentiatedLog(null, CATEGORY_FQCN, LocationAwareLogger.WARN_INT, message, null);
}public void error(Object message) {differentiatedLog(null, CATEGORY_FQCN, LocationAwareLogger.ERROR_INT, message, null);
}public void fatal(Object message) {differentiatedLog(FATAL_MARKER, CATEGORY_FQCN, LocationAwareLogger.ERROR_INT, message, null);
}void differentiatedLog(Marker marker, String fqcn, int level, Object message, Throwable t) {String m = convertToString(message);if (locationAwareLogger != null) {locationAwareLogger.log(marker, fqcn, level, m, null, t);} else {switch (level) {case LocationAwareLogger.TRACE_INT:slf4jLogger.trace(marker, m, (Throwable) t);break;case LocationAwareLogger.DEBUG_INT:slf4jLogger.debug(marker, m, (Throwable) t);break;case LocationAwareLogger.INFO_INT:slf4jLogger.info(marker, m, (Throwable) t);break;case LocationAwareLogger.WARN_INT:slf4jLogger.warn(marker, m, (Throwable) t);break;case LocationAwareLogger.ERROR_INT:slf4jLogger.error(marker, m, (Throwable) t);break;}}
}

这些方法通过调用 differentiatedLog 方法将日志记录的请求转发给 SLF4J 的 Logger 实例。differentiatedLog 方法会根据不同的日志级别调用相应的 SLF4J 方法。

3、日志级别检查方法:

public boolean isDebugEnabled() {return slf4jLogger.isDebugEnabled();
}public boolean isInfoEnabled() {return slf4jLogger.isInfoEnabled();
}public boolean isWarnEnabled() {return slf4jLogger.isWarnEnabled();
}public boolean isErrorEnabled() {return slf4jLogger.isErrorEnabled();
}

这些方法直接委托给 SLF4J 的 Logger 实例,检查当前日志级别是否启用。

在上面的演示代码中main方法就是客户端,调用了log4j 1.x的打印日志方法,这里以info为例进行分析,debug和error方法的原理类似。

在获取Logger实例时,会实例化其父类Category。其中适配者slf4jLogger是使用的Logback实现类ch.qos.logback.classic.Logger。

【图】获取到了适配者slf4jLogger

通过上面分析可以看出,Category 类封装了日志打印相关的目标API,同时在这些API中将日志记录的请求委托给 SLF4J 的 Logger 实例,从而实现了适配器模式。这样,客户端可以继续使用 log4j 的 Category 类进行日志记录,而实际的日志记录工作由 SLF4J 完成。下图是log4j 1.x的info方法的调用栈,最终调用的是Logback的实现。

【图】调用Logback的方法打印日志

至此,log4j-over-slf4j 中实现的适配器模式原理分析完毕。

推荐阅读

关于Java日志框架系列,之前已经从使用和源码层面进行了介绍,具体内容参见:

【Java开发】SLF4J 门面日志框架原理分析
【Java开发】Log4j 2 与 SLF4J 互转的核心:log4j-slf4j-impl 和 log4j-to-slf4j
【Java开发】一文理清 Java 日志框架的来龙去脉

参考资料

[1]SLF4J Migrator: https://www.slf4j.org/migrator.html
[2]Bridging legacy APIs: https://www.slf4j.org/legacy.html
[3]log4j-over-slf4j: https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-to-slf4j
[4]jcl-over-slf4j: https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j
[5]jul-to-slf4j: https://mvnrepository.com/artifact/org.slf4j/jul-to-slf4j
[6]log4j-to-slf4j: https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-to-slf4j

更多内容,请关注公众号 程序员Ink
个人观点,仅供参考

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

41. css溢出、定位、z-index属性

1. 溢出属性 1.1 概念 内容超出了标签的最大范围 overflow的值与描述:visible 默认值。内容不会被修剪,会呈现在元素框之外。hidden 内容会被修剪,并且其余内容是不可见的。scroll 无论内容是否超出范围,都会显示滚动条。auto 内容没有超出范围,不会显示滚动条。 内容超出…

成员推理攻击的防御方法及简单实现

defense_methods 2024年12月14日更新 前面我们实现了多种模型的成员推理攻击,接下来我们将针对其防御方法及其原理进行一个简单的介绍,并进行简单的代码实现,给用户提供一个详细的帮助文档。 目录 基本介绍 常用方法介绍 简单实现 基本介绍 机器学习中的成员推理攻击指的是攻…

【Java开发】SLF4J 门面日志框架原理分析

SLF4J的门面设计模式 SLF4J(Simple Logging Facade for Java)是一套日志接口,它提供了一种一致的API来使用不同的日志框架,如java.util.logging(JUL)、Logback、Log4j、Log4j 2等。SLF4J的设计基于门面(Facade)设计模式,这种设计模式为子系统中的一组接口提供一个统一…

活动报名:Voice Agent 开发者分享会丨RTE Meetup

引入 voice agent 的口语学习应用 Speak 估值已达 10 亿美元 Voice Agent 开发者分享会 一同探索语音驱动的下一代人机交互界面,一场 voice agent builder 的小规模深度交流会。RTE Meetup 迎来第六期!12 月 15 日(周日)上午,线上举办。本次活动将 聚焦 voice agent 领域 …

智慧灌区系统平台建设方案

在现代农业发展中,水资源的高效利用是提升农业产量和质量的关键。智慧灌区系统平台的建设,正是为了实现这一目标。该平台通过集成测绘地理信息与遥感技术,对灌区进行全方位的监测和管理,以提高水资源的利用效率和农业的可持续发展。一、智慧灌区系统平台的建设背景随着全球…

Go支付中台方案:多平台兼容与多项目对接

Go支付中台方案:多平台兼容与多项目对接 原创 就业陪跑训练营 王中阳2024年12月10日 09:02 湖南 6人欢迎点击下方👇关注我,记得星标哟~ 文末会有重磅福利赠送王中阳 专注程序员的就业辅导、简历优化、学习路线规划。私信我666,免费发你价值999元的学习资料。 344篇原创内容…

转载:【AI系统】推理参数

本文将介绍 AI 模型网络参数方面的一些基本概念,以及硬件相关的性能指标,为后面让大家更了解模型轻量化做初步准备。值得让人思考的是,随着深度学习的发展,神经网络被广泛应用于各种领域,模型性能的提高同时也引入了巨大的参数量和计算量(如下图右所示),一般来说模型参…

转载:【AI系统】推理引擎示例:AscendCL

AscendCL 作为华为 Ascend 系列 AI 处理器的软件开发框架,为用户提供了强大的编程支持。通过 AscendCL,开发者可以更加高效地进行 AI 应用的开发和优化,从而加速 AI 技术在各个领域的应用和落地。AscendCL 的易用性和高效性,使得它成为开发 AI 应用的重要工具之一。 本文将…

转载:【AI系统】轻量级CNN模型综述

神经网络模型被广泛的应用于工业领域,并取得了巨大成功。然而,由于存储空间以及算力的限制,大而复杂的神经网络模型是难以被应用的。首先由于模型过于庞大,计算参数多(如下图所示),面临内存不足的问题。其次某些场景要求低延迟,或者响应要快。所以,研究小而高效的 CNN 模…

转载:【AI系统】推理系统介绍

推理系统是一个专门用于部署神经网络模型,执行推理预测任务的 AI 系统。它类似于传统的 Web 服务或移动端应用系统,但专注于 AI 模型的部署与运行。通过推理系统,可以将神经网络模型部署到云端或者边缘端,并服务和处理用户的请求。因此,推理系统也需要应对模型部署和服务生…

转载:【AI系统】推理系统引言

在深入探究 AI 编译原理之后,将进一步迈向一个与日常生活紧密相连的新领域。这个领域无处不在,无论是日常使用的购物应用、观看在线视频的平台,还是钟爱的游戏,它们都与这个领域息息相关。该领域,便是推理系统与推理引擎。 那么,推理系统与推理引擎究竟是什么呢?它们之间…

转载:【AI系统】TVM 实践案例

在本文我们探讨一下,如何利用 AI 编译器在新的硬件上部署一个神经网络,从算法设计到实际运行,有哪些需要考虑的地方?本文将以 TVM 为例,首先介绍一下 TVM 的工作流:导入模型。TVM 可以从 TensorFlow、PyTorch、ONNX 等框架导入模型。 转换为 Relay。Relay 是 TVM 的中间表…