浅谈slf4j中的桥接器是如何运作的

阅读分析 slf4j 的日志源码,发现其中涵盖了许多知识点和优秀的设计,关键它们都是活生生的实践案例。写成系列文章与大家分享,欢迎持续关注。

前言

在日志框架 slf4j 中有一组项目,除了核心的 slf4j-api 之外,还有 slf4j-log4j12、slf4j-jdk14 等项目。这一类项目统称桥接器项目,针对不同的日志框架有不同的桥接器项目。

在使用 logback 日志框架时,并没有针对的桥接器,这是因为 logback 与 slf4j 是一个作者所写,在 logback 中直接实现了 slf4j 的 SPI 机制。

但如果使用其他日志框架,那么就必须要用到桥机器相关依赖。比如,当我们基于 log4j 使用 slf4j 时,除了需要引入 log4j 的 jar 包依赖,还需要引入 slf4j 的下面两个依赖:

<dependency>
 <groupId>org.slf4j</groupId>
 <artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
 <groupId>org.slf4j</groupId>
 <artifactId>slf4j-log4j12</artifactId>
</dependency>

slf4j-api 为核心依赖,必须引入,而 slf4j-log4j12 就是桥接器用来在 slf4j 和 log4j 之间进行过渡和封装。下面,我们就聊聊桥接器项目的核心实现。

slf4j-log4j12 桥接器的价值

要了解桥接器的运作,首先需要回顾一下 slf4j 的 SPI 机制。在我们通过 LoggerFactory.getLogger(Foo.class); 时,slf4j 会通过 SPI 机制寻找并初始化 SLF4JServiceProvider 的实现类。

然后,通过 SLF4JServiceProvider 的实现类来获取日志相关的具体工厂类对象,进而进行日志功能的处理。先来看一下 SLF4JServiceProvider 的接口定义:

public interface SLF4JServiceProvider {

  /**
   * 返回ILoggerFactory的实现类,用于LoggerFactory类的绑定
   */
  ILoggerFactory getLoggerFactory();

  /**
   * 返回IMarkerFactory实例
   */
  IMarkerFactory getMarkerFactory();

  /**
   * 返回MDCAdapter实例
   */
  MDCAdapter getMDCAdapter();

  /**
   * 获取请求版本
   */
  String getRequesteApiVersion();

  /**
   * 初始化,实现类中一般用于初始化ILoggerFactory等
   */
  void initialize();
}

SLF4JServiceProvider 接口是在 slf4j-api 中定义的,具体的实现类由其他日志框架来完成。但是像 log4j(logback“敌对阵营”)是不会在框架内实现该接口的。那么,怎么办?

针对此问题,slf4j 提供了 slf4j-log4j12 这类桥接器的过渡项目。在其中实现 SLF4JServiceProvider 接口,并对 Log4j 日志框架接口进行封装,将 Logger(slf4j) 接收到的命令全部委托给 Logger(log4j) 去完成,在使用者无感知的情况下完成偷天换日。

slf4j-log4j12 的核心实现类

理解了桥接器的存在价值及原理,下面就来看看 slf4j-log4j12 是如何实现这一功能的。

首先来看看核心实现类之一 Log4j12ServiceProvider。它实现了 SLF4JServiceProvider 接口,主要功能就是完成接口中定义的相关工厂接口的实现。源代码如下:

public class Log4j12ServiceProvider implements SLF4JServiceProvider {

  public static String REQUESTED_API_VERSION = "1.8.99"; 

  private ILoggerFactory loggerFactory;
  private IMarkerFactory markerFactory;
  private MDCAdapter mdcAdapter;

  public Log4j12ServiceProvider() {
    try {
      @SuppressWarnings("unused")
      Level level = Level.TRACE;
    } catch (NoSuchFieldError nsfe) {
      Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
    }
  }

