总的入口就是这句 LoggerFactory.getLogger(Main.class),我们从这里入手。

public class Main {

    private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {

        LOGGER.info("\\{} {} test", "hello");
        LOGGER.info("\\\\{}", "place");
    }
}

1.8.0 之前

在 SLF4J 1.8.0 版本之前,它依赖于 Java 类加载机制,通过特定的类名来查找并加载具体框架的绑定实现。我们看下具体的实现:

getLogger 会使用传入的类名进行查找,它首先需要找到具体的 ILoggerFactory 实例,然后调用 getLogger(name) 方法获取指定名称的 Logger 实例。ILoggerFactory 的实现类会根据具体的日志框架实现来创建和返回 Logger 实例。

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

顺着 getILoggerFactory() 往下追,能够找到 bind() 方法,它是整个加载的核心。

private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
                // 查找可能的 `StaticLoggerBinder` 的路径集合
                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();
        } 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);
        }
    }

bind() 方法中,findPossibleStaticLoggerBinderPathSet() 查找可能的 StaticLoggerBinder 的路径集合。主要逻辑就是通过类加载器,获取所有 org/slf4j/impl/StaticLoggerBinder.class 的资源。可以看到,所有支持 SLF4J 的框架,都在指定路径定义了这个类。

StaticLoggerBinder.getSingleton(); 这里会进行实际的绑定。在有多个实现的情况下,会依据类加载顺序,选择第一个。如果 SLF4J 在类路径中找不到任何 SLF4J 绑定,那么它将默认使用 NOP(No Operation)绑定,就是说没有任何日志输出。也就是下面的 NOP_FALLBACK_FACTORY。

public static ILoggerFactory getILoggerFactory() {
        ...
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        ...
        }
        throw new IllegalStateException("Unreachable code");
    }

1.8.0 及之后

从 SLF4J 1.8.0 版本开始,SLF4J 使用了 ServiceLoader 机制来加载日志框架的实现。当应用程序启动时,ServiceLoader 会扫描所有的类路径,查找并加载可用的日志框架实现。

依旧是最关键的 bind()

private final static void bind() {
        try {
            List<SLF4JServiceProvider> providersList = findServiceProviders();
            reportMultipleBindingAmbiguity(providersList);
            ...
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

findServiceProviders(),提供了两种方式对服务提供者进行加载。

  1. 通过系统变量 slf4j.provider 来指定。
  2. 通过 ServiceLoader.load() 加载 SLF4JServiceProvider.class 对象列表。这里使用了 Java 的 SPI 机制,该机制会扫描 META-INF/services 下的文件,文件名为接口全限定名,内容是实现了这个接口的类的全限定名。关于 SPI,大家可以看看这篇文章剖析 SPI 在 Spring 中的应用

为什么有这样的改变?

  • 在 SLF4J 1.8.0 版本之前,对于其他日志框架,需要在自己项目实现一个包名为 org.slf4j.impl 的类,这会导致代码与 SLF4J 框架耦合。
  • 对于 SLF4J 自身,它自身不能有类 StaticLoggerBinder,但是为了编译通过,还是需要提供一个简单的实现,然后在打包阶段删除。
  • SLF4J 2004就开始发布,而 Java SPI 直到 2006 年才正式加入标准库。