学习Java日志框架之——搞懂日志门面(JCL+SLF4J)

news/2024/5/4 1:04:03/文章来源:https://blog.csdn.net/A_art_xiang/article/details/129702868

文章目录

  • 一、什么是日志门面
    • 1、门面模式(外观模式)
    • 2、日志门面
  • 二、了解JCL
    • 1、JCL组件结构
    • 2、JCL案例
      • (1)JCL默认实现
      • (2)导入log4j测试原有程序
  • 三、SLF4J简介
  • 四、SLF4J基本使用
    • 1、入门案例
    • 2、动态打印信息
    • 3、打印异常信息
  • 五、SLF4J集成其他日志实现
    • 1、分析
    • 2、代码
    • 3、slf4j同时集成多个日志实现结果分析
      • (1)slf4j-simple日志实现的基础上,又集成了logback
      • (2)在logback之后,又集成了slf4j-simple依赖
      • (3)只保留logback依赖
      • (4)总结
      • (5)源码分析
    • 4、使用slf4j-nop禁止日志打印
    • 5、slf4j集成log4j(使用适配器)
    • 6、slf4j集成jul(使用适配器)
    • 7、桥接器的使用
      • (1)源码分析

一、什么是日志门面

1、门面模式(外观模式)

门面模式(Facade Pattern),是GoF23种设计模式其中之一,也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。

外观模式主要是体现了Java中的一种好的封装性。更简单的说,就是对外提供的接口要尽可能的简单。

2、日志门面

常见的日志实现:JUL、log4j、logback、log4j2
常见的日志门面 :JCL、slf4j
出现顺序 :log4j -->JUL–>JCL–> slf4j --> logback --> log4j2

JUL、log4j、logback、log4j2这几种日志框架,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性。

为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。

二、了解JCL

全称为Jakarta Commons Logging,是Apache提供的一个通用日志API。

用户可以自由选择第三方的日志组件作为具体实现,像log4j,或者jdk自带的jul, common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。

当然,common-logging内部有一个Simple logger的简单实现,但是功能很弱。所以使用common-logging,通常都是配合着log4j以及其他日志框架来使用。

使用它的好处就是,代码依赖是common-logging而非log4j的API, 避免了和具体的日志API直接耦合,在有必要时,可以更改日志实现的第三方库。

JCL 有两个基本的抽象类:
Log:日志记录器
LogFactory:日志工厂(负责创建Log实例)

1、JCL组件结构

在这里插入图片描述

2、JCL案例

添加依赖:

<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version>
</dependency>

(1)JCL默认实现

JCL默认的情况下,会使用JUL日志框架做日志的记录操作。

JCL使用原则:如果有log4j,优先使用log4j,如果没有任何第三方日志框架的时候,我们使用的就是JUL。

Log log = LogFactory.getLog(JCLTest01.class);
log.info("info信息");

(2)导入log4j测试原有程序

在集成了log4j环境后,使用的又是log4j,通过测试观察,虽然日志框架发生了变化,但是代码完全没有改变。

我们分析一下LogFactory的getLog执行逻辑:

// org.apache.commons.logging.LogFactory#getLog(java.lang.String)
public static Log getLog(String name) {switch (logApi) {case LOG4J:return Log4jDelegate.createLog(name);case SLF4J_LAL:return Slf4jDelegate.createLocationAwareLog(name);case SLF4J:return Slf4jDelegate.createLog(name);default:// Defensively use lazy-initializing delegate class here as well since the// java.logging module is not present by default on JDK 9. We are requiring// its presence if neither Log4j nor SLF4J is available; however, in the// case of Log4j or SLF4J, we are trying to prevent early initialization// of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly// trying to parse the bytecode for all the cases of this switch clause.return JavaUtilDelegate.createLog(name);}
}

我们发现,会通过logApi来判断加载的日志Log。

在LogFactory的静态代码块中,会挨个判断加载的日志类,会根据类的存在与否,依次加载Log4j、slf4j、JUL:

static {ClassLoader cl = LogFactory.class.getClassLoader();try {// Try Log4j 2.x APIcl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger");logApi = LogApi.LOG4J;}catch (ClassNotFoundException ex1) {try {// Try SLF4J 1.7 SPIcl.loadClass("org.slf4j.spi.LocationAwareLogger");logApi = LogApi.SLF4J_LAL;}catch (ClassNotFoundException ex2) {try {// Try SLF4J 1.7 APIcl.loadClass("org.slf4j.Logger");logApi = LogApi.SLF4J;}catch (ClassNotFoundException ex3) {// Keep java.util.logging as default}}}
}

三、SLF4J简介

简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。 当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。所以我们可以得出SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接。

官方网站: https://www.slf4j.org/

通常,我们依赖的某些组件依赖于SLF4J以外的日志API。我们可能还假设这些组件在不久的将来不会切换到SLF4J。为了处理这种情况,SLF4J附带了几个桥接模块,这些模块会将对log4j,JCL和java.util.logging API的调用重定向为行为,就好像是对SLF4J API进行的操作一样。

四、SLF4J基本使用

1、入门案例

引入依赖:

<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!--slf4j 自带的简单日志实现 -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.25</version>
</dependency>

SLF4J对日志的级别划分trace、debug、info、warn、error五个级别

  • trace:日志追踪信息
  • debug:日志详细信息
  • info:日志的关键信息 默认打印级别
  • warn:日志警告信息
  • error:日志错误信息

如果在没有任何其他日志实现框架集成的基础之上,slf4j使用的就是自带的框架slf4j-simple,slf4j-simple也必须以单独依赖的形式导入进来。

// 都是slf4j包下的
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
logger.trace("trace信息");
logger.debug("debug信息");
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");

2、动态打印信息

我们输出动态的信息时,也可以使用占位符的形式来代替字符串的拼接。

我们有些时候输出的日志信息,需要我们搭配动态的数据,有可能是信息,有可能是数据库表中的数据。总之我们这样做最大的好处就是能够让日志打印变得更加灵活,如果是通过拼接字符串的形式,不仅麻烦,而且更重要的是可读性差。

我们的日志打印是支持以替代符的形式做日志信息拼接的,一般情况下,几乎所有的日志实现产品,都会提供这种基础功能。

// 都是slf4j包下的
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
String name = "zs";
int age = 23;
//logger.info("学生信息-姓名:"+name+";年龄:"+age);
logger.info("学生信息-姓名:{},年龄:{}",name,age);

{}作为占位符,后面的参数代表花括号要替换的值。

3、打印异常信息

一般情况下,我们在开发中的异常信息,都是记录在控制台上(我们开发环境的一种日志打印方式),我们会根据异常信息提取出有用的线索,来调试bug。

但是在真实生产环境中(项目上线),对于服务器或者是系统相关的问题,在控制台上其实也会提供相应的异常或者错误信息的输出,但是这种错误输出方式(输出的时间,位置,格式…)都是服务器系统默认的。

我们可以通过日志技术,选择将异常以日志打印的方式,进行输出,查看输出的时间,位置(控制台,文件),格式,完全由我们自己去进行定义。

Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);try {Class.forName("aaa");
} catch (ClassNotFoundException e) {//打印栈追踪信息//e.printStackTrace();logger.info("XXX类中的XXX方法出现了异常,请及时关注信息");//e是引用类型对象,不能根前面的{}做有效的字符串拼接//logger.info("具体错误是:{}",e);//我们不用加{},直接后面加上异常对象e即可logger.info("具体错误是:",e);
}

五、SLF4J集成其他日志实现

1、分析

(图片来自官网)
在这里插入图片描述
SLF4J日志门面,共有3种情况对日志实现进行绑定:

  • 1.在没有绑定任何日志实现的基础之上,日志是不能够绑定实现任何功能的,值得大家注意的是,通过我们刚刚的演示,slf4j-simple是slf4j官方提供的。使用的时候,也是需要导入依赖,自动绑定到slf4j门面上。如果不导入,slf4j 核心依赖是不提供任何实现的。
  • 2.logback和simple(包括nop)都是slf4j门面时间线后面提供的日志实现,所以API完全遵循slf4j进行的设计。那么我们只需要导入想要使用的日志实现依赖,即可与slf4j无缝衔接。值得一提的是nop虽然也划分到实现中了,但是他是指不实现日志记录。
  • 3.log4j和JUL都是slf4j门面时间线前面的日志实现,所以API不遵循slf4j进行设计。需要通过适配桥接的技术,完成的与日志门面的衔接。

2、代码

以下测试均使用同样的java代码,因为主要测试和学习包依赖以及slf4j的基本使用。

Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);try {Class.forName("aaa");
} catch (ClassNotFoundException e) {logger.info("具体错误是:",e);
}

