SLF4J ファサード ログ フレームワークのソース コード探索 | JD Cloud テクニカル チーム

1 SLF4J の概要

SLF4J は Java 用の Simple Logging Facade であり、Java のすべてのロギング フレームワークの単純な外観または抽象化を提供します。したがって、ユーザーは単一の依存関係で Log4j、Logback、JUL (java.util.logging) などのロギング フレームワークを操作できるようになります。必要なロギング フレームワークは、デプロイメント時にクラスパスに適切な jar ファイル (バインディング) を挿入することで挿入できます。ロギング フレームワークを変更する場合は、依存する slf4j バインディングを置き換えるだけです。たとえば、java.util.logging を log4j に置き換えるには、slf4j-jdk14-1.7.28.jar を slf4j-log4j12-1.7.28.jar に置き換えるだけです。

2 SLF4Jのソースコード解析

コードから始めて、コードをレイヤーごとに追加し、SLF4J の印刷ログを直感的に感じて、コードをソースまで遡っていきます。主に、SLF4J がファサードおよび他のロギング フレームワークとしてどのように分離されるかを理解します。

2.1 pom は slf4j-api への依存関係のみを参照しており、バージョンは 1.7.30 です。

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>

2.1.1 デモの実行

public class HelloSlf4j {

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

2.1.2 プロンプト情報をログに記録する

org.slf4j.impl.StaticLoggerBinder のバインドに失敗しました。クラスパス上にバインディングが見つからない場合、SLF4J はデフォルトで no-op 実装になります。

2.1.3 ソースコードの追跡

getLogger() メソッドをクリックすると、LoggerFactory が静的ファクトリを使用してロガーを作成していることが直感的にわかります。次の方法でステップごとにクリックすると、エラー レポートを簡単に見つけることができ、bind() メソッドで出力された例外ログ情報を確認できます。

org.slf4j.LoggerFactory#getLogger(java.lang.Class<?>)
org.slf4j.LoggerFactory#getLogger(java.lang.String)
org.slf4j.LoggerFactory#getILoggerFactory
org.slf4j.LoggerFactory#performInitialization
org.slf4j.LoggerFactory #練る

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()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
        } 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);
        } finally {
            postBindCleanUp();
        }
    }

バインディング メソッド findPossibleStaticLoggerBinderPathSet() をさらに分析すると、このパスのすべてのリソース "org/slf4j/impl/StaticLoggerBinder.class" が現在の ClassPath でクエリされていることがわかります。ここにはファイルがロードされていないか、複数のファイルがロードされている可能性があります。バインディングのないシーンや複数のバインディングについては、わかりやすいリマインダーが表示されます。ここでパスを介してリソースをロードする目的は、主に、さまざまな異常なロード シナリオを促すことです。

StaticLoggerBinder.getSingleton() 以下のコードは実際のバインディングであり、StaticLoggerBinder のインスタンスを取得します。ここで逆コンパイルすると、そのようなクラス StaticLoggerBinder がまったく存在しないことがわかります。

ファイルにロードされていない場合、上記のデモ実行の結果、NoSuchMethodError 例外が発生し、バインドされたシーンが存在しないことを示すプロンプト メッセージが出力されます。

findPossibleStaticLoggerBinderPathSet()メソッドのソースコードは以下の通りで、クラ​​スローダがパス経由でURLリソースを取得していることが分かります。

            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);
            }

2.2 pom 参照は logback-classic に依存します

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

2.2.1 デモを実行する

正常に印刷ログ情報が確認でき、異常はありません

2.2.2 ソースコードの追跡

この時点で、クリックしてメソッド StaticLoggerBinder.getSingleton() に入ると、クラス StaticLoggerBinder がパッケージ logback-classic によって提供され、SLF4J のインターフェース LoggerFactoryBinder を実装していることがわかります。StaticLoggerBinder の作成にはシングルトン パターンが使用され、このクラスの主な目的は、Logger を作成するファクトリを返すことです。ここでは、実際には ch.qos.logback.classic.LoggerContext のインスタンスが返され、このインスタンスによって ch.qos.logback.classic.Logger が作成されます。

UML のクラス図は次のとおりです。

2.3 Pom が log4j-slf4j-impl を再導入

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.9.1</version>
        </dependency>

2.3.1 デモを実行する

