为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。
对实现这一功能的简单代码有什么建议吗?
为什么在Java中这么难做到这一点?如果您希望拥有任何类型的模块系统,您需要能够动态加载JAR文件。有人告诉我,有一种方法可以通过编写自己的ClassLoader来实现,但这需要做很多工作,因为(至少在我看来)应该像调用以JAR文件作为参数的方法一样简单。
对实现这一功能的简单代码有什么建议吗?
当前回答
在Java 9中,URLClassLoader的答案现在会给出如下错误:
java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader
这是因为所使用的类装入器发生了变化。相反,要添加到系统类装入器,您可以通过代理使用Instrumentation 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
运行代理:
使用bytes -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());
}
}
其他回答
你应该看一看OSGi,比如在Eclipse平台中实现的。它就是这么做的。您可以安装、卸载、启动和停止所谓的包,这些包实际上是JAR文件。但是它做的更多,例如,它提供了可以在运行时在JAR文件中动态发现的服务。
或参阅Java模块系统的规范。
我需要在运行时为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());
}
}
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");
虽然这里列出的大多数解决方案要么是hack(在JDK 9之前)难以配置(代理),要么就是不再工作(在JDK 9之后),但我发现没有人提到一个清晰的文档化方法,这真的很令人惊讶。
您可以创建一个自定义系统类装入器,然后您可以自由地做任何您想做的事情。不需要反射,所有类共享相同的类加载器。
当启动JVM时添加这个标志:
java -Djava.system.class.loader=com.example.MyCustomClassLoader
类加载器必须有一个接受类加载器的构造函数,必须将类加载器设置为父类加载器。构造函数将在JVM启动时调用,真正的系统类加载器将被传递,主类将由自定义加载器加载。
要添加jar,只需调用ClassLoader.getSystemClassLoader()并将其转换为您的类。
请查看此实现,以获得精心制作的类装入器。请注意,您可以将add()方法更改为public。
我找到的最好的是org.apache.xbean.classloader.JarFileClassLoader,它是XBean项目的一部分。
下面是我过去使用的一个简短方法,用于从特定目录中的所有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);