💡

入門Service Loader: SLF4JはなぜLogbackのロガーを取得できるのか

に公開

環境

  • JDK 21
  • lobback-classic 1.5.18
  • slf4j-api 2.0.17

SLF4Jの謎

こんな感じでLogbackを追加します。

pom.xml
    <dependencies>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.5.18</version>
        </dependency>
    </dependencies>

そうすると、推移的依存性としてSLF4Jが追加されます(=logback-classicが依存しているライブラリであるlogback-coreとslf4j-apiも追加される)。

$ mvn dependency:tree
...
[INFO] com.example:sample:jar:1.0-SNAPSHOT
[INFO] \- ch.qos.logback:logback-classic:jar:1.5.18:compile
[INFO]    +- ch.qos.logback:logback-core:jar:1.5.18:compile
[INFO]    \- org.slf4j:slf4j-api:jar:2.0.17:compile
...

こんな感じでログを出力します。

Main.java
package com.example;

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

public class Main {
    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) throws Exception {
        logger.info("Hello!");
    }
}
実行結果
21:55:54.993 [main] INFO com.example.Main -- Hello!

SLF4Jは単なるファサード(窓口のようなもの)であり、内部で実際にログを出力しているのはLogbackです。しかしimport文を見れば分かる通り、LoggerLoggerFactoryもSLF4Jのインタフェースorクラスです。SLF4Jはどうやって内部的にLogbackを利用しているのでしょうか?

答えはServiceLoader

その謎を解決するのが、Javaにもともと備わっている ServiceLoader という機能です。ざっくり言うと、利用している全ライブラリのJARファイル内にある META-INF/services 内にあるインタフェース名のテキストファイルに書かれたクラス名を読み込み、そのインスタンスを自動的に生成するという機能です。

詳細な仕様についてはServiceLoaderのJavadocをご確認ください。

SLF4Jでいうと、処理の流れはこんな感じになります。

(1) slf4j-apiにはorg.slf4j.spi.SLF4JServiceProviderインタフェースが定義されている

(2) logback-classicには、org.slf4j.spi.SLF4JServiceProviderインタフェースの実装であるch.qos.logback.classic.spi.LogbackServiceProviderクラスが定義されている

(3) logback-classic.jar内のMETA-INF/servicesフォルダにはorg.slf4j.spi.SLF4JServiceProviderという名前のテキストファイルがあり、内容は上記の実装クラス名ch.qos.logback.classic.spi.LogbackServiceProviderとなっている

(4) SLF4JのLoggerFactory.getLogger()内では、ServiceLoaderにより上記テキストファイルが読み込まれ、LogbackServiceProviderのインスタンスが生成される

LoggerFactory.java(抜粋&編集)
// ServiceLoaderを生成
ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class, classLoaderOfLoggerFactory);
...
Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
    // ここでch.qos.logback.classic.spi.LogbackServiceProviderのインスタンスが生成される
    SLF4JServiceProvider provider = iterator.next();
    ...
}

詳細な流れはLoggerFactory.getLogger()のソースコードを読んでみてください。

これにより、logback-classicのJARファイルを追加しただけで、SLF4JからLogbackを使えるようになっていたんですねー。

ServiceLoaderが使われている他のライブラリ

  • JDBCドライバー
    • postgresql.jarのMETA-INF/servicesフォルダにはjava.sql.Driverファイルがあり、その内容は実装クラスorg.postgresql.Driverとなっている(GitHub
  • OpenTelemetry

Discussion