  @Override
  public void initialize() {
    loggerFactory = new Log4jLoggerFactory();
    markerFactory = new BasicMarkerFactory();
    mdcAdapter = new Log4jMDCAdapter();
  }

  @Override
  public ILoggerFactory getLoggerFactory() {
    return loggerFactory;
  }

  @Override
  public IMarkerFactory getMarkerFactory() {
    return markerFactory;
  }

  @Override
  public MDCAdapter getMDCAdapter() {
    return mdcAdapter;
  }

  @Override
  public String getRequesteApiVersion() {
    return REQUESTED_API_VERSION;
  }
}

该类的实现看起来很简单,构造方法中通过尝试使用 log4j 的 Level.TRACE 调用来验证 log4j 的版本是否符合要求。log4j1.2.12 之前并没有 Level.TRACE,所以会抛出异常,并打印日志信息。不得不赞叹作者在此处检查版本的巧妙用法。

而这里对接口中返回的实现类主要通过 initialize() 方法来实现的。这里我们重点看 Log4jLoggerFactory 类的实现。

public class Log4jLoggerFactory implements ILoggerFactory {

  private static final String LOG4J_DELEGATION_LOOP_URL = "http://www.slf4j.org/codes.html#log4jDelegationLoop";

  // check for delegation loops
  static {
    try {
      Class.forName("org.apache.log4j.Log4jLoggerFactory");
      String part1 = "Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. ";
      String part2 = "See also " + LOG4J_DELEGATION_LOOP_URL + " for more details.";

      Util.report(part1);
      Util.report(part2);
      throw new IllegalStateException(part1 + part2);
    } catch (ClassNotFoundException e) {
      // this is the good case
    }
  }

  ConcurrentMap<String, Logger> loggerMap;

  public Log4jLoggerFactory() {
    loggerMap = new ConcurrentHashMap<>();
    // force log4j to initialize
    org.apache.log4j.LogManager.getRootLogger();
  }

  @Override
  public Logger getLogger(String name) {
    Logger slf4jLogger = loggerMap.get(name);
    if(slf4jLogger != null) {
      return slf4jLogger;
    } else {
      org.apache.log4j.Logger log4jLogger;
      if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) {
        log4jLogger = LogManager.getRootLogger();
      } else {
        log4jLogger = LogManager.getLogger(name);
      }

      Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
      Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
      return oldInstance == null ? newInstance : oldInstance;
    }
  }
}

在 Log4j12ServiceProvider 中进行了 Log4jLoggerFactory 的实例化操作,也就直接 new 出来一个对象。我们知道,在 new 对象执行会先执行 static 代码块,本类的静态代码块的核心工作就是检查依赖文件中是否同时存在反向桥接器的依赖。

其中,org.apache.log4j.Log4jLoggerFactory 是反向桥接器 log4j-over-slf4j 项目中的类,如果加装到了,说明存在,则抛出异常,打印日志信息。此处再次赞叹作者运用的技巧的巧妙。

在 Log4jLoggerFactory 的构造方法中,做了两件事:第一,初始化一个 ConcurrentMap 变量,用于存储实例化的 Logger;第二,强制初始化 log4j 的组件,其中强制初始化 log4j 的组件是通过 getRootLogger 方法,来初始化一些静态的变量。

构造方法时初始化了 ConcurrentMap 变量,在 Log4jLoggerFactory 实现的 getLogger 方法中,先从 Map 中获取一下是否存在对应的 Logger,如果存在直接返回,如果不存在则进行构造。而构造的 Log4jLoggerAdapter 类很显然使用了适配器模式,它内部持有了 log4j 的 Logger 对象,自身又实现了 slf4j 的 Logger 接口。

下面看一下 Log4jLoggerAdapter 的部分代码实现:

public final class Log4jLoggerAdapter extends LegacyAbstractLogger implements LocationAwareLogger, Serializable {

  final transient org.apache.log4j.Logger logger;

