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

作为Java开发,平时虽然日志使用的比较多,但是其深入的原理压根就没想过要去研究,有一种日用而不知的感觉。

扪心自问,这么简单的问题,确实不清楚。

之后,便是知耻而后勇,便有了对平常使用的日志的仔细研究。

下面,笔者以我们通常用的最多的spring-boot-starter-web说起。

相信大家对下面的这个依赖很熟悉:


   org.springframework.boot
   spring-boot-starter-web

这个是我们目前搭建springboot项目最常见的依赖引入。

在idea中,我们通过ctrl+鼠标左键,一步步点击进去,可以看到:


  org.springframework.boot
  spring-boot-starter
  2.5.0
  compile

继续:


  org.springframework.boot
  spring-boot-starter-logging
  2.5.0
  compile

最终我们看到了在项目中默认使用的log框架:


  ch.qos.logback
  logback-classic
  1.2.3
  compile

...

  org.slf4j
  jul-to-slf4j
  1.7.30
  compile

 笔者研究的便是slf4j及其实现logback的关系。

在进入正题之前,我们先回顾我们常见的一个设计模式:外观模式。

其定义为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。详见:JAVA设计模式之门面模式(外观模式) | 菜鸟教程

而我们的slf4j便是相当于一个Facade层,所用的日志打印都是通过slf4j来转发,但是具体的功能实现是由logback来实现,当然也可以由别的依赖来实现,比如slf4j-simple。

首先我们看springboot框架默认实现:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestLogger {
    private static final Logger log = LoggerFactory.getLogger(TestLogger.class);

    public static void main(String[] args) {
        log.info("test---------->>>>>>>>>>>><<<<<<<");
    }
}

归根结底,所有的用法都只是片面,我们要理解原理,还是要从源码入手。进入getLogger方法:

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 http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
            }
        }

        return logger;
    }

重点关注

getLogger(clazz.getName())

继续点击:

public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

日志打印的具体实现便在

getILoggerFactory()
public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == 0) {
            Class var0 = LoggerFactory.class;
            synchronized(LoggerFactory.class) {
                if (INITIALIZATION_STATE == 0) {
                    INITIALIZATION_STATE = 1;
                    performInitialization();
                }
            }
        }

        switch(INITIALIZATION_STATE) {
        case 1:
            return SUBST_FACTORY;
        case 2:
            throw new IllegalStateException("org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
        case 3:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case 4:
            return NOP_FALLBACK_FACTORY;
        default:
            throw new IllegalStateException("Unreachable code");
        }
    }

日志打印的重点便在于返回

StaticLoggerBinder.getSingleton().getLoggerFactory()

这个对象来实现具体的日志打印工作,那StaticLoggerBinder这个类又是从哪里来,是要干什么的呢?

我们通过实际代码执行可以知道,StaticLoggerBinder便是logback这个jar包提供对slf4j日志接口LoggerFactoryBinder的具体实现,也就是说实际的日志打印slf4j不能执行,只能通过接口的实现类StaticLoggerBinder来进行执行。

这样的好处便在于slf4j相当于只是提供一个接口或者说标准,但是具体的执行可以由其实现类来执行,这样只要是实现了slf4j标准接口的任意日志框架便都可以来执行日志打印。

通过这种方式slf4j可以同时支持多种日志框架,且无需任何配置,只需要引入特定的jar包让其拥有指定全类名的StaticLoggerBinder类即可。

那么这样也会导致另一个问题,如果系统引入了多个同时实现slf4j接口的类,那么系统怎么办,是否会报错?

这种特殊情况,slf4j也有做处理,其处理方式便是通过打印所有的引入实现类,然后由JVM虚拟机选择一个合适的实现类来执行日志打印。

这样既不会影响系统日志执行,也能使程序员通过日志,清楚的看出系统中存在哪些日志的实现类。

其具体代码在上午

getILoggerFactory()方法中的performInitialization()执行:
private static final void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == 3) {
            versionSanityCheck();
        }

    }

在bind执行:

private static final void bind() {
        try {
            String msg;
            try {
                Set staticLoggerBinderPathSet = null;
                if (!isAndroid()) {
                    staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                    reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
                }

                StaticLoggerBinder.getSingleton();
                INITIALIZATION_STATE = 3;
                reportActualBinding(staticLoggerBinderPathSet);
            } catch (NoClassDefFoundError var7) {
......

重点便在于

findPossibleStaticLoggerBinderPathSet();//找到潜在的slf4j实现类
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);//打印所有的实现类全类名
reportActualBinding(staticLoggerBinderPathSet);//打印实际的实现类全类名

详细方法如下:

static Set findPossibleStaticLoggerBinderPathSet() {
        LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();

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

            while(paths.hasMoreElements()) {
                URL path = (URL)paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException var4) {
            Util.report("Error getting resources from path", var4);
        }

        return staticLoggerBinderPathSet;
    }

可以看到,其是通过

STATIC_LOGGER_BINDER_PATH

即,

private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class

也就是指定的全限定类名来进行加载的。

这样就涉及到一个问题, 不同的jar包依赖是可以创建同样的全限定类名的,这样会导致

paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);

出现多个具有同样全限定类名的类被重复找到。

那么在slf4j有多个实现的时候,如何保证其加载指定的实现呢?

以实现框架slf4j-sample和logback为例,在工程的pom文件中:

Java日志框架:slf4j作用及其实现原理_第1张图片

 sif4j-sample在前,默认引入logback在后,其运行结果如下:

 可以看到 sif4j-sample和logback有着一个完全相同的全限定类名:StaticLoggerBinder,那么系统到底选用哪一个呢?通过日志打印我们可以看到系统选择的是sif4j-sample,这是由于在pom文件中,其引入在logback之前,JVM虚拟机加载class文件时,便会选择优先引入的StaticLoggerBinder类。

 反之,如果默认引入在前,则会使用默认logback。

如果只需要引入一个日志实现,也可以显示的注释或者排除掉另外一个。

 参考:Java日志框架:slf4j作用及其实现原理 - 五月的仓颉 - 博客园

你可能感兴趣的:(Java,java,spring,intellij-idea)