web-dev-qa-db-ja.com

Javaでparent-last / child-first ClassLoaderを作成する方法、または親CLに既にロードされている古いXercesバージョンをオーバーライドする方法

親-最後/子-最初のクラスローダーを作成したいと思います。最初に子クラスローダーでクラスを検索し、次にその親ClassLoaderに委任してクラスを検索するクラスローダー。

説明:

完全なClassLoading分離を取得するには、親としてnullを渡すURLClassLoaderのようなものを使用する必要があることを知っています。これは、前の質問の this answer のおかげです。

しかし、現在の質問は私がこの問題を解決するのに役立ちます:

  1. 私のコード+依存jarは、そのシステムのClassLoaderを親として設定するClassLoader(URLClassLoader)を使用して、既存のシステムにロードされています

  2. そのシステムは、私が必要とするバージョンと互換性のないバージョンのいくつかのライブラリを使用しています(たとえば、Xercesの古いバージョンで、コードを実行できません)

  3. スタンドアロンで実行するとコードは完全に正常に実行されますが、そのClassLoaderから実行すると失敗します

  4. 親ClassLoader内の他の多くのクラスにアクセスする必要があるかどうか

  5. したがって、私がオーバーライドできるようにしたいのですが、親クラスローダーが自分のjarで「jar」します。呼び出すクラスが子クラスローダーで見つかった場合(たとえば、1人のユーザーではなく、自分のjarで新しいバージョンのXercesを提供しました)私のコードとjarをロードしたClassLoaderによって。

これは私のコードとジャーをロードするシステムのコードです(これは変更できません)

File addOnFolder = new File("/addOns"); 
URL url = addOnFolder.toURL();         
URL[] urls = new URL[]{url};
ClassLoader parent = getClass().getClassLoader();
cl = URLClassLoader.newInstance(urls, parent);

ここに「私の」コードがあります(Flying Sauserの「Hello World」コードのデモから完全に引用):

package flyingsaucerpdf;

import Java.io.*;
import com.lowagie.text.DocumentException;
import org.xhtmlrenderer.pdf.ITextRenderer;

public class FirstDoc {

    public static void main(String[] args) 
            throws IOException, DocumentException {

        String f = new File("sample.xhtml").getAbsolutePath();
        System.out.println(f);
        //if(true) return;
        String inputFile = "sample.html";
        String url = new File(inputFile).toURI().toURL().toString();
        String outputFile = "firstdoc.pdf";
        OutputStream os = new FileOutputStream(outputFile);

        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocument(url);
        renderer.layout();
        renderer.createPDF(os);

        os.close();
    }
}

これはスタンドアロンで動作します(メインを実行)が、親CLを介してロードされると、このエラーで失敗します。

org.w3c.dom.DOMException:NAMESPACE_ERR:名前空間に関して正しくない方法でオブジェクトを作成または変更しようとしました。

おそらく親システムが古いバージョンのXercesを使用しているためです。/addOnsフォルダーに適切なXerces jarを提供しても、そのクラスは親システムによって既にロードおよび使用されているため、独自のコードを使用できません。代表団の指示による私自身の瓶。これにより私の質問がより明確になることを願っています。また、以前に質問されたことがあると思います。 (おそらく私は正しい質問をしません)

35
Eran Medan

私はこの正確な問題を解決しなければならなかったので、今日はあなたの幸運な日です。ただし、クラスローディングの内部は怖い場所です。これを行うと、Java=の設計者は、親最後のクラスローダーが必要になるとは想像もしていなかったと思います。

使用するには、子クラスローダーで使用できるクラスまたはjarを含むURLのリストを指定します。

_/**
 * A parent-last classloader that will try the child classloader first and then the parent.
 * This takes a fair bit of doing because Java really prefers parent-first.
 * 
 * For those not familiar with class loading trickery, be wary
 */
private static class ParentLastURLClassLoader extends ClassLoader 
{
    private ChildURLClassLoader childClassLoader;

    /**
     * This class allows me to call findClass on a classloader
     */
    private static class FindClassClassLoader extends ClassLoader
    {
        public FindClassClassLoader(ClassLoader parent)
        {
            super(parent);
        }

        @Override
        public Class<?> findClass(String name) throws ClassNotFoundException
        {
            return super.findClass(name);
        }
    }

    /**
     * This class delegates (child then parent) for the findClass method for a URLClassLoader.
     * We need this because findClass is protected in URLClassLoader
     */
    private static class ChildURLClassLoader extends URLClassLoader
    {
        private FindClassClassLoader realParent;

        public ChildURLClassLoader( URL[] urls, FindClassClassLoader realParent )
        {
            super(urls, null);

            this.realParent = realParent;
        }

        @Override
        public Class<?> findClass(String name) throws ClassNotFoundException
        {
            try
            {
                // first try to use the URLClassLoader findClass
                return super.findClass(name);
            }
            catch( ClassNotFoundException e )
            {
                // if that fails, we ask our real parent classloader to load the class (we give up)
                return realParent.loadClass(name);
            }
        }
    }

    public ParentLastURLClassLoader(List<URL> classpath)
    {
        super(Thread.currentThread().getContextClassLoader());

        URL[] urls = classpath.toArray(new URL[classpath.size()]);

        childClassLoader = new ChildURLClassLoader( urls, new FindClassClassLoader(this.getParent()) );
    }

    @Override
    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        try
        {
            // first we try to find a class inside the child classloader
            return childClassLoader.findClass(name);
        }
        catch( ClassNotFoundException e )
        {
            // didn't find it, try the parent
            return super.loadClass(name, resolve);
        }
    }
}
_

[〜#〜] edit [〜#〜]:Sergioとɹoƃıは、同じクラス名で_.loadClass_を呼び出すと、LinkageErrorが発生することを指摘しました。これは真実ですが、このクラスローダーの通常のユースケースは、それをスレッドのクラスローダーThread.currentThread().setContextClassLoader()として、またはClass.forName()を介して設定することであり、そのまま動作します。

ただし、.loadClass()が直接必要な場合は、このコードを上部のChildURLClassLoader findClassメソッドに追加できます。

_                Class<?> loaded = super.findLoadedClass(name);
                if( loaded != null )
                    return loaded;
_
30
karoberts

次のコードは私が使用するものです。親チェーンを壊さないという他の答えよりも優れています(getClassLoader().getParent()をフォローできます)。

また、ホイールを再発明せず、他のオブジェクトに依存しないため、TomcatのWebappClassLoaderよりも優れています。 URLClassLoaderのコードを可能な限り再利用します。

(それはまだシステムクラスローダーを尊重していませんが、修正されたら回答を更新します)

システムクラスローダーを尊重します(Java。*クラス、承認されたディレクトリなど)。セキュリティがオンになっていて、クラスローダーがその親にアクセスできない場合にも機能します(そうです、この状況は奇妙ですが、可能です)。

public class ChildFirstURLClassLoader extends URLClassLoader {

    private ClassLoader system;

    public ChildFirstURLClassLoader(URL[] classpath, ClassLoader parent) {
        super(classpath, parent);
        system = getSystemClassLoader();
    }

    @Override
    protected synchronized Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            if (system != null) {
                try {
                    // checking system: jvm classes, endorsed, cmd classpath, etc.
                    c = system.loadClass(name);
                }
                catch (ClassNotFoundException ignored) {
                }
            }
            if (c == null) {
                try {
                    // checking local
                    c = findClass(name);
                } catch (ClassNotFoundException e) {
                    // checking parent
                    // This call to loadClass may eventually call findClass again, in case the parent doesn't find anything.
                    c = super.loadClass(name, resolve);
                }
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

    @Override
    public URL getResource(String name) {
        URL url = null;
        if (system != null) {
            url = system.getResource(name); 
        }
        if (url == null) {
            url = findResource(name);
            if (url == null) {
                // This call to getResource may eventually call findResource again, in case the parent doesn't find anything.
                url = super.getResource(name);
            }
        }
        return url;
    }

    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        /**
        * Similar to super, but local resources are enumerated before parent resources
        */
        Enumeration<URL> systemUrls = null;
        if (system != null) {
            systemUrls = system.getResources(name);
        }
        Enumeration<URL> localUrls = findResources(name);
        Enumeration<URL> parentUrls = null;
        if (getParent() != null) {
            parentUrls = getParent().getResources(name);
        }
        final List<URL> urls = new ArrayList<URL>();
        if (systemUrls != null) {
            while(systemUrls.hasMoreElements()) {
                urls.add(systemUrls.nextElement());
            }
        }
        if (localUrls != null) {
            while (localUrls.hasMoreElements()) {
                urls.add(localUrls.nextElement());
            }
        }
        if (parentUrls != null) {
            while (parentUrls.hasMoreElements()) {
                urls.add(parentUrls.nextElement());
            }
        }
        return new Enumeration<URL>() {
            Iterator<URL> iter = urls.iterator();

            public boolean hasMoreElements() {
                return iter.hasNext(); 
            }
            public URL nextElement() {
                return iter.next();
            }
        };
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        URL url = getResource(name);
        try {
            return url != null ? url.openStream() : null;
        } catch (IOException e) {
        }
        return null;
    }

}
18
Yoni

JettyまたはTomcatのいずれかのソースコードを読み取ることにより、どちらもwebappセマンティクスを実装するための親-最後のクラスローダーを提供します。

https://github.com/Apache/Tomcat/blob/7.0.93/Java/org/Apache/catalina/loader/WebappClassLoaderBase.Java

つまり、findClassクラスのClassLoaderメソッドをオーバーライドします。しかし、それを盗むことができるのに、なぜ車輪を再発明するのでしょうか?

さまざまな更新を読んでいると、XML SPIシステムでいくつかの古典的な問題が発生したことがわかります。

一般的な問題はこれです。完全に分離されたクラスローダーを作成すると、返されるオブジェクトを使用することが難しくなります。共有を許可すると、親に間違ったバージョンのものが含まれているときに問題が発生する可能性があります。

OSGiが発明されたのは、このすべての狂気に対処するためですが、それは飲み込むには大きな問題です。

Webappsでも、クラスローダーは、コンテナーとwebappがそれらの間のAPIに同意する必要があるという前提で、一部のパッケージを 'local-first'処理から除外します。

13
bmargulies

(私が見つけたソリューションの更新については下部を参照してください)

AntClassLoaderは最初/最後の親をサポートしているようです(まだテストしていません)

http://svn.Apache.org/repos/asf/ant/core/trunk/src/main/org/Apache/tools/ant/AntClassLoader.Java

これがスニペットです

/**
 * Creates a classloader for the given project using the classpath given.
 *
 * @param parent The parent classloader to which unsatisfied loading
 *               attempts are delegated. May be <code>null</code>,
 *               in which case the classloader which loaded this
 *               class is used as the parent.
 * @param project The project to which this classloader is to belong.
 *                Must not be <code>null</code>.
 * @param classpath the classpath to use to load the classes.
 *                  May be <code>null</code>, in which case no path
 *                  elements are set up to start with.
 * @param parentFirst If <code>true</code>, indicates that the parent
 *                    classloader should be consulted  before trying to
 *                    load the a class through this loader.
 */
public AntClassLoader(
    ClassLoader parent, Project project, Path classpath, boolean parentFirst) {
    this(project, classpath);
    if (parent != null) {
        setParent(parent);
    }
    setParentFirst(parentFirst);
    addJavaLibraries();
}

更新:

見つかりました this 同様に、最後の手段としてGoogleでクラス名を推測し始めたとき(これはChildFirstURLClassLoaderが生成したものです)-正しくないようです

更新2:

1番目のオプション(AntClassLoader)はAntに非常に結合されています(プロジェクトコンテキストが必要であり、URL[]を渡すのは簡単ではありません)

2番目のオプション(google-codeの OSGIプロジェクト から)は、システムクラスローダーの前に親クラスローダーを検索するため、私が必要としていたものではありませんでした(Antクラスローダーが正しく処理します)。私が見る問題は、親のクラスローダーに、JDK 1.4にはなかったが1.5で追加された機能のjar(必要ではない)が含まれていると考えています。これは、親の最後のクラスローダーとして害はありません(通常の委任モデル(URLClassLoaderなど)は常にJDKのクラスを最初にロードしますが、ここでは子ファーストナイーブ実装は、親クラスローダーの古い冗長なjarを明らかにし、JDK/JRE独自の実装を隠しているようです。

特定のソリューション(Ant、Catalina/Tomcat)に結合されていない、認定済みの十分にテストされた、成熟したParent Last/Child Firstの正しい実装をまだ見つけていません

Update 3-見つけました!私は間違った場所を探していました、

私がしたことは、META-INF/services/javax.xml.transform.TransformerFactoryを追加して、古いXalanのcom.Sun.org.Apache.xalan.internal.xsltc.trax.TransformerFactoryImplの代わりにJDKのorg.Apache.xalan.processor.TransformerFactoryImplを復元することだけでした。

私がまだ「自分の答えを受け入れない」唯一の理由は、META-INF/servicesアプローチが通常のクラスと同じクラスローダーの委任を持っているかどうかがわからないことです(たとえば、親が最初/子が最後か、親-最後/子-最初

2
Eran Medan

findClass()およびloadClass()をオーバーライドして、子ファーストクラスローダーを実装できます。


/**
 * Always throws {@link ClassNotFoundException}. Is called if parent class loader
 * did not find class.
 */
@Override
protected final Class findClass(String name)
        throws ClassNotFoundException
{
    throw new ClassNotFoundException();
}

@Override
protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)){
        /*
         * Check if we have already loaded this class.
         */
        Class c = findLoadedClass(name);

        if (c == null){
            try {
                /*
                 * We haven't previously loaded this class, try load it now
                 * from SUPER.findClass()
                 */
                c = super.findClass(name);
            }catch (ClassNotFoundException ignore){
                /*
                 * Child did not find class, try parent.
                 */
                return super.loadClass(name, resolve);
            }
        }

        if (resolve){
            resolveClass(c);
        }

        return c;
    }
}
0
Jesse