出力されるログは次のとおりで、2 つの StaticLoggerBinder.classes がバインドされていることが示されていますが、最終的には ch.qos.logback.classic.util.ContextSelectorStaticBinder がバインドされています。ここでは、クラスがロードされると、同じグローバル修飾名を持つクラスはロードできないことも確認されます。ここで Jar パッケージがロードされる順序は、クラスがロードされる順序を直接決定します。

SLF4J: Found binding in [jar:file:/D:/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.9.1/log4j-slf4j-impl-2.9.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
18:19:43.521 [main] INFO com.cj.HelloSlf4j - Hello World info

2.4 log4j-slf4j-impl と logback-classic のインポート位置変換

Pom ファイルが最初に log4j-slf4j-impl をインポートし、次に logback-classic をインポートする場合

2.4.1 デモを実行する

ログの出力結果を見ると、実際のバインディングは org.apache.logging.slf4j.Log4jLoggerFactory; であることがわかりますが、ログが正常に出力されず、log4j2 のログ設定が必要となります。これは、実際のバインディングが og4j-slf4j-impl パッケージ内の org/slf4j/impl/StaticLoggerBinder.class ファイルであることを示しています。ここでは、複数のブリッジング パッケージが導入されている場合、実際のバインディングは最初にロードされたファイルであることも検証されています。

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.9.1/log4j-slf4j-impl-2.9.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. Set system property 'log4j2.debug' to show Log4j2 internal initialization logging.

2.5 クラスのロード方法の変更

2.5.1 slf4j-api-1.7.30 バージョンのパッケージ化スキル

逆コンパイルのために slf4j-api-1.7.30-sources.jar を確認すると、そのようなクラス org.slf4j.impl.StaticLoggerBinder がまったく存在しないことがわかります。どうすれば正常にコンパイルできるでしょうか? このクラスはパッケージ化時に除外されたと思いますか? git 経由でソース コードをダウンロードすると、slf4j ソース コードに実際にこのファイル org/slf4j/impl/StaticLoggerBinder.class があることがわかります。ここでは、パッケージ化時に実装クラスを除外するための小さなトリックが使用されています。とても賢いです。

2.5.2 slf4j-api-2.0.0 バージョンでは SPI (サービス プロバイダー インターフェイス) を導入

このバージョンでは、SPI メソッドを使用して実装クラスをロードします。これは、以前の実装メソッドよりもはるかに洗練されています。ブリッジ パッケージは、META-INF/services/ の場所にある必要があるだけで、ファイル org.slf4j.spi.SLF4JServiceProvider (SLFJ4 によって提供されるインターフェイス名として名前が付けられます) を定義し、ファイル内で実装クラスを指定します。このブリッジング パッケージが導入されている限り、対応する実装のログ フレームワークに適合させることができます。

以下はSPIでロードされたソースコードです。

private static List<SLF4JServiceProvider> findServiceProviders() {
        ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
        List<SLF4JServiceProvider> providerList = new ArrayList();
        Iterator var2 = serviceLoader.iterator();

        while(var2.hasNext()) {
            SLF4JServiceProvider provider = (SLF4JServiceProvider)var2.next();
            providerList.add(provider);
        }

        return providerList;
     }

2.5.3 クラスロード方法の比較

2.6 SLF4J はバインディング ログ フレームワークを正式に実装しました

slf4j は、一般的に使用されるログ フレームワーク用のブリッジング パッケージと、非常に使いやすい詳細なドキュメントの説明を提供しています。
次の図は、SLF4J 公式 Web サイトで提供されており、さまざまなログ実装フレームワークと SLF4J の関係を示しています。

2.7 概要

  • SLF4J API は、一度に 1 つの基礎となるロギング フレームワークにバインドするように設計されています。さらに、SLF4J の導入後は、StaticLoggerBinder にロードできるか、複数の StaticLoggerBinder にロードできるかに関係なく、フレンドリーなプロンプトが表示され、ユーザー エクスペリエンスが非常に考慮されています。クラスパス上に複数のバインディングがある場合、SLF4J はそれらのバインディングが存在する場所の警告リストを発行します。クラスパス上で複数のバインディングが使用できる場合は、使用するバインディングを選択し、他のバインディングを削除する必要があります。
  • 実際、SLF4J のソース コードを見ると、全体的な設計と実装は非常にシンプルかつ明確で、見た目を整えるだけで、位置付けも非常に明確です。
  • SLF4J インターフェイスとそのデプロイメント モデルのシンプルさを考えると、新しいロギング フレームワークの開発者は SLF4J バインディングを簡単に作成できるはずです。
  • 現在主流のログ フレームワークには互換性があり、適応を実装することでサポートされます。ユーザーが SLF4J を選択する限り、将来的にログ フレームワークを変更する自由が保証されます。

3 SLF4J デザインパターンの使用

slf4j では、ファサード パターン、シングルトン パターン、静的ファクトリー パターンなどのいくつかの古典的なデザイン パターンが使用されています。次のデザイン パターンを分析してみましょう。

3.1 ファサードパターン

1) 説明する

