細流Tomcatのシリーズ(4) - クラスローダのTomcat

Tomcatの中でクラスローダを学習する前に、とTomcatモデルを破るために、親クラスローダのデリゲート理由を実現すべき理由は、まず親がそう何モデルを任命するJavaクラスローダで定義されているかを知る必要があります。

Javaのクラスローダ

クラスローダは、動的にJVMにロードされたJavaファイルを実行しているプログラムのために責任があります

Java仮想マシンが関係しているの観点から、それから、二つの異なるクラスローダがあります。

  • クラスローダ(ブートストラップクラスローダ)を起動します。このクラスローダは、C ++言語を使用している仮想マシン自体の一部です。

  • 他のクラスローダ:Java言語、外部の仮想マシンの独立した、と抽象クラスから派生したすべてのことでクラスローダjava.lang.ClassLoader他のクラスローダが大まかに分け、

    • ExtensionClassLoader:によるこのクラスローダExtClassLoaderのロードを担当して実装JAVA_HOME/lib/ext、または、ディレクトリ内のすべてのクラスをjava.ext.dirすべてのクラスで指定されたシステム変数パス。
    • ApplicationClassLoader:このクラスローダが構成されAppClassLoader、実装上のすべてのクラス、それはアプリケーションが独自のクラスローダをカスタマイズしていない場合は、指定されたユーザクラスパス(クラスパス)をロードする責任があり、その後、一般的には、デフォルトのクラスローダをプログラムすることです。
    • カスタムローダー:自分のニーズに応じて、カスタムアドオンローダーを特定のパス。

任意のクラスのために、私たちは一緒にそのクラスローダとクラス自体をロードすることによって、Java仮想マシンでその独自性を確立する必要があります

親委譲モデル

上の図は、親クラスローダ委譲モデルと呼ばれる階層構造を示しています。親委任モデルに加えて、ローダー、他の親は、独自のローダーを持っている必要があり、ブートクラスローダのトップを必要とします。ここでは、親子関係が継承を通じて、しかし設定することによって達成されていないのですparent達成するための変数を。

親委任モデルの作業プロセスは:あなたは自分自身でクラスをロードするための要求を受信した場合、このカテゴリにロードされませんが、クラスが開始されるまで、最初に各レベルを完了するために、親クラスローダに委譲します。この要求は、そうですローダー、このファイルをロードしていないだけで、親クラス、そしてサブクラスは独自のものをロードしようとします。

