実行時にJARファイルを動的にロードする方法は?


308

なぜこれをJavaで行うのがそれほど難しいのですか?任意の種類のモジュールシステムが必要な場合は、JARファイルを動的にロードできる必要があります。独自ClassLoaderにを作成する方法はあると言われていますが、JARファイルを引数としてメソッドを呼び出すのと同じくらい簡単な(少なくとも私の心の中で)必要な作業には、多くの作業が必要です。

これを行う簡単なコードの提案はありますか?


4
私は同じことをしたいのですが、ロードされたjarをよりサンドボックス化された環境で実行します(セキュリティ上の理由から)。たとえば、すべてのネットワークとファイルシステムへのアクセスをブロックしたいとします。
Jus12

回答:


253

難しいのはセキュリティです。クラスローダーは不変であることを意図しています。実行時にそれにクラスを追加することはできません。システムのクラスローダーで動作することに本当に驚いています。独自の子クラスローダーを作成する方法は次のとおりです。

URLClassLoader child = new URLClassLoader(
        new URL[] {myJar.toURI().toURL()},
        this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);

痛みを伴うが、それはあります。


16
このアプローチの唯一の問題は、どのクラスがどのjarにあるかを知る必要があることです。jarのディレクトリをロードしてからクラスをインスタンス化するのではなく、誤解していますか?
Allain Lalonde、

10
このメソッドはIDEで実行するとうまく機能しますが、JARをビルドするときにClass.forName()を呼び出すとClassNotFoundExceptionが発生します。
darrickc 2009

29
このアプローチを使用する場合は、クラスごとにこのloadメソッドを2回以上呼び出さないようにする必要があります。ロード操作ごとに新しいクラスローダーを作成しているため、クラスがすでにロードされているかどうかはわかりません。これは悪い結果をもたらす可能性があります。たとえば、クラスが数回読み込まれ、静的フィールドが複数回存在するため、シングルトンは機能しません。
Eduard Wirch、

8
動作します。jar内の他のクラスへの依存関係があっても。最初の行は不完全でした。URLClassLoader child = new URLClassLoader (new URL[] {new URL("file://./my.jar")}, Main.class.getClassLoader());jarファイルが呼び出さmy.jarれ、同じディレクトリにあると想定して使用しました。
ジョー

4
URLを忘れないでくださいurl = file.toURI()。toURL();
johnstosh

139

次のソリューションはリフレクションを使用してカプセル化をバイパスするためハックですが、問題なく機能します。

File file = ...
URL url = file.toURI().toURL();

URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);

40
この応答に関するすべての活動から、さまざまなシステムの運用環境でどのくらいのハッキングが実行されているのか不思議に思います。私は答えを知りたいのかわかりません
Andrei Savu 2015年

6
システムクラスローダーがたまたまURLClassLoader以外のものである場合、うまく機能しません...
Gus

6
Java 9+ URLClassLoader.class.getDeclaredMethod("addURL", URL.class)は、これがリフレクションの違法な使用であり、将来は失敗することを警告しています。
チャールウィード2018

1
このコードをJava 9以降で動作するように更新する方法はありますか?
FiReTiTi

1
@FiReTiTi はい !!
モルデチャイ

51

たとえば、Eclipse Platformに実装されているOSGiを見てください。それはまさにそれを行います。いわゆるバンドルと呼ばれるバンドルをインストール、アンインストール、開始、および停止できます。これらは事実上JARファイルです。ただし、実行時にJARファイルで動的に検出できるサービスなどを提供するため、もう少し多くのことができます。

または、Java Module Systemの仕様を参照してください。


41

どの程度JCLクラスローダフレームワーク?認めざるを得ません、使ったことはありませんが、有望に見えます。

使用例:

JarClassLoader jcl = new JarClassLoader();
jcl.add("myjar.jar"); // Load jar file  
jcl.add(new URL("http://myserver.com/myjar.jar")); // Load jar from a URL
jcl.add(new FileInputStream("myotherjar.jar")); // Load jar file from stream
jcl.add("myclassfolder/"); // Load class folder  
jcl.add("myjarlib/"); // Recursively load all jar files in the folder/sub-folder(s)

