百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Java基础:Java日志框架:slf4j作用及其实现原理

ccwgpt 2024-10-14 08:39 24 浏览 0 评论

简单回顾门面模式

slf4j是门面模式的典型应用,因此在讲slf4j前,我们先简单回顾一下门面模式,

门面模式,其核心为外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。用一张图来表示门面模式的结构为:

门面模式的核心为Facade即门面对象,门面对象核心为几个点:

  • 知道所有子角色的功能和责任
  • 将客户端发来的请求委派到子系统中,没有实际业务逻辑
  • 不参与子系统内业务逻辑的实现

大致上来看,对门面模式的回顾到这里就可以了,开始接下来对SLF4J的学习。

我们为什么要使用slf4j

我们为什么要使用slf4j,举个例子:

我们自己的系统中使用了logback这个日志系统
我们的系统使用了A.jar,A.jar中使用的日志系统为log4j
我们的系统又使用了B.jar,B.jar中使用的日志系统为slf4j-simple
这样,我们的系统就不得不同时支持并维护logback、log4j、slf4j-simple三种日志框架,非常不便。

解决这个问题的方式就是引入一个适配层,由适配层决定使用哪一种日志系统,而调用端只需要做的事情就是打印日志而不需要关心如何打印日志,slf4j或者commons-logging就是这种适配层,slf4j是本文研究的对象。

从上面的描述,我们必须清楚地知道一点:slf4j只是一个日志标准,并不是日志系统的具体实现。理解这句话非常重要,slf4j只做两件事情:

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

slf4j-simple、logback都是slf4j的具体实现,log4j并不直接实现slf4j,但是有专门的一层桥接slf4j-log4j12来实现slf4j。

为了更理解slf4j,我们先看例子,再读源码,相信读者朋友会对slf4j有更深刻的认识。

slf4j应用举例

上面讲了,slf4j的直接/间接实现有slf4j-simple、logback、slf4j-log4j12,我们先定义一个pom.xml,引入相关jar包:

 1 
 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 4 <modelVersion>4.0.0</modelVersion>
 5 
 6 <groupId>org.xrq.log</groupId>
 7 <artifactId>log-test</artifactId>
 8 <version>1.0.0</version>
 9 <packaging>jar</packaging>
10 
11 <name>log-test</name>
12 <url>http://maven.apache.org</url>
13 
14 <properties>
15 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
16 </properties>
17 
18 <dependencies>
19 <dependency>
20 <groupId>junit</groupId>
21 <artifactId>junit</artifactId>
22 <version>4.11</version>
23 <scope>test</scope>
24 </dependency>
25 <dependency>
26 <groupId>org.slf4j</groupId>
27 <artifactId>slf4j-api</artifactId>
28 <version>1.7.25</version>
29 </dependency>
30 <dependency>
31 <groupId>ch.qos.logback</groupId>
32 <artifactId>logback-classic</artifactId>
33 <version>1.2.3</version>
34 </dependency>
35 <dependency>
36 <groupId>org.slf4j</groupId>
37 <artifactId>slf4j-simple</artifactId>
38 <version>1.7.25</version>
39 </dependency>
40 <dependency>
41 <groupId>log4j</groupId>
42 <artifactId>log4j</artifactId>
43 <version>1.2.17</version>
44 </dependency>
45 <dependency>
46 <groupId>org.slf4j</groupId>
47 <artifactId>slf4j-log4j12</artifactId>
48 <version>1.7.21</version>
49 </dependency>
50 </dependencies>
51 </project>

写一段简单的Java代码:

 1 @Test
 2 public void testSlf4j() {
 3 Logger logger = LoggerFactory.getLogger(Object.class);
 4 logger.error("123");
 5 }

接着我们首先把上面pom.xml的第30行~第49行注释掉,即不引入任何slf4j的实现类,运行Test方法,我们看一下控制台的输出为:

看到没有任何日志的输出,这验证了我们的观点:slf4j不提供日志的具体实现,只有slf4j是无法打印日志的

接着打开logback-classic的注释,运行Test方法,我们看一下控制台的输出为:

看到我们只要引入了一个slf4j的具体实现类,即可使用该日志框架输出日志。

最后做一个测验,我们把所有日志打开,引入logback-classic、slf4j-simple、log4j,运行Test方法,控制台输出为:

和上面的差别是,可以输出日志,但是会输出一些告警日志,提示我们同时引入了多个slf4j的实现,然后选择其中的一个作为我们使用的日志系统。

从例子我们可以得出一个重要的结论,即slf4j的作用:只要所有代码都使用门面对象slf4j,我们就不需要关心其具体实现,最终所有地方使用一种具体实现即可,更换、维护都非常方便

