Java 如何在运行时动态加载 JAR 文件?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/60764/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to load JAR files dynamically at Runtime?
提问by Allain Lalonde
Why is it so hard to do this in Java? If you want to have any kind of module system you need to be able to load JAR files dynamically. I'm told there's a way of doing it by writing your own ClassLoader
, but that's a lot of work for something that should (in my mind at least) be as easy as calling a method with a JAR file as its argument.
为什么在 Java 中很难做到这一点?如果您想拥有任何类型的模块系统,您需要能够动态加载 JAR 文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader
.
Any suggestions for simple code that does this?
对执行此操作的简单代码有什么建议吗?
采纳答案by jodonnell
The reason it's hard is security. Classloaders are meant to be immutable; you shouldn't be able to willy-nilly add classes to it at runtime. I'm actually very surprised that works with the system classloader. Here's how you do it making your own child classloader:
之所以难,是因为安全。类加载器是不可变的;你不应该在运行时随意地向它添加类。我真的很惊讶与系统类加载器一起工作。以下是如何制作自己的子类加载器:
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);
Painful, but there it is.
痛苦,但它就在那里。
回答by Allain Lalonde
The following solution is hackish, as it uses reflection to bypass encapsulation, but it works flawlessly:
以下解决方案是hackish,因为它使用反射来绕过封装,但它可以完美地工作:
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);
回答by Martin Klinke
You should take a look at OSGi, e.g. implemented in the Eclipse Platform. It does exactly that. You can install, uninstall, start and stop so called bundles, which are effectively JAR files. But it does a little more, as it offers e.g. services that can be dynamically discovered in JAR files at runtime.
您应该看看OSGi,例如在Eclipse 平台中实现。它正是这样做的。您可以安装、卸载、启动和停止所谓的包,它们实际上是 JAR 文件。但它做得更多,因为它提供了例如可以在运行时在 JAR 文件中动态发现的服务。
Or see the specification for the Java Module System.
或者查看Java Module System的规范。
回答by jodonnell
The best I've found is org.apache.xbean.classloader.JarFileClassLoaderwhich is part of the XBeanproject.
我发现的最好的是org.apache.xbean.classloader.JarFileClassLoader,它是XBean项目的一部分。
Here's a short method I've used in the past, to create a class loader from all the lib files in a specific directory
这是我过去使用的一个简短方法,用于从特定目录中的所有 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());
}
Then to use the classloader, just do:
然后要使用类加载器,只需执行以下操作:
classLoader.loadClass(name);
回答by Chris
How about the JCL class loader framework? I have to admit, I haven't used it, but it looks promising.
如何在JCL类加载器的框架?我不得不承认,我没有使用过它,但它看起来很有希望。
Usage example:
用法示例:
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");
回答by Jonathan Nadeau
Here is a version that is not deprecated. I modified the original to remove the deprecated functionality.
这是一个未被弃用的版本。我修改了原始版本以删除不推荐使用的功能。
/**************************************************************************************************
* 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();
}
}
回答by tanyehzheng
I personally find that java.util.ServiceLoaderdoes the job pretty well. You can get an example here.
我个人认为java.util.ServiceLoader可以很好地完成这项工作。你可以在这里得到一个例子。
回答by Caner
If you are working on Android, the following code works:
如果您使用的是 Android,则以下代码有效:
String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");
回答by venergiac
The solution proposed by jodonnell is good but should be a little bit enhanced. I used this post to develop my application with success.
jodonnell 提出的解决方案很好,但应该有所增强。我使用这篇文章成功开发了我的应用程序。
Assign the current thread
分配当前线程
Firstly we have to add
首先我们必须添加
Thread.currentThread().setContextClassLoader(classLoader);
or you will not able to load resource (such as spring/context.xml) stored into the jar.
否则您将无法加载存储在 jar 中的资源(例如 spring/context.xml)。
Do not include
不包括
your jars into the parent class loader or you will not able to understand who is loading what.
将您的 jars 放入父类加载器,否则您将无法理解谁在加载什么。
see also Problem reloading a jar using URLClassLoader
另请参阅使用 URLClassLoader 重新加载 jar 的问题
However, OSGi framework remain the best way.
然而,OSGi 框架仍然是最好的方式。
回答by fgb
With Java 9, the answers with URLClassLoader
now give an error like:
使用Java 9,URLClassLoader
现在的答案会给出如下错误:
java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader
This is because the class loaders used have changed. Instead, to add to the system class loader, you can use the InstrumentationAPI through an agent.
这是因为使用的类加载器发生了变化。相反,要添加到系统类加载器,您可以通过代理使用InstrumentationAPI。
Create an agent class:
创建代理类:
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));
}
}
Add META-INF/MANIFEST.MF and put it in a JAR file with the agent class:
添加 META-INF/MANIFEST.MF 并将其放入带有代理类的 JAR 文件中:
Manifest-Version: 1.0
Agent-Class: ClassPathAgent.ClassPathAgent
Run the agent:
运行代理:
This uses the byte-buddy-agentlibrary to add the agent to the running JVM:
这使用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());
}
}