ファサード パターン (外観パターンとも呼ばれます) では、サブシステムの外部とその内部の間の通信が統一されたオブジェクトを通じて実行される必要があります。ファサード パターンは、サブシステムを使いやすくする高レベルのインターフェイスを提供します。ファサード パターンは、クライアントの呼び出しを容易にするために使用されます。

Slf4j はログ使用標準を策定し、高レベルのインターフェイスを提供しています。コーディング プロセスは、ログの出力を実現するために、インターフェイス Logger とファクトリ クラス LoggerFactory に依存するだけで済みます。内部実装を気にする必要はありません。ログの詳細 log4j の実装。

2) 図

        Logger logger = LoggerFactory.getLogger(HelloSlf4j.class);
        logger.info("Hello World info");

3) 利点

デカップリングによりシステムの相互依存性が軽減されます。すべての依存関係はファサード オブジェクトに依存しており、サブシステムとは何の関係もありません。ビジネス層の開発では、基盤となるログ フレームワークの実装と詳細を気にする必要はなく、フレームワークの置き換えコストを考慮する必要もありません。将来コーディングするときに。
インターフェイスと実装は分離されており、基礎となる実装の詳細とインターフェイス指向のプログラミングが保護されています。

3.2 シングルトンパターン

1) 説明する

シングルトン パターンでは、クラスにインスタンスが 1 つだけ存在することが保証され、そのインスタンスへのグローバル アクセス ポイントが提供されます。
SLF4J アダプテーション パッケージでは、StaticLoggerBinder クラスを実装する必要がありますが、このクラス StaticLoggerBinder の実装はシングルトン モードを使用しており、最も単純な実装方法です。静的イニシャライザでは、直接 new StaticLoggerBinder() によってグローバル アクセス メソッドが提供されます。インスタンスを取得します。

2) UML図

3) 利点

シングルトン パターンでは、アクティブなシングルトンのインスタンスは 1 つだけあり、シングルトン クラスのすべてのインスタンス化は同じインスタンスを取得します。これにより、他のオブジェクトが自身をインスタンス化することが防止され、すべてのオブジェクトがインスタンスにアクセスできるようになります。シングルトン
モードには特定のスケーラビリティがあります。クラス自体がインスタンス化プロセスを制御し、インスタンス化プロセスを変更するときにクラスも対応するスケーラビリティを持ちます。

固有のインスタンスへの制御されたアクセスを提供します。

メモリ内にはインスタンスが 1 つだけあるため、メモリのオーバーヘッドが削減され、システムのパフォーマンスが向上します。

4 啓示

  • SLF4J のコード全体は短いながらも非常に洗練されていますが、ファサード モードがうまく活用されていることがわかります。ファサード モードは、インターフェイスを統一的に定義し、マルチバージョン実装の互換性を提供する方法に関するリファレンスも提供します。
  • SLF4J の定義と実装はユーザーにとって非常に使いやすいものであると同時に、完全なドキュメントの使用をガイドするためのさまざまなブリッジング パッケージが提供されています。全体として、ユーザー エクスペリエンスは素晴らしく、これが SLF4J の最も人気のある理由の 1 つである可能性があります。
  • 私たちはインターフェイス指向のプログラミングについてもっと考え、コードの結合を減らし、コードのスケーラビリティを向上させる必要があります。
  • SPI メソッドを使用して、拡張機能の実装をエレガントにロードします。
  • 優れた製品は設計され、最適化され、反復されます。

5 参考文献

著者: JD Logistics Cao Jun

出典: JD Cloud 開発者コミュニティ

{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10084131