JclObjectFactory factory = JclObjectFactory.getInstance();
// Create object of loaded class  
Object obj = factory.create(jcl, "mypackage.MyClass");

9
また、バグが多く、findResources(...)などの重要な実装が不足しています。特定のことがうまくいかない理由を調査する素晴らしい夜を過ごす準備をしてください=)
セルゲイカルプシン14

プロジェクトが2番目のメジャーバージョンに更新されているため、@ SergeyKarpushinの主張がまだ残っているのかどうか、私は今でも疑問に思っています。経験を聞きたいです。
Erdin Eray

2
@ ErdinEray、OpenJDKへの切り替えを「強制」されたので、私も自分自身に質問するのは非常に良い質問です。私はまだJavaプロジェクトに取り組んでおり、最近Open JDKが失敗するという証拠はありません(当時は問題がありました)。私は何か他のものにぶつかるまで私の主張を取り下げると思います。
セルゲイカルプシン

20

これは非推奨ではないバージョンです。オリジナルを変更して、廃止された機能を削除しました。

/**************************************************************************************************
 * Copyright (c) 2004, Federal University of So Carlos                                           *
 *                                                                                                *
 * All rights reserved.                                                                           *
 *                                                                                                *
 * Redistribution and use in source and binary forms, with or without modification, are permitted *
 * provided that the following conditions are met:                                                *
 *                                                                                                *
 *     * Redistributions of source code must retain the above copyright notice, this list of      *
 *       conditions and the following disclaimer.                                                 *
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of   *
 *     * conditions and the following disclaimer in the documentation and/or other materials      *
 *     * provided with the distribution.                                                          *
 *     * Neither the name of the Federal University of So Carlos nor the names of its            *
 *     * contributors may be used to endorse or promote products derived from this software       *
 *     * without specific prior written permission.                                               *
 *                                                                                                *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS                            *
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT                              *
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR                          *
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR                  *
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,                          *
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,                            *
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR                             *
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF                         *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING                           *
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS                             *
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                   *
 **************************************************************************************************/
/*
 * Created on Oct 6, 2004
 */
package tools;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Useful class for dynamically changing the classpath, adding classes during runtime. 
 */
public class ClasspathHacker {
    /**
     * Parameters of the method to add an URL to the System classes. 
     */
    private static final Class<?>[] parameters = new Class[]{URL.class};

    /**
     * Adds a file to the classpath.
     * @param s a String pointing to the file
     * @throws IOException
     */
    public static void addFile(String s) throws IOException {
        File f = new File(s);
        addFile(f);
    }

    /**
     * Adds a file to the classpath
     * @param f the file to be added
     * @throws IOException
     */
    public static void addFile(File f) throws IOException {
        addURL(f.toURI().toURL());
    }

    /**
     * Adds the content pointed by the URL to the classpath.
     * @param u the URL pointing to the content to be added
     * @throws IOException
     */
    public static void addURL(URL u) throws IOException {
        URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
        Class<?> sysclass = URLClassLoader.class;
        try {
            Method method = sysclass.getDeclaredMethod("addURL",parameters);
            method.setAccessible(true);
            method.invoke(sysloader,new Object[]{ u }); 
        } catch (Throwable t) {
            t.printStackTrace();
            throw new IOException("Error, could not add URL to system classloader");
        }        
    }

    public static void main(String args[]) throws IOException, SecurityException, ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{
        addFile("C:\\dynamicloading.jar");
        Constructor<?> cs = ClassLoader.getSystemClassLoader().loadClass("test.DymamicLoadingTest").getConstructor(String.class);
        DymamicLoadingTest instance = (DymamicLoadingTest)cs.newInstance();
        instance.test();
    }
}

19
古いスレッドをぶつけたくないのですが、stackoverflowのすべてのコンテンツがCCライセンスであることを指摘したいと思います。あなたの著作権声明は事実上無効です。stackoverflow.com/faq#editing
Huckle