なぜ親はデリゲートモデルを設定する必要がありますか?実際に、このように配置されたオブジェクトクラス、などのJavaプログラムの安定動作を保証するためにrt.jarかかわらず、オブジェクトクラスをロードするクラスローダを、最終的にはそのように使用されるオブジェクトのすべてのクラスが同じで、上部にBootStrapClassLoaderを委託しますクラスは、逆に、ランダムクラスローダが、アプリケーションは非常に混乱となり、新しいクラスのオブジェクトを定義することができ、親委任モデルが存在しない場合。実際には、保護者の委任モデルのコードは非常に簡単です。ClassLoaderのloadClassメソッドで達成。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 首先,检查请求类是否被加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
            // 如果没被本类类加载器加载过,先委托给父类进行加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                // 如果没有父类,则表明在顶层,就交给BootStrap类加载器加载
                    c = findBootstrapClassOrNull(name);
                }
                // 如果最顶层的类也找不到,那么就会抛出ClassNotFoundException异常
            } catch (ClassNotFoundException e) {

            }
            // 如果父类都没有加载过此类,子类才开始加载此类
            if (c == null) {
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

复制代码

我々は、見ることができるfindClassのアプローチは独自のロジックのサブクラスを達成するために必要とされます。

クラスローダのTomcat

次の図は、与えられた公式文書のTomcatのクラスローダ図Tomcat9バージョンです。


      Bootstrap
          |
       System
          |
       Common
       /     \
  Webapp1   Webapp2 ..

复制代码
  • ブートストラップ:JavaはC言語を使用して、ローダーの最高で、主に、例えばJVMを起動するために必要なコアクラスをロードするために使用されている$JAVA_HOME/jre/lib/extカテゴリ・パスの下に。
  • システム:ロードCLASSPATH定義されているすべてのクラスのシステム変数のパスを。
  • 共通:Tomcatのパス内のlibファイル内のすべてのクラスをロードします。
  • webapp1、webapp2をは......:Webアプリケーションのパスプロジェクトの下のすべてのクラスをロードします。従って、アプリケーションのクラス間の分離を達成WebappClassLoaderに対応するプログラム。

Javaの親委任モデル図の上に、この3つの部分からは、反映されています。しかし、あなたは示されていないExtClassLoaderを見ることができ、ブートストラップとの合併として理解することができ、それはにすべてだJAVA_HOME/jre/lib次の負荷クラス。なぜTomcatのカスタムクラスローダそれはすべき?

  • 分離異なる用途:AおよびBは、Spring2.5と例えばAため、Tomcatで異なる用途に配備されています。Spring3.5とBは、2つのアプリケーションを使用すると、同じクラスローダを使用している場合は、Webアプリケーションのjarパッケージが起動しないカバーしますので。
  • 柔軟性:互いに独立したWebアプリケーション間のクラスローダ、その後、変更されたファイルに応じて、異なるクラスローダの元の復興を置き換えることができます。これは、他のアプリケーションには影響を与えません。
  • パフォーマンス:複数のアプリケーションにTomcatにデプロイした場合、複数のアプリケーションが同じライブラリの依存関係を持っています。そして、これと同じライブラリには、共通のクラスローダーがロードするようにすることができます。

TomcatのWebAppClassLoaderは、クラスローダをカスタマイズしました。両親は、メカニズムの委任を破ったことは、クラスローダが要求を受信した場合、それは親がロードローダーかけて片手見つからない場合は、自分自身をロードしようとする、ある、目的は自分の定義のWebアプリケーションの優先度クラスをロードすることです。私たちは、クラスが書き換えWebAppClassLoader loadClassメソッドを参照することができ、私達はこのルールを破るために持っているように、我々はloadClassメソッドを書き換える必要があり、その後のTomcat、デフォルトのClassLoader loadClassメソッドは、クラスをロードするためのモデル親デリゲートに基づいていることを知っています。

@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

    synchronized (getClassLoadingLock(name)) {
        Class<?> clazz = null;
        // 1. 从本地缓存中查找是否加载过此类
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        // 2. 从AppClassLoader中查找是否加载过此类
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        String resourceName = binaryNameToPath(name, false);
        // 3. 尝试用ExtClassLoader 类加载器加载类,防止Web应用覆盖JRE的核心类
        ClassLoader javaseLoader = getJavaseClassLoader();
        boolean tryLoadingFromJavaseLoader;
        try {
            URL url;
            if (securityManager != null) {
                PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                url = AccessController.doPrivileged(dp);
            } else {
                url = javaseLoader.getResource(resourceName);
            }
            tryLoadingFromJavaseLoader = (url != null);
        } catch (Throwable t) {
            tryLoadingFromJavaseLoader = true;
        }

        boolean delegateLoad = delegate || filter(name, true);

        // 4. 判断是否设置了delegate属性,如果设置为true那么就按照双亲委派机制加载类
        if (delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader1 " + parent);
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        // 5. 默认是设置delegate是false的,那么就会先用WebAppClassLoader进行加载
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Loading class from local repository");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // 6. 如果此时在WebAppClassLoader没找到类,那么就委托给AppClassLoader去加载
        if (!delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader at end: " + parent);
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
    }
    throw new ClassNotFoundException(name);
}

复制代码

最後に、要約Tomcatの公式ウェブサイトの言葉を借ります:

デフォルトのWebアプリケーションのクラスのロード順は(親がデリゲートのルールを破った)です。

  1. ロードされたJVMを起動しBootStrapClassLoader。
  2. 負荷の下では、Webアプリケーション/WEB-INF/classesクラスインチ
  3. 負荷の下では、Webアプリケーション/WEB-INF/lib/*.japクラスの瓶インチ
  4. クラス次のローディング経路上で定義したとおりのシステム。
  5. 以下に上記で定義された一般的なクラスのロードパス。

あなたが設定ファイルで構成されている場合<Loader delegate="true"/>、そのルールは次のように両親の代理人、ロード順序に従うことです。

  1. ロードされたJVMを起動しBootStrapClassLoader。
  2. クラス次のローディング経路上で定義したとおりのシステム。
  3. 以下に上記で定義された一般的なクラスのロードパス。
  4. 負荷の下では、Webアプリケーション/WEB-INF/classesクラスインチ
  5. 負荷の下では、Webアプリケーション/WEB-INF/lib/*.japクラスの瓶インチ

過去の記事

どのようにブレークポイントTomcatのソースコードのデバッグ

細流Tomcatのシリーズ(1) - 全体的なアーキテクチャ

細流Tomcatのシリーズ(2) - エンドポイントソースの解析

サイケTomcatのシリーズ(3) - スタートを行うと、ボタンを停止するにはどのようにTomcatの

奇妙な旅の問題を探すにStackOverflowError

フリーハンドのラインとシンプルなRPCフレームワーク

フリーハンドのラインとシンプルなRPCフレームワーク(2) - リノベーションプロジェクト

参考記事

おすすめ

転載: juejin.im/post/5d1efd96f265da1bd04f0032