入門Service Loader: SLF4JはなぜLogbackのロガーを取得できるのか
環境
- JDK 21
- lobback-classic 1.5.18
- slf4j-api 2.0.17
SLF4Jの謎
こんな感じでLogbackを追加します。
<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
...
こんな感じでログを出力します。
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文を見れば分かる通り、Logger
もLoggerFactory
も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
のインスタンスが生成される
// 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)
- postgresql.jarのMETA-INF/servicesフォルダには
- OpenTelemetry
- Kengo Todaさんの記事「JVMにおけるServiceLoaderとjavaagent」参照
Discussion