43
ええと。技術的には、元のコンテンツはCCライセンスですが、ここに著作権で保護されたコンテンツを投稿しても、コンテンツが著作権で保護されているという事実は削除されません。ミッキーマウスの写真を投稿しても、CCライセンスにはなりません。だから私は著作権ステートメントを追加し直しています。
Jason S

19

ここにリストされているほとんどのソリューションは、ハック(JDK 9以前)の設定が難しい(エージェント)か、機能しなくなった(JDK 9以降)かのどちらかですが、明確に文書化された方法について誰も言及しなかったのは本当に衝撃的です。

カスタムシステムクラスローダーを作成して、自由に操作できます。リフレクションは不要で、すべてのクラスが同じクラスローダーを共有します。

JVMを起動するときに、次のフラグを追加します。

java -Djava.system.class.loader=com.example.MyCustomClassLoader

クラスローダーには、クラスローダーを受け入れるコンストラクターが必要です。これは、その親として設定する必要があります。コンストラクターはJVMの起動時に呼び出され、実際のシステムクラスローダーが渡されます。メインクラスはカスタムローダーによってロードされます。

jarを追加するにはClassLoader.getSystemClassLoader()、呼び出してクラスにキャストするだけです。

慎重に作成されたクラスローダーについては、この実装を確認してください。add()メソッドをパブリックに変更できることに注意してください。


ありがとう-これは本当に役に立ちます!JDK 8以前のWeb使用方法に関する他のすべての参照-これには複数の問題があります。
Vishal Biyani

15

Javaの9、との答えはURLClassLoader、今のようなエラーを与えます:

java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader

これは、使用されるクラスローダーが変更されたためです。代わりに、システムクラスローダーに追加するには、エージェントを介してインストルメンテーション APIを使用できます。

エージェントクラスを作成します。

package ClassPathAgent;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;

public class ClassPathAgent {
    public static void agentmain(String args, Instrumentation instrumentation) throws IOException {
        instrumentation.appendToSystemClassLoaderSearch(new JarFile(args));
    }
}

META-INF / MANIFEST.MFを追加し、エージェントクラスを使用してJARファイルに配置します。

Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent

エージェントを実行します。

これは、byte-buddy-agentライブラリを使用して、実行中のJVMにエージェントを追加します。

import java.io.File;

import net.bytebuddy.agent.ByteBuddyAgent;

public class ClassPathUtil {
    private static File AGENT_JAR = new File("/path/to/agent.jar");

    public static void addJarToClassPath(File jarFile) {
        ByteBuddyAgent.attach(AGENT_JAR, String.valueOf(ProcessHandle.current().pid()), jarFile.getPath());
    }
}

9

私が見つけた最高のものは、XBeanプロジェクトの一部であるorg.apache.xbean.classloader.JarFileClassLoaderです

特定のディレクトリ内のすべてのlibファイルからクラスローダーを作成するために過去に使用した短いメソッドを次に示します

public void initialize(String libDir) throws Exception {
    File dependencyDirectory = new File(libDir);
    File[] files = dependencyDirectory.listFiles();
    ArrayList<URL> urls = new ArrayList<URL>();
    for (int i = 0; i < files.length; i++) {
        if (files[i].getName().endsWith(".jar")) {
        urls.add(files[i].toURL());
        //urls.add(files[i].toURI().toURL());
        }
    }
    classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(), 
        urls.toArray(new URL[urls.size()]), 
        GFClassLoader.class.getClassLoader());
}

次に、クラスローダーを使用するには、次のようにします。

classLoader.loadClass(name);

プロジェクトはあまりよく維持されていないように見えることに注意してください。たとえば、将来のロードマップには、2014年のリリースがいくつか含まれています。
Zero3

6

Androidで作業している場合は、次のコードが機能します。

String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");

6

以下は、Javaの新しいバージョンと互換性を持たせるためのAllainのメソッドの簡単な回避策です。

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
    Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class);
    method.setAccessible(true);
    method.invoke(classLoader, new File(jarPath).toURI().toURL());
} catch (NoSuchMethodException e) {
    Method method = classLoader.getClass()
            .getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
    method.setAccessible(true);
    method.invoke(classLoader, jarPath);
}