  Log4jLoggerAdapter(org.apache.log4j.Logger logger) {
    this.logger = logger;
    this.name = logger.getName();
    traceCapable = isTraceCapable();
  }

  @Override
  public boolean isDebugEnabled() {
    return logger.isDebugEnabled();
  }

  @Override
  public void log(Marker marker, String callerFQCN, int level, String msg, Object[] arguments, Throwable t) {
    Level log4jLevel = toLog4jLevel(level);
    NormalizedParameters np = NormalizedParameters.normalize(msg, arguments, t);
    String formattedMessage = MessageFormatter.basicArrayFormat(np.getMessage(), np.getArguments());
    logger.log(callerFQCN, log4jLevel, formattedMessage, np.getThrowable());
  }

  public void log(LoggingEvent event) {
    Level log4jLevel = toLog4jLevel(event.getLevel().toInt());
    if (!logger.isEnabledFor(log4jLevel))
      return;

    org.apache.log4j.spi.LoggingEvent log4jevent = toLog4jEvent(event, log4jLevel);
    logger.callAppenders(log4jevent);

  }

  // 省略其他方法
}

源码中,通过构造方法传入 log4j 的 Logger 对象,而 Log4jLoggerAdapter 对外提供的方法,都是通过 log4j 的 Logger 进行具体实现。

总之,slf4j 的 Logger 接口的方法通过 Log4jLoggerAdapter 进行包装和转换,交由 log4j 的 Logger 去执行,这就达到了连接 slf4j-api 和 log4j 的目的。而此时,slf4j-api 不并关系日志是如何实现记录,对此也无感知。

小结

本文通过源码跟踪,逐步分析了 slf4j 项目中桥接器项目的运作机制,其中还涉及到了 SPI 机制、版本及依赖检查小技巧、桥接器运作本质(适配器模式)等。其实,在 slf4j 项目中还有文中提到的反向桥接器,其实基本机制也是如此,感兴趣的朋友可以阅读一下 log4j-over-slf4j 中的源码。