3、slf4j同时集成多个日志实现结果分析

(1)slf4j-simple日志实现的基础上,又集成了logback

<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!--slf4j 自带的简单日志实现 -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.25</version>
</dependency><!-- logback依赖 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>

我们查看执行结果:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.m2/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.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.slf4j.impl.SimpleLoggerFactory]
[main] INFO com.demo.slf4j.test01.SLF4JTest01 - 具体错误是:
java.lang.ClassNotFoundException: aaa

通过测试,日志是打印出来了 java.lang.ClassNotFoundException: aaa
但是通过SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]这一句我们可以发现,虽然集成了logback,但是我们现在使用的仍然是slf4j-simple。

只要出现了这个提示:LF4J: Class path contains multiple SLF4J bindings.,在slf4j环境下,证明同时出现了多个日志实现。

(2)在logback之后,又集成了slf4j-simple依赖

<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!-- logback依赖 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
<!--slf4j 自带的简单日志实现 -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.25</version>
</dependency>

此时打印结果:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.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:/C:/Users/Admin/.m2/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.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]
15:38:55.178 [main] INFO com.demo.slf4j.test01.SLF4JTest01 - 具体错误是:
java.lang.ClassNotFoundException: aaa

我们发现,默认使用的就是logback依赖,但是仍然提示有多个依赖。

(3)只保留logback依赖

<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!-- logback依赖 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>

执行结果:

15:40:51.974 [main] INFO com.demo.slf4j.test01.SLF4JTest01 - 具体错误是:
java.lang.ClassNotFoundException: aaaat java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)

我们发现,slf4j门面使用的就是logback日志实现,这一次没有多余的提示信息。
所以在实际应用的时候,我们一般情况下,仅仅只是做一种日志实现的集成就可以了。

(4)总结

通过以上测试,我们会发现虽然底层的日志实现变了,但是源代码完全没有改变。

这就是日志门面给我们带来最大的好处,在底层真实记录日志的时候,不需要应用去做任何的了解应用只需要去记slf4j的API就可以了。

值得一提的是,我们虽然底层使用的是log4j做的打印,但是从当前代码使用来看,我们其实使用的仍然是slf4j日志门面,至于日志是log4j打印的(或者是logback打印的)都是由slf4j进行操作的,我们不用操心。

(5)源码分析

我们进入getLogger的源码:

// org.slf4j.LoggerFactory#getLogger(java.lang.Class<?>)
public static Logger getLogger(Class<?> clazz) {Logger logger = getLogger(clazz.getName()); // 执行重载方法if (DETECT_LOGGER_NAME_MISMATCH) {Class<?> autoComputedCallingClass = Util.getCallingClass();if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),autoComputedCallingClass.getName()));Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");}}return logger;
}
// org.slf4j.LoggerFactory#getLogger(java.lang.String)
public static Logger getLogger(String name) {ILoggerFactory iLoggerFactory = getILoggerFactory(); // 获取Logger工厂实现return iLoggerFactory.getLogger(name);
}
// org.slf4j.LoggerFactory#getILoggerFactory
public static ILoggerFactory getILoggerFactory() {// 双重锁检查,// INITIALIZATION_STATE :默认为0,表示是否被初始化过if (INITIALIZATION_STATE == UNINITIALIZED) {// UNINITIALIZED:0synchronized (LoggerFactory.class) {if (INITIALIZATION_STATE == UNINITIALIZED) {INITIALIZATION_STATE = ONGOING_INITIALIZATION; // ONGOING_INITIALIZATION:1performInitialization(); // 核心初始化方法}}}switch (INITIALIZATION_STATE) {case SUCCESSFUL_INITIALIZATION:return StaticLoggerBinder.getSingleton().getLoggerFactory();case NOP_FALLBACK_INITIALIZATION:return NOP_FALLBACK_FACTORY;case FAILED_INITIALIZATION:throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);case ONGOING_INITIALIZATION:// support re-entrant behavior.// See also http://jira.qos.ch/browse/SLF4J-97return SUBST_FACTORY;}throw new IllegalStateException("Unreachable code");
}

我们进入到performInitialization方法:

// org.slf4j.LoggerFactory#performInitialization
private final static void performInitialization() {bind(); // 绑定if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {versionSanityCheck();}
}// org.slf4j.LoggerFactory#bind
private final static void bind() {try {// N多个日志框架的实现Set<URL> staticLoggerBinderPathSet = null;// skip check under android, see also// http://jira.qos.ch/browse/SLF4J-328if (!isAndroid()) {// 查找所有日志实现staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();// 对于绑定多实现的处理,打印日志报告reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);}// the next line does the bindingStaticLoggerBinder.getSingleton();INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;// 打印最终使用的日志实现reportActualBinding(staticLoggerBinderPathSet);fixSubstituteLoggers();replayEvents();// release all resources in SUBST_FACTORYSUBST_FACTORY.clear();} 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);}
}

我们看一下findPossibleStaticLoggerBinderPathSet方法:

// org.slf4j.LoggerFactory#findPossibleStaticLoggerBinderPathSet
static Set<URL> findPossibleStaticLoggerBinderPathSet() {// use Set instead of list in order to deal with bug #138// LinkedHashSet appropriate here because it preserves insertion order// during iteration// 有序不可重复的集合对象Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();try {ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();// 声明了枚举类的路径,经过if else判断,以获取系统中都有哪些日志实现// STATIC_LOGGER_BINDER_PATH:org/slf4j/impl/StaticLoggerBinder.classEnumeration<URL> paths;if (loggerFactoryClassLoader == null) {paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);} else {paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);}// 将path放入LinkedHashSet并返回while (paths.hasMoreElements()) {URL path = paths.nextElement();staticLoggerBinderPathSet.add(path);}} catch (IOException ioe) {Util.report("Error getting resources from path", ioe);}return staticLoggerBinderPathSet;
}

StaticLoggerBinder就是我们slf4j的适配器。

在每个日志的适配器中(log4j、logback、jul等),都有一个StaticLoggerBinder类,如果引入了对应的适配器包,就会查找到该类。
比如说log4j的适配器中StaticLoggerBinder类,会默认会创建Log4jLoggerFactory:

private StaticLoggerBinder() {loggerFactory = new Log4jLoggerFactory();
}

此时我们继续回到bind方法的reportMultipleBindingAmbiguity逻辑,用于打印日志报告:

// org.slf4j.LoggerFactory#reportMultipleBindingAmbiguity
private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {Util.report("Class path contains multiple SLF4J bindings.");for (URL path : binderPathSet) {Util.report("Found binding in [" + path + "]");}Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");}
}

由上分析,在真实生产环境中,slf4j只绑定一个日志实现框架就可以了,绑定多个,默认使用导入依赖的第一个,而且会产生没有必要的警告信息。

4、使用slf4j-nop禁止日志打印

<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!-- 导入nop -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-nop</artifactId><version>1.7.25</version>
</dependency>
<!-- logback依赖 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>

将slf4j-nop放在依赖最上面,默认就会使用slf4j-nop(之前我们总结的,日志集成会优先集成依赖的第一种)。

打印结果就会出现:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.m2/repository/org/slf4j/slf4j-nop/1.7.25/slf4j-nop-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.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.slf4j.helpers.NOPLoggerFactory]

我们自定义的日志就被禁止了。

5、slf4j集成log4j(使用适配器)

由于log4j是在slf4j之前出品的日志框架实现,所以并没有遵循slf4j的API规范。

如果想要使用slf4j,需要绑定一个适配器,叫做slf4j-log4j12,再导入log4j的实现。

<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!-- 导入log4j适配器依赖 -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25</version>
</dependency>
<!-- 导入log4j依赖 -->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>

如果不导入slf4j-log4j12适配实现,会提示以下信息:

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.

log4j的使用,需要搭配其配置文件,具体log4j的使用请移步:
学习Java日志框架之——搞懂log4j

可以看出,虽然我们使用的是slf4j,但是底层完全是log4j的使用,这就是日志门面的强大之处。

6、slf4j集成jul(使用适配器)

同样要引入jul的适配器。

<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!--jul适配器-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>1.7.25</version>
</dependency>

因为jul是jdk默认实现,所以不需要额外导入包,只需要一个适配器即可。

7、桥接器的使用

在这里插入图片描述

当我们老项目使用log4j时:

<!-- 导入log4j依赖 -->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;Logger logger = LogManager.getLogger(SLF4JTest01.class);
logger.info("info信息");

此时我们项目升级,想使用slf4j+logback的形式,在不触碰java源代码的情况下,需要怎么做?此时桥接器的用处就体现出来了!

将log4j去除,将slf4j日志门面和logback的日志实现依赖加入进来,这样做,没有了log4j环境的支持,编译报错。此时引入log4j的桥接器,原来的代码以及包都不需要修改!新的日志输出,就是logback的输出了。

<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
<!--桥接器-->
<dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>1.7.25</version>
</dependency>

在重构之后,就会为我们造成这样一种假象,使用的明明是log4j包下的日志组件资源,但是真正日志的实现,却是使用slf4j门面+logback实现,这就是桥接器给我们带来的效果。

注意:在桥接器加入之后,适配器就没有必要加入了,桥接器和适配器不能同时导入依赖,桥接器如果配置在适配器的上方,则运行报错,不同同时出现,桥接器如果配置在适配器的下方,则不会执行桥接器,没有任何的意义。

(1)源码分析

我们进入到LogManager.getLogger方法,发现该LogManager已经是log4j-over-slf4j包下的了,已经不是log4j包下的了:

// org.apache.log4j.LogManager#getLogger(java.lang.Class)
public static Logger getLogger(final Class clazz) {return Log4jLoggerFactory.getLogger(clazz.getName());
}// org.apache.log4j.Log4jLoggerFactory#getLogger(java.lang.String)
public static Logger getLogger(String name) {org.apache.log4j.Logger instance = log4jLoggers.get(name);if (instance != null) {return instance;} else {Logger newInstance = new Logger(name); // 创建LoggerLogger oldInstance = log4jLoggers.putIfAbsent(name, newInstance);return oldInstance == null ? newInstance : oldInstance;}
}

我们查看Logger的构造方法:

// org.apache.log4j.Logger#Logger
protected Logger(String name) {super(name);
}// org.apache.log4j.Category#Category
Category(String name) {this.name = name;// 下面的逻辑就是通过Slf4j获取的Loggerslf4jLogger = LoggerFactory.getLogger(name);if (slf4jLogger instanceof LocationAwareLogger) {locationAwareLogger = (LocationAwareLogger) slf4jLogger;}
}

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

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

相关文章

一次内存泄露排查

前因&#xff1a; 因为测试 长时间压测导致 接口反应越来越慢&#xff0c;甚至 导致服务器 崩溃 排查过程 1、top 查看是 哪个进程 占用 内存过高 2、根据 进程 id 去查找 具体是哪个 程序的问题 ps -ef| grep 41356 可以看到 具体的 容器位置 排查该进程 对象存活 状态…

23年PMP考试会使用第七版教材吗?

大家都知道了&#xff0c;今年的考纲是改版了的&#xff0c;为啥要改版呢&#xff0c;因为《PMBOK指南》更新到第七版了&#xff0c;考纲自然也要更新&#xff0c;据PMI的市场调查&#xff0c;近年来&#xff0c;项目管理行业新趋势在第六版和旧考纲中未收纳&#xff0c;为了确…

三、数据链路层

&#xff08;一&#xff09;纠错与检错1、奇偶校验码&#xff08;再研究下&#xff0c;原理知道&#xff0c;具体过程无法重现&#xff09;分为奇校验和偶校验&#xff0c;奇偶校验位在首部或尾部&#xff0c;奇偶校验满信息位奇偶校验位&#xff08;1&#xff09;原理&#xf…

Redis 数据结构

这里写目录标题Redis 数据结构一、String类型String数据类型的使用场景key 的设置约定二、Hash数据类型string存储对象&#xff08;json&#xff09;与hash存储对象的区别三、list 类型四、set 类型set数据交并差操作set 类型数据操作的注意事项六、sorted_set 类型Redis 数据结…

算法----火柴拼正方形

题目 你将得到一个整数数组 matchsticks &#xff0c;其中 matchsticks[i] 是第 i 个火柴棒的长度。你要用 所有的火柴棍 拼成一个正方形。你 不能折断 任何一根火柴棒&#xff0c;但你可以把它们连在一起&#xff0c;而且每根火柴棒必须 使用一次 。 如果你能使这个正方形&a…

Junit单元测试框架

1)Junit是一个开源的JAVA语言的单元测试框架&#xff0c;也是JAVA方向使用最广泛的单元测试框架&#xff0c;使用JAVA开发者都应该学习junit框架&#xff0c;并且掌握单元测试的编写 2)selenium和Junit都可以被导入到maven项目里面 3)先进行创建maven项目&#xff0c;导入相关依…

linux 全局环境变量删除后 还有 仍然存在

linux 全局环境变量删除后 还有 仍然存在1、编辑 /etc/profile2、设置REDISCLI_AUTH后&#xff0c;redis-cli 进去redis后不需要再次认证2、删除全局环境后 source后 仍然存在3、unset释放全局环境变量4、总结1、编辑 /etc/profile 设置redis环境变量 在末尾加入一行 export R…

家电企业数字工厂系统解决方案

国内小型家电生产商的中小企业普遍使用传统的手工作业模式&#xff0c;依靠大量的人力&#xff0c;线下管理各种数据&#xff0c;如&#xff1a;纸质文档、excel制作等&#xff0c;信息化程度非常低&#xff0c;严重限制着企业生产效率的提升和生产规模的扩大。对传统制造企业来…

基于WEB的网上购物系统的设计与实现(附:源码 论文 sql文件)

摘 要 随着计算机网络技术的飞速发展和人们生活节奏的不断加快&#xff0c;电子商务技术已经逐渐融入了人们的日常生活当中&#xff0c;网上商城作为电子商务最普遍的一种形式&#xff0c;已被大众逐渐接受。因此开发一个网上商城系统&#xff0c;适合当今形势&#xff0c;更加…

AWS白皮书 – 成本优化

本文讲解AWS良好架构框架&#xff08;AWS Well-Architected Framework&#xff09;里其中五大支柱之一&#xff1a;成本优化&#xff08;Cost Optimization&#xff09;。 一套成本优化型系统应充分利用全部资源、以最低价格来实现业务成果&#xff0c;同时充分满足你的功能需…

Google Bard VS ChatGPT:哪个是更好的AI聊天机器人?

文章目录前言一、Bard和ChatGPT的宏观对比二、应用场景不同三、知识的时效性四、未来的归宿总结前言 自从 OpenAI 向公众发布ChatGPT以来的过去几个月里&#xff0c;我们都见证了围绕 ChatGPT 的各种测评&#xff0c;并为它带来的效果感到惊艳。 昨晚Google开放了自家研发的A…

SpringBoot的简介和使用

文章目录1. SpringBoot简介和概述2. SpringBoot的使用3.SpringBoot 项目打包及运行4.切换web服务器1. SpringBoot简介和概述 Spring Boot是由Pivotal团队提供的一套开源框架&#xff0c;可以简化spring应用的创建及部署。它提供了丰富的Spring模块化支持&#xff0c;可以帮助开…

JUC并发编程共享模型之不可变(五)

5.1 问题引出 public interface Account {// 获取余额Integer getBalance();void withdraw(Integer amount);/*** 方法内会启动1000个线程&#xff0c;每个线程做-10元的操作* 如果初始余额为 10000 那么正确的结果应当是0*/static void demo(Account account){List<Thread…

整数拼接(思维枚举,两变量满足某条件-->通过其中一变量根据条件推断另一变量

2068.整数拼接&#xff08;思维&#xff0c;枚举&#xff09; 输入样例&#xff1a; 4 2 1 2 3 4输出样例&#xff1a; 6大佬思路 很多需要双重循环两个值&#xff0c;暴力判断组合在一起是否满足某个条件(比如等式是否成立)&#xff0c; 其实可以换个角度&#xff0c;遍历…

WPF中阴影效果和模糊效果的使用

总目录 文章目录总目录前言一、DropShadowEffect1、DropShadowEffect各属性效果图2、实现代码二、BlurEffect1、BlurEffect各属性效果图2、实现代码3、进阶使用结语前言 WPF中的控件效果主要通过Effect来实现&#xff0c;而Effect有DropShadowEffect&#xff08;投影效果&…

【并发】详解redis的incr、decr命令

一、前言 redis是一个单线程的服务&#xff0c;那么所有的命令肯定会排队被redis执行&#xff0c;redis提供的命令都是原子性的&#xff0c;百度搜索incr\decr就是说将对应的key1&#xff0c;key-1的值重新set到redis中&#xff0c;而且很多都是认为incr\decr原子性的&#xf…

chatgpt优化使用手册

提问方式的优化 我们在首次使用chatgpt的时候&#xff0c;当我们问它一些问题的时候&#xff0c;我们会发现它的有些回答广泛且空洞&#xff0c;不够专业&#xff0c;但是chatgpt是能实现角色的扮演和切换的&#xff0c;所以我们在提问它时需要先给它输入一些剧本&#xff0c;…

激活函数σ、tanh、relu、Leakyrelu

激活函数1- SIgmoid1-1 sigmoid导数2- tanh2-1 tanh函数导数3- ReLU4- LeakyReLu5- LR 公式推导Sigmoid、tanh、ReLU、LeakyReLu1- SIgmoid sigmoid 函数将元素的值映射到0和1之间 sigmoid(x)11exp(−x)sigmoid(x)\frac{1}{1exp(-x)}sigmoid(x)1exp(−x)1​ import torch imp…

leetcode 2492. Minimum Score of a Path Between Two Cities(两个城市间路径的最小score)

定义roads数组&#xff0c;每个元素长度为3&#xff0c;具体为[from, to, 该条road的distance], 其中road是双向的&#xff08;也就是无向图&#xff09;&#xff0c;可以走多次&#xff08;走不通可以折回来&#xff09;&#xff0c; 需要找的是城市1到城市n的路径中&#xff…

测试用例的价值与体系(软件测试入门)

1.测试用例概念&#xff1a; 测试用例(Test Case)是为特定的目的而设计的一组测试输入、执行条件和预期的结果的文档通过大量的测试用例来检验软件的运行效果它是指导测试工作进行的依据2.测试用例价值 指导测试的实施规划测试数据的准备编写测试脚本的"设计规格说明书…