slf4j-api 1.6.99初始化源码艺术

SLF4J初始化机制深度解析

目录

  1. 核心流程概览
  2. 初始化状态机
  3. 绑定机制剖析
  4. 版本兼容设计
  5. 临时日志处理

核心流程概览

graph TD A[用户调用 LoggerFactory.getLogger] --> B{检查INITIALIZATION_STATE} B -- UNINITIALIZED --> C[获取类锁] C --> D[执行bind绑定实现] D --> E[版本兼容检查] E --> F[初始化ILoggerFactory] F --> G[返回Logger实例] B -- ONGOING_INITIALIZATION --> H[返回SUBST_FACTORY] B -- SUCCESSFUL_INITIALIZATION --> I[返回绑定LoggerFactory] B -- NOP_FALLBACK_INITIALIZATION --> J[返回NOP_FACTORY] B -- FAILED_INITIALIZATION --> K[抛出异常] classDef critical fill:#f9f,stroke:#333; classDef normal fill:#bbf,stroke:#333; class A,B critical class C,D,E,F,G normal

初始化状态机

SLF4J使用5种状态控制初始化流程:

  1. UNINITIALIZED(0):未初始化
  2. ONGOING_INITIALIZATION(1):初始化中
  3. SUCCESSFUL_INITIALIZATION(2):成功
  4. NOP_FALLBACK_INITIALIZATION(3):降级到NOP
  5. FAILED_INITIALIZATION(4):失败

状态转换通过双重检查锁保证线程安全:

Logger logger=LoggerFactory.getLogger("xxxx");

// 在LoggerFactory中调用了 getILoggerFactory
public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

绑定机制剖析

核心绑定逻辑在bind()方法中,主要处理:

  1. 查找可能的StaticLoggerBinder实现
  2. 处理多绑定冲突
  3. 捕获并处理绑定异常
  4. 最终清理工作

关键设计点:

  • 类路径扫描机制(非标准SPI):
    • 通过ClassLoader加载org.slf4j.impl.StaticLoggerBinder
    • 允许任意jar放置实现类
  • 编译时分离:
    • api和实现独立编译
    • 通过约定接口交互
  • 运行时绑定:
    • 动态加载实现类
    • 版本兼容检查
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:
            // 没有bind成功,进行降级到No Operation
            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
            // 初始化中时,先返回 SUBST_FACTORY,日志先暂存,后续重放
            return SUBST_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
}

调用了performInitialization进行初始化

private final static void performInitialization() {
    // 绑定日志实现
    bind();
    if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
        // api和日志实版本现兼容性检查
        versionSanityCheck();
    }
}

performInitialization内部进行了日志实现的绑定,主要看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();
                // 类路径下,存在多个org/slf4j/impl/StaticLoggerBinder.class
                // 会打印警告信息 Class path contains multiple SLF4J bindings
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            // 打包的时候slf4j-api会将本身的StaticLoggerBinder去掉,
            // 让真正的日志实现框架提供StaticLoggerBinder的实现
            // 从而获得特定的日志框架实现
            // 自身的StaticLoggerBinder只是为了编译时能编译过
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            // 打印真正的LoggerFactory实现类
            reportActualBinding(staticLoggerBinderPathSet);
        } catch (NoClassDefFoundError ncde) {
            // 编译时类存在(即代码没有编译错误),但运行时找不到类定义。
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                // 如果是StaticLoggerBinder导致的NoClassDefFoundError,就降级到No Operation
                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()")) {
                // 如果是StaticLoggerBinder导致的NoSuchMethodError,说明版本不兼容
                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);
        } finally {
            postBindCleanUp();
        }
  }

临时日志处理

初始化期间的日志处理采用生产者-消费者模式:

  1. SUBST_FACTORY作为临时存储
  2. LinkedBlockingQueue缓冲日志事件
  3. 初始化完成后重放(replay)日志

这种设计保证:

  • 初始化期间不丢失日志
  • 线程安全
  • 最终一致性

也就是说,在初始化完成后,对初始化过程中打印的日志(多线程场景)重放给真实的最终Logger来处理