特定のJVMの内部実装に関する知識に依存しているため、理想的ではなく、普遍的なソリューションではないことに注意してください。ただし、標準のOpenJDKまたはOracle JVMを使用することがわかっている場合、これは迅速で簡単な回避策です。また、JVMの新しいバージョンがリリースされたときに、いつか壊れる可能性があるため、そのことを覚えておく必要があります。


Javaの11.0.2で、私が得る:Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation(java.lang.String) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @18ef96
リチャードZAK

アプリケーションサーバー環境のJava 8 EEで動作します。

4

jodonnellによって提案されたソリューションは優れていますが、少し強化する必要があります。この投稿を使用して、成功したアプリケーションを開発しました。

現在のスレッドを割り当てる

まず、追加する必要があります

Thread.currentThread().setContextClassLoader(classLoader);

または、jarに格納されているリソース(spring / context.xmlなど)をロードできなくなります。

含めない

jarを親クラスローダーに入れないと、だれが何をロードしているかを理解できません。

URLClassLoaderを使用したjarのリロードの問題も参照してください

ただし、OSGiフレームワークは依然として最良の方法です。


2
あなたの答えは少し混乱しているように見えますが、それが単なる改善である場合は、おそらくjodonnellによる答えへのコメントとして適しています。
Zero3 2016年

4

Allainのハッキングソリューションの別のバージョンは、JDK 11でも動作します。

File file = ...
URL url = file.toURI().toURL();
URLClassLoader sysLoader = new URLClassLoader(new URL[0]);

Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});

JDK 11では、いくつかの非推奨の警告が表示されますが、JDK 11でAllainソリューションを使用する場合の一時的なソリューションとして機能します。


jarも削除できますか?
user7294900

3

インストゥルメンテーションを使用した別の実用的な解決策。これには、クラスローダーの検索を変更して、依存クラスのクラスの可視性に関する問題を回避できるという利点があります。

エージェントクラスを作成する

この例では、コマンドラインから呼び出されたのと同じjar上にある必要があります。

package agent;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.jar.JarFile;

public class Agent {
   public static Instrumentation instrumentation;

   public static void premain(String args, Instrumentation instrumentation) {
      Agent.instrumentation = instrumentation;
   }

   public static void agentmain(String args, Instrumentation instrumentation) {
      Agent.instrumentation = instrumentation;
   }

   public static void appendJarFile(JarFile file) throws IOException {
      if (instrumentation != null) {
         instrumentation.appendToSystemClassLoaderSearch(file);
      }
   }
}

MANIFEST.MFを変更する

エージェントへの参照を追加します。

Launcher-Agent-Class: agent.Agent
Agent-Class: agent.Agent
Premain-Class: agent.Agent

私は実際にはNetbeansを使用しているので、この投稿はmanifest.mfを変更する方法について説明します

ランニング

Launcher-Agent-Class唯一のJDK 9+に支持され、コマンドラインで明示的にそれを定義せずにエージェントをロードするための責任があるされています。

 java -jar <your jar>

JDK 6+で機能する方法は、-javaagent引数を定義することです。

java -javaagent:<your jar> -jar <your jar>

実行時に新しいJARを追加する

その後、次のコマンドを使用して、必要に応じてjarを追加できます。

Agent.appendJarFile(new JarFile(<your file>));

ドキュメントでこれを使用しても問題は見つかりませんでした。


何らかの理由でこのソリューションを使用すると、「スレッド "main"で例外java.lang.ClassNotFoundException:agent.Agent」が発生します。「Agent」クラスをメインの「war」アプリケーションにパッケージ化したので、そこにあると確信しています
Sergei Ledvanov '15

3

将来誰かがこれを検索する場合に備えて、この方法はOpenJDK 13.0.2で私にとってはうまくいきます。

実行時に動的にインスタンス化する必要がある多くのクラスがあり、それぞれ異なるクラスパスを持つ可能性があります。

