Slf4j获取日志对象(Logger)的实现分析

1 Slf4j简介

日常开发中记录日志有很多框架,如:java.util.logging、Apache log4j、logback等。如果使用某种具体框架,一段时间后需要切换框架那么会很麻烦,因此有了Slf4j(Simple logging Facade for Java)。Slf4j提供了统一的日志接口,你可以根据需求配置不同的实现。这样可以并保持接口稳定,并轻易变更具体日志框架。

2 实现分析

在使用时可以引入某个具体实现,例如slf4j-simple需要引入依赖:

<dependency>
   <groupId>org.slf4jgroupId>
   <artifactId>slf4j-simpleartifactId>
   <version>1.7.25version>
dependency>

使用logback时引入依赖:

<dependency>
    <groupId>ch.qos.logbackgroupId>
    <artifactId>logback-classicartifactId>
    <version>1.1.7version>
dependency>

使用时统一通过slf4j-api包中的类org.slf4j.LoggerFactory来获取具体的Logger对象。

public class Main {
    public static void main(String[] args){
        Logger logger = LoggerFactory.getLogger(Main.class);
        logger.info("123");
    }
}

通过依赖分析工具可以看到这些实现类都需要依赖slf4j-api包,并实现了包中定义的接口。
Slf4j获取日志对象(Logger)的实现分析_第1张图片

slf4j-api包中定义了几个主要的接口和类,如Logger接口、ILoggerFactory接口及LoggerFactory类。

Logger接口定义了日志的使用方法,如trace,info等,具体日志框架提供不同的实现。

ILoggerFactory定义了如何获取logger对象的方法。

LoggerFactory工具类用提供了具体的实现方法最终获取不同框架实现的logger对象。

接下来看看Slf4j是如何根据配置找到不同的日志实现,即获取具体的Logger对象。

 Logger logger = LoggerFactory.getLogger(Main.class);

获取一个Logger对象归结为两步

  1. 获取ILoggerFactory实例
  2. 通过ILoggerFactory实例来获取Logger对象
    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }
    
org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:357)
	  at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:383)
	  at script.Main.main(Main.java:14)

getILoggerFactory在状态更新为SUCCESSFUL_INITIALIZATION时,会通过StaticLoggerBinder.getSingleton().getLoggerFactory()返回ILoggerFactory实例。

......
import org.slf4j.impl.StaticLoggerBinder;
......
    public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        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-97
            return SUBST_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }

当我们引入不同的依赖,会发现StaticLoggerBinder所在的包也不同。根据源码中导入依赖可以发只有日志框架中的实现包中提供了org/slf4j/impl/StaticLoggerBinder.class实现,那么才能通过StaticLoggerBinder获取到ILoggerFactory具体实现。

即StaticLoggerBinder类实现了slf4j-api中的LoggerFactoryBinder接口,并返回某个具体日志框架ILoggerFactory实例。

那么问题来了,当同时引入多个Slf4j实现会发生什么?到底选择哪个日志框架?接着看一看performInitialization()方法。

            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstituteLoggers();
            replayEvents();
            // release all resources in SUBST_FACTORY
            SUBST_FACTORY.clear();
	  at org.slf4j.LoggerFactory.findPossibleStaticLoggerBinderPathSet(LoggerFactory.java:296)
	  at org.slf4j.LoggerFactory.bind(LoggerFactory.java:146)
	  at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
	  at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:412)

findPossibleStaticLoggerBinderPathSet方法会尝试在引入的依赖中找到所有定义了org/slf4j/impl/StaticLoggerBinder.class的位置信息(URL对象),并保存在Set中(binderPathSet),用于后续的警告。

    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();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            while (paths.hasMoreElements()) {
                URL path = paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }

当这个Set大小大于1那么通过reportMultipleBindingAmbiguity方法会生成警告信息。即我们通常都会看到的一条警告:Class path contains multiple SLF4J bindings。

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/sunfei/.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:/Users/sunfei/.m2/repository/ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]

最后通过reportActualBinding方法提示最终使用的日志框架。可以看到还是通过引入的StaticLoggerBinder来确定最终使用的框架。那么当引入多个依赖时,根据依赖顺序,第一个依赖的实现最为最终使用的日志框架。其余的作为警告输出。

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

3.总结

Slf4j-api包定义统一接口来实现日志操作,而具体的日志实现工具需要在org.slf4j.impl下定义StaticLoggerBinder,这样Slf4j通过定义的工具类并使用StaticLoggerBinder类获取 ILoggerFactory实例,最终得到具体的Logger对象。

你可能感兴趣的:(Java)