private static void postBindCleanUp() {
        // 处理临时占位Logger
        // 当用户代码在 SLF4J 初始化完成之前调用日志时,此时真实的日志实现尚未绑定
        // 在真实Logger未就绪期间,所有日志事件被暂存在 LinkedBlockingQueue 中。
        fixSubstituteLoggers();
        // 重放缓冲的日志事件
        replayEvents();
        // 释放资源
        // release all resources in SUBST_FACTORY
        SUBST_FACTORY.clear();
}

private static void fixSubstituteLoggers() {
    synchronized (SUBST_FACTORY) {
        SUBST_FACTORY.postInitialization();
        for (SubstituteLogger substLogger : SUBST_FACTORY.getLoggers()) {
            Logger logger = getLogger(substLogger.getName());
            // 将临时Logger的调用委托给真实Logger
            substLogger.setDelegate(logger);
        }
    }
}

最后进行版本兼容性的检查

private final static void versionSanityCheck() {
        try {
            String requested = StaticLoggerBinder.REQUESTED_API_VERSION;

            boolean match = false;
            for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
                if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
                    match = true;
                }
            }
            if (!match) {
                Util.report("The requested version " + requested + " by your slf4j binding is not compatible with " + Arrays.asList(API_COMPATIBILITY_LIST).toString());
                Util.report("See " + VERSION_MISMATCH + " for further details.");
            }
        } catch (java.lang.NoSuchFieldError nsfe) {
            // 旧版实现库(如 log4j 1.2)未声明 REQUESTED_API_VERSION 字段。
            // 不报错,保持向后兼容
            // given our large user base and SLF4J's commitment to backward
            // compatibility, we cannot cry here. Only for implementations
            // which willingly declare a REQUESTED_API_VERSION field do we
            // emit compatibility warnings.
        } catch (Throwable e) {
            // we should never reach here
            Util.report("Unexpected problem occured during version sanity check", e);
        }
}

版本兼容设计

通过非final的REQUESTED_API_VERSION字段实现:

  1. 避免编译器优化为字面量
  2. 允许实现库小版本升级
  3. 保持api与实现的松耦合

版本检查算法:

  1. 前缀匹配机制
  2. 容忍旧版实现(NoSuchFieldError)
  3. 明确的不兼容提示

这个设计的关键在于REQUESTED_API_VERSION字段没有使用final修饰符。根据Java语言规范(JLS 17.5.3):

  1. final字段的常量折叠(Constant Folding):
  • 编译器会将final基本类型和String常量直接替换为字面量
  • 例如:final String VERSION = "1.6"; 在编译后等同于直接使用"1.6"
  1. 非final字段的动态绑定:
  • 运行时才会解析字段的实际值
  • 允许不同的实现类提供不同的版本号

具体到SLF4J的实现:

// slf4j-api的LoggerFactory中
String requested = StaticLoggerBinder.REQUESTED_API_VERSION;

// 如果字段是final的,编译后会变成:
String requested = "1.6.99"; // 硬编码值

// 非final字段则保持为字段访问:
String requested = StaticLoggerBinder.REQUESTED_API_VERSION;

这种设计实现了:

  • 编译时解耦:api和实现可以独立编译
  • 运行时绑定:实际使用实现库提供的版本号
  • 版本灵活性:实现库可以自由升级小版本
/**
     * Declare the version of the SLF4J API this implementation is compiled against.
     * The value of this field is modified with each major release.
     * 不要final,final会被编译器优化为字面量!
     * 如 String version = StaticLoggerBinder.REQUESTED_API_VERSION;
     * 会被优化成 String version="1.6.99";
     * 而不是真实实现StaticLoggerBinder的REQUESTED_API_VERSION值
     * <p>
     * 正是因为 StaticLoggerBinder 类被刻意从 slf4j-api 中移除(通过 org.slf4j.impl 分包),
     * SLF4J 必须通过运行时动态读取实现库的版本声明,才能实现:
     * 松耦合:API 和实现完全分离编译
     * 动态适配:同一份 slf4j-api 可对接不同版本的日志实现
     * 安全演进:当出现不兼容变更时能明确拒绝绑定
     */
// to avoid constant folding by the compiler, this field must *not* be final
public static String REQUESTED_API_VERSION = "1.6.99"; // !final
posted @ 2025-06-18 16:59  骑白马走三关  阅读(57)  评论(0)    收藏  举报