到此这篇关于浅谈slf4j中的桥接器是如何运作的 的文章就介绍到这了,更多相关slf4j 桥接器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解SSM框架下结合log4j、slf4j打印日志

    本文主要介绍了详解SSM框架下结合log4j.slf4j打印日志,分享给大家,具体如下: 首先加入log4j和slf4j的jar包 <!-- 日志处理 <!-- slf4j日志包--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dep

  • 详解Spring Boot 使用slf4j+logback记录日志配置

    在学校的时候使用Java进行开发,工作之后由于项目组采用.net进行开发就转到.net了.最近开始学习Java,对一些新东西进行学习.开始看SpringBoot,对遇到的问题进行记录. 学习新的东西最好从例子开始,只看文档太枯燥,但是文档还是必须要看的. spring boot主要的目的是: 为 Spring 的开发提供了更快更广泛的快速上手 使用默认方式实现快速开发 提供大多数项目所需的非功能特性,诸如:嵌入式服务器.安全.心跳检查.外部配置等 SLF4J是为各种loging APIs提供一个

  • Day21logj4与sl4j的使用与区别详解

    学习目标 (1)Junit 针对方法 (2)log4j与sl4j (3)Spring - IOC log4j的介绍 (1)什么是log4j?  Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件等 (2)有什么特点?  >可以控制每一条日志的输出格式  >控制日志的生成过程 通过一个配置文件来灵活地进行配置log4j.properties,而不需要修改应用的代码 public class Test01 { //模式 debug priva

  • 浅谈Slf4j与其他日志系统兼容的使用方法

    java生产的各种框架(如spring等)里各个框架会使用不同的日志体系,多个不同日志在一个jvm里混搭会出现一定问题 ,这里梳理一下java体系里常见的日志框架,以SFL4j为中心介绍下跟各个日志框架的关系,介绍下生产环境如何打理各种日志框架. 1. 接口简介 在java的体系里,主要有slf4j和common-logging两种日志体系接口.实现的框架有很多,主流的诸如logback.log4j等. 当然,虽然都是接口,但两者也可以通过桥接包实现相互的日志代理输出. common-loggi

  • 详解Spring Boot实现日志记录 SLF4J

    在开发中打印内容,使用 System.out.println() 和 Log4j 应当是人人皆知的方法了. 其实在开发中我们不建议使用 System.out 因为大量的使用 System.out 会增加资源的消耗. 而Log4j 更为灵活在性能上也相比 System.out 要高,我们可以配置输出级别,可以指定多个日志文件分别记录不同的日志. 使用 System.out 是在当前线程执行的,写入文件也是写入完毕后才继续执行下面的程序.而使用Log工具不但可以控制日志是否输出,怎么输出,它的处理机

  • slf4j与log4j全面了解

    推荐使用SLF4J(Simple Logging Facade for Java)作为日志的api,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统. 1.Slf4j优势 与使用apache commons-logging或直接使用log4j相比,SLF4J提供了一个名为参数化日志的高级特性,可以显著提高在配置为关闭日志的情况下的日志语句性能, log.debug("Found {} records matching filter: '{}'&qu

  • Springboot日志开启SLF4J过程解析

    一.日志 1.配置日志级别 日志记录器(Logger)的行为是分等级的.如下表所示: 分为:OFF.FATAL.ERROR.WARN.INFO.DEBUG.ALL 默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别 # 设置日志级别 logging.level.root=WARN 这种方式只能将日志打印在控制台上 二.Logback日志 spring boot内部使用Logback作为日志实现的框架. Logback和log4j非常相似,如果你对

  • 浅谈Java slf4j日志简单理解

    一.理解 slf4j(Simple Logging Facade for Java),表示为java提供的简单日志门面,更底层一点说就是接口.通过将程序中的信息导入到日志系统并记录,实现程序和日志系统的解耦 日志门面接口本身通常并没有实际的日志输出能力,它底层还是需要去调用具体的日志框架API的,也就是实际上它需要跟具体的日志框架结合使用.由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合可能需要对应的桥接器,就好像JDBC与各种不同的数据库之间的结合需要对应的

  • 浅谈slf4j中的桥接器是如何运作的

    阅读分析 slf4j 的日志源码,发现其中涵盖了许多知识点和优秀的设计,关键它们都是活生生的实践案例.写成系列文章与大家分享,欢迎持续关注. 前言 在日志框架 slf4j 中有一组项目,除了核心的 slf4j-api 之外,还有 slf4j-log4j12.slf4j-jdk14 等项目.这一类项目统称桥接器项目,针对不同的日志框架有不同的桥接器项目. 在使用 logback 日志框架时,并没有针对的桥接器,这是因为 logback 与 slf4j 是一个作者所写,在 logback 中直接实现

  • 浅谈Java中的桥接方法与泛型的逆变和协变

    目录 1. 泛型的协变 1.1 泛型协变的使用 1.2 泛型协变存在的问题 1.2.1 Java当中桥接方法的来由 1.2.2 为什么泛型协变时,不允许添加元素呢 1.2.3 从Java字节码的角度去看桥接方法 2. 泛型逆变 2.1 泛型逆变的使用 2.2 泛型逆变会有什么问题 3.协变与逆变-PECS原则 泛型的协变和逆变是什么?对应于Java当中,协变对应的就是<? extends XXX>,而逆变对应的就是<? super XXX>. 1. 泛型的协变 1.1 泛型协变的使

  • 浅谈django中的认证与登录

    认证登录 django.contrib.auth中提供了许多方法,这里主要介绍其中的三个: 1  authenticate(**credentials)    提供了用户认证,即验证用户名以及密码是否正确 一般需要username  password两个关键字参数 如果认证信息有效,会返回一个  User  对象.authenticate()会在User 对象上设置一个属性标识那种认证后端认证了该用户,且该信息在后面的登录过程中是需要的.当我们试图登陆一个从数据库中直接取出来不经过authent

  • 浅谈python中的面向对象和类的基本语法

    当我发现要写python的面向对象的时候,我是踌躇满面,坐立不安呀.我一直在想:这个坑应该怎么爬?因为python中关于面向对象的内容很多,如果要讲透,最好是用面向对象的思想重新学一遍前面的内容.这个坑是如此之大,犹豫再三,还是只捡一下重要的内容来讲吧,不足的内容只能靠大家自己去补充了. 惯例声明一下,我使用的版本是 python2.7,版本之间可能存在差异. 好,在开讲之前,我们先思考一个问题,看代码: 为什么我只创建是为 a 赋值,就可以使用一些我没写过的方法? 可能会有小伙伴说:因为 a

  • 浅谈python中的数字类型与处理工具

    python中的数字类型工具 python中为更高级的工作提供很多高级数字编程支持和对象,其中数字类型的完整工具包括: 1.整数与浮点型, 2.复数, 3.固定精度十进制数, 4.有理分数, 5.集合, 6.布尔类型 7.无穷的整数精度 8.各种数字内置函数及模块. 基本数字类型 python中提供了两种基本类型:整数(正整数金额负整数)和浮点数(注:带有小数部分的数字),其中python中我们可以使用多种进制的整数.并且整数可以用有无穷精度. 整数的表现形式以十进制数字字符串写法出现,浮点数带

  • 浅谈Javascript数据属性与访问器属性

    ES5中对象的属性可以分为'数据属性'和'访问器属性'两种. 数据属性一般用于存储数据数值,访问器属性对应的是set/get操作,不能直接存储数据值. 数据属性特性:value.writable.enumerable.configurable. 解释:configurable:true/false,是否可以通过delete删除属性,能否修改属性的特性,能否把属性修改为访问器属性,默认false: enumerable:true/false,是否可以通过for in循环返回,默认false: wr

  • 浅谈Java中各种修饰符与访问修饰符的说明

    JAVA中的类只能是public 或者package的.这是符合逻辑的:人们定义类的初衷就是为了让别人用的.倘若是private,别人怎么调用?但是有一个内部类可以被定义为private.严格上说,内部类,算不得上是一种光明正大的类,内部类在某种意义上是类这个王国里的特务和地下工作者.特务和地下工作者为王国起了不少作用,但是几乎从来不敢在公众场合抛投露面.就算要露面,也要在主人(class)的同意下,向导(Interface)的引导下,才敢战战兢兢的走出来.下面是常规的一些类的修饰符和访问修饰符

  • 浅谈SpringMVC中的session用法及细节记录

    前言 初学SpringMVC,最近在给公司做的系统做登录方面,需要用到session. 在网上找了不少资料,大致提了2点session保存方式: 1.javaWeb工程通用的HttpSession 2.SpringMVC特有的@SessionAttributes 我个人比较关注@SessionAttributes的用法,毕竟现在是在用SpringMVC嘛.但是我看网上那些文章,基本都是只说明了基础用法,详细的使用和细节却基本没有,我想这是不够的,所以我自己做了一些测试,然后整理了下代码做了个de

  • 浅谈VS中添加头文件时显示无法找到文件的问题

    目录或库文件名中包含汉字或空格的话,请将其用半角双引号括住. 项目.属性.C/C++.附加包含目录:填写附加头文件所在目录 分号间隔多项 项目.属性.链接器.附加库目录:填写附加依赖库所在目录 分号间隔多项 项目.属性.链接器(点前面的+展开).输入.附加依赖项:填写附加依赖库的名字.lib 空格间隔多项 这样在我们添加现有项后,经常出现的找不到源文件等等问题就解决了 以上这篇浅谈VS中添加头文件时显示无法找到文件的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们

随机推荐