なぜJavaでこれを行うのは難しいのですか? 何らかの種類のモジュール システムが必要な場合は、JAR ファイルを動的にロードできる必要があります。独自の を書くことでそれを行う方法があると言われていますClassLoader
が、それは、JAR ファイルを引数としてメソッドを呼び出すのと同じくらい簡単なはずの (少なくとも私の考えでは) ための多くの作業です。
これを行う簡単なコードの提案はありますか?
なぜJavaでこれを行うのは難しいのですか? 何らかの種類のモジュール システムが必要な場合は、JAR ファイルを動的にロードできる必要があります。独自の を書くことでそれを行う方法があると言われていますClassLoader
が、それは、JAR ファイルを引数としてメソッドを呼び出すのと同じくらい簡単なはずの (少なくとも私の考えでは) ための多くの作業です。
これを行う簡単なコードの提案はありますか?
難しい理由はセキュリティです。クラスローダーは不変であることを意図しています。実行時にクラスを勝手に追加することはできません。システムクラスローダーで動作することに、私は実際に非常に驚いています。独自の子クラスローダーを作成する方法は次のとおりです。
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);
痛いですが、そこにあります。
次の解決策は、リフレクションを使用してカプセル化をバイパスするため、ハックですが、問題なく機能します。
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);
たとえば、 Eclipse Platformに実装されているOSGiを調べてください。それはまさにそれを行います。実質的に JAR ファイルである、いわゆるバンドルをインストール、アンインストール、開始、および停止できます。ただし、実行時に JAR ファイルで動的に検出できるサービスなどを提供するなど、もう少し多くのことを行います。
またはJava Module Systemの仕様を参照してください。
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");
これは非推奨ではないバージョンです。非推奨の機能を削除するためにオリジナルを変更しました。
/**************************************************************************************************
* 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();
}
}
私が見つけた最高のものは、 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);
私のために働くインストルメンテーションを使用した別の実用的なソリューション。これには、依存クラスのクラスの可視性に関する問題を回避して、クラス ローダーの検索を変更できるという利点があります。
エージェント クラスの作成
この例では、コマンドラインによって呼び出されたのと同じ 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>));
ドキュメントでこれを使用しても問題は見つかりませんでした。
jodonnell によって提案されたソリューションは優れていますが、少し強化する必要があります。この投稿を使用して、アプリケーションを成功裏に開発しました。
まず、追加する必要があります
Thread.currentThread().setContextClassLoader(classLoader);
そうしないと、jar に保存されているリソース (spring/context.xml など) をロードできなくなります。
jar を親クラス ローダーに入れないと、誰が何をロードしているのか理解できなくなります。
URLClassLoader を使用して jar をリロードする際の問題も参照してください。
ただし、OSGi フレームワークが最善の方法であることに変わりはありません。
個人的には、java.util.ServiceLoaderが非常にうまく機能することがわかりました。ここで例を得ることができます。