slf4j实现原理

上面看了slf4j的示例,下面研究一下slf4j的实现,我们只关注重点代码。

slf4j的用法就是常年不变的一句"Logger logger = LoggerFactory.getLogger(Object.class);",可见这里就是通过LoggerFactory去拿slf4j提供的一个Logger接口的具体实现而已,LoggerFactory的getLogger的方法实现为:

 1 public static Logger getLogger(Class<?> clazz) {
 2 Logger logger = getLogger(clazz.getName());
 3 if (DETECT_LOGGER_NAME_MISMATCH) {
 4 Class<?> autoComputedCallingClass = Util.getCallingClass();
 5 if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
 6 Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
 7 autoComputedCallingClass.getName()));
 8 Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
 9 }
10 }
11 return logger;
12 }

从第2行开始跟代码,一直跟到LoggerFactory的bind()方法:

 1 private final static void bind() {
 2 try {
 3 Set<URL> staticLoggerBinderPathSet = null;
 4 // skip check under android, see also
 5 // http://jira.qos.ch/browse/SLF4J-328
 6 if (!isAndroid()) {
 7 staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
 8 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
 9 }
10 // the next line does the binding
11 StaticLoggerBinder.getSingleton();
12 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
13 reportActualBinding(staticLoggerBinderPathSet);
14 fixSubstituteLoggers();
15 replayEvents();
16 // release all resources in SUBST_FACTORY
17 SUBST_FACTORY.clear();
18 } catch (NoClassDefFoundError ncde) {
19 String msg = ncde.getMessage();
20 if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
21 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
22 Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
23 Util.report("Defaulting to no-operation (NOP) logger implementation");
24 Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
25 } else {
26 failedBinding(ncde);
27 throw ncde;
28 }
29 } catch (java.lang.NoSuchMethodError nsme) {
30 String msg = nsme.getMessage();
31 if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
32 INITIALIZATION_STATE = FAILED_INITIALIZATION;
33 Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
34 Util.report("Your binding is version 1.5.5 or earlier.");
35 Util.report("Upgrade your binding to version 1.6.x.");
36 }
37 throw nsme;
38 } catch (Exception e) {
39 failedBinding(e);
40 throw new IllegalStateException("Unexpected initialization failure", e);
41 }
42 }

这个地方第7行是一个关键,看一下代码:

 1 static Set<URL> findPossibleStaticLoggerBinderPathSet() {
 2 // use Set instead of list in order to deal with bug #138
 3 // LinkedHashSet appropriate here because it preserves insertion order
 4 // during iteration
 5 Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
 6 try {
 7 ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
 8 Enumeration<URL> paths;
 9 if (loggerFactoryClassLoader == null) {
10 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
11 } else {
12 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
13 }
14 while (paths.hasMoreElements()) {
15 URL path = paths.nextElement();
16 staticLoggerBinderPathSet.add(path);
17 }
18 } catch (IOException ioe) {
19 Util.report("Error getting resources from path", ioe);
20 }
21 return staticLoggerBinderPathSet;
22 }

这个地方重点其实就是第12行的代码,getLogger的时候会去classpath下找STATIC_LOGGER_BINDER_PATH,STATIC_LOGGER_BINDER_PATH值为"org/slf4j/impl/StaticLoggerBinder.class",即所有slf4j的实现,在提供的jar包路径下,一定是有"org/slf4j/impl/StaticLoggerBinder.class"存在的,我们可以看一下:

我们不能避免在系统中同时引入多个slf4j的实现,所以接收的地方是一个Set。大家应该注意到,上部分在演示同时引入logback、slf4j-simple、log4j的时候会有警告:

这就是因为有三个"org/slf4j/impl/StaticLoggerBinder.class"存在的原因,此时reportMultipleBindingAmbiguity方法控制台输出语句:

1 private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
2 if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
3 Util.report("Class path contains multiple SLF4J bindings.");
4 for (URL path : binderPathSet) {
5 Util.report("Found binding in [" + path + "]");
6 }
7 Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
8 }
9 }

那网友朋友可能会问,同时存在三个"org/slf4j/impl/StaticLoggerBinder.class"怎么办?首先确定的是这不会导致启动报错,其次在这种情况下编译期间,编译器会选择其中一个StaticLoggerBinder.class进行绑定,这个地方sfl4j也在reportActualBinding方法中报告了绑定的是哪个日志框架:

1 private static void reportActualBinding(Set<URL> binderPathSet) {
2 // binderPathSet can be null under Android
3 if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
4 Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");
5 }
6 }

对照上面的截图,看最后一行,确实是"Actual binding is of type..."这句。