このコードでは、ロードしようとしているクラスに関するメタデータを保持するpackというオブジェクトがすでにあります。getObjectFile()メソッドは、クラスのクラスファイルの場所を返します。getObjectRootPath()メソッドは、インスタンス化しようとしているクラスを含むクラスファイルを含むbin /ディレクトリへのパスを返します。getLibPath()メソッドは、クラスが含まれるモジュールのクラスパスを構成するjarファイルを含むディレクトリへのパスを返します。

File object = new File(pack.getObjectFile()).getAbsoluteFile();
Object packObject;
try {
    URLClassLoader classloader;

    List<URL> classpath = new ArrayList<>();
    classpath.add(new File(pack.getObjectRootPath()).toURI().toURL());
    for (File jar : FileUtils.listFiles(new File(pack.getLibPath()), new String[] {"jar"}, true)) {
        classpath.add(jar.toURI().toURL());
    }
    classloader = new URLClassLoader(classpath.toArray(new URL[] {}));

    Class<?> clazz = classloader.loadClass(object.getName());
    packObject = clazz.getDeclaredConstructor().newInstance();

} catch (Exception e) {
    e.printStackTrace();
    throw e;
}
return packObject;

以前はこれを行うためにMaven依存関係:org.xeustechnologies:jcl-core:2.8を使用していましたが、JDK 1.8を通過した後、それがフリーズして、Reference :: waitForReferencePendingList()で「参照待ち」でスタックして戻ってこないことがありました。

また、インスタンス化しようとしているクラスが、すでにインスタンス化したクラスと同じモジュールにある場合に再利用できるように、クラスローダーのマップを保持しています。


2

私が始めたこのプロジェクトを見てください:proxy-object lib

このlibは、ファイルシステムまたはその他の場所からjarをロードします。ライブラリの競合がないことを確認するために、jar専用のクラスローダーを使用します。ユーザーは、ロードされたjarから任意のオブジェクトを作成し、その上で任意のメソッドを呼び出すことができます。このlibは、Java 7をサポートするコードベースからJava 8でコンパイルされたjarをロードするように設計されています。

オブジェクトを作成するには:

    File libDir = new File("path/to/jar");

    ProxyCallerInterface caller = ObjectBuilder.builder()
            .setClassName("net.proxy.lib.test.LibClass")
            .setArtifact(DirArtifact.builder()
                    .withClazz(ObjectBuilderTest.class)
                    .withVersionInfo(newVersionInfo(libDir))
                    .build())
            .build();
    String version = caller.call("getLibVersion").asString();

ObjectBuilderは、ファクトリメソッド、静的関数の呼び出し、およびインターフェイス実装のコールバックをサポートしています。Readmeページにさらに例を掲載します。


2

これは遅い応答になる可能性があります。DataMelt(http://jwork.org/dmelt)のjhplot.Webクラスを使用して、これ(fastutil-8.2.2.jarの簡単な例)のように実行できます。

import jhplot.Web;
Web.load("http://central.maven.org/maven2/it/unimi/dsi/fastutil/8.2.2/fastutil-8.2.2.jar"); // now you can start using this library

ドキュメントによると、このファイルは「lib / user」内にダウンロードされ、動的にロードされるため、同じプログラムでこのjarファイルのクラスをすぐに使用できます。


1

Java 8とJava 9+の両方の実行時にjarファイルをロードする必要がありました(上記のコメントは、これらのバージョンの両方では機能しません)。これを行う方法は次のとおりです(関連する場合はSpring Boot 1.5.2を使用)。

public static synchronized void loadLibrary(java.io.File jar) {
    try {            
        java.net.URL url = jar.toURI().toURL();
        java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
        method.setAccessible(true); /*promote the method to public access*/
        method.invoke(Thread.currentThread().getContextClassLoader(), new Object[]{url});
    } catch (Exception ex) {
        throw new RuntimeException("Cannot load library from jar file '" + jar.getAbsolutePath() + "'. Reason: " + ex.getMessage());
    }
}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.