最后StaticLoggerBinder就比较简单了,不同的StaticLoggerBinder其getLoggerFactory实现不同,拿到ILoggerFactory之后调用一下getLogger即拿到了具体的Logger,可以使用Logger进行日志输出。

相关推荐

RACI矩阵:项目管理中的角色与责任分配利器

作者:赵小燕RACI矩阵RACI矩阵是项目管理中的一种重要工具,旨在明确团队在各个任务中的角色和职责。通过将每个角色划分为负责人、最终责任人、咨询人和知情人四种类型,RACI矩阵确保每个人都清楚自己...

在弱矩阵组织中,如何做好项目管理工作?「慕哲制图」

慕哲出品必属精品系列在弱矩阵组织中,如何做好项目管理工作?【慕哲制图】-------------------------------慕哲制图系列0:一图掌握项目、项目集、项目组合、P2、商业分析和NP...

Scrum模式:每日站会(Daily Scrum)

定义每日站会(DailyScrum)是一个Scrum团队在进行Sprint期间的日常会议。这个会议的主要目的是为了应对Sprint计划中的不断变化,确保团队能够有效应对挑战并达成Sprint目标。为...

大家都在谈论的敏捷开发&amp;Scrum,到底是什么?

敏捷开发作为一种开发模式,近年来深受研发团队欢迎,与瀑布式开发相比,敏捷开发更轻量,灵活性更高,在当下多变环境下,越来越多团队选择敏捷开发。什么是敏捷?敏捷是一种在不确定和变化的环境中,通过创造和响应...

敏捷与Scrum是什么?(scrum敏捷开发是什么)

敏捷是一种思维模式和哲学,它描述了敏捷宣言中的一系列原则。另一方面,Scrum是一个框架,规定了实现这种思维方式的角色,事件,工件和规则/指南。换句话说,敏捷是思维方式,Scrum是规定实施敏捷哲学的...

敏捷项目管理与敏捷:Scrum流程图一览

敏捷开发中的Scrum流程通常可以用一个简单的流程图来表示,以便更清晰地展示Scrum框架的各个阶段和活动。以下是一个常见的Scrum流程图示例:这个流程图涵盖了Scrum框架的主要阶段和活动,其中包...

一张图掌握项目生命周期模型及Scrum框架

Mockito 的最佳实践(mock方法)

记得以前面试的时候,面试官问我,平常开发过程中自己会不会测试?我回答当然会呀,自己写的代码怎么不测呢。现在想想我好像误会他的意思了,他应该是想问我关于单元测试,集成测试以及背后相关的知识,然而当时说到...

EffectiveJava-5-枚举和注解(java枚举的作用与好处)

用enum代替int常量1.int枚举:引入枚举前,一般是声明一组具名的int常量,每个常量代表一个类型成员,这种方法叫做int枚举模式。int枚举模式是类型不安全的,例如下面两组常量:性别和动物种...

Maven 干货 全篇共:28232 字。预计阅读时间:110 分钟。建议收藏!

Maven简介Maven这个词可以翻译为“知识的积累”,也可以翻译为“专家”或“内行”。Maven是一个跨平台的项目管理工具。主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。仔...

Java单元测试框架PowerMock学习(java单元测试是什么意思)

前言高德的技术大佬在谈论方法论时说到:“复杂的问题要简单化,简单的问题要深入化。”这句话让我感触颇深,这何尝不是一套编写代码的方法——把一个复杂逻辑拆分为许多简单逻辑,然后把每一个简单逻辑进行深入实现...

Spring框架基础知识-第六节内容(Spring高级话题)

Spring高级话题SpringAware基本概念Spring的依赖注入的最大亮点是你所有的Bean对Spring容器的存在是没有意识的。但是在实际的项目中,你的Bean必须要意识到Spring容器...

Java单元测试浅析(JUnit+Mockito)

作者:京东物流秦彪1.什么是单元测试(1)单元测试环节:测试过程按照阶段划分分为:单元测试、集成测试、系统测试、验收测试等。相关含义如下:1)单元测试:针对计算机程序模块进行输出正确性检验工作...

揭秘Java代码背后的质检双侠:JUnit与Mockito!

你有没有发现,现在我们用的手机App、逛的网站,甚至各种智能设备,功能越来越复杂,但用起来却越来越顺畅,很少遇到那种崩溃、卡顿的闹心事儿?这背后可不是程序员一拍脑袋写完代码就完事儿了!他们需要一套严谨...

单元测试框架哪家强?Junit来帮忙!

大家好,在前面的文章中,给大家介绍了以注解和XML的方式分别实现IOC和依赖注入。并且我们定义了一个测试类,通过测试类来获取到了容器中的Bean,具体的测试类定义如下:@Testpublicvoid...

取消回复欢迎 发表评论: