我应该如何在运行时dynamic加载Jars?
为什么在Java中这么做很难? 如果你想有任何一种模块系统,你需要能够dynamic加载jar子。 我被告知有一种方法可以通过编写自己的ClassLoader
,但是对于那些应该(至less在我的脑海里)像使用jar文件作为参数来调用方法一样简单的工作来说,这是一个很大的工作。
任何build议简单的代码,这样做?
很难的原因是安全。 类加载器是不可改变的。 你不应该能够在运行时为它添加类。 我真的很惊讶,与系统类加载器一起工作。 下面是你如何做你自己的孩子类加载器:
URLClassLoader child = new URLClassLoader (myJar.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);
痛苦,但它是。
下面的解决scheme是hackish,因为它使用reflection绕过封装,但它完美地工作:
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);
你应该看看OSGi ,例如在Eclipse平台上实现 。 它确实如此。 您可以安装,卸载,启动和停止所谓的捆绑包,这是有效的JAR文件。 但是它做的更多,因为它提供了例如可以在运行时在JAR文件中dynamic发现的服务。
或者参阅Java模块系统的规范。
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");
这是一个不被弃用的版本。 我修改了原来的删除不推荐使用的function。
/************************************************************************************************** * 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 sa 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(); } }
我发现最好的是org.apache.xbean.classloader.JarFileClassLoader ,它是XBean项目的一部分。
以下是我过去使用的一个简短方法,用于从特定目录中的所有lib文件创build一个类加载器
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);
如果您正在使用Android,则以下代码工作:
String jarFile = "path/to/jarfile.jar"; DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader()); Class<?> myClass = classLoader.loadClass("MyClass");
由jodonnell提出的解决scheme是好的,但应该有点增强。 我用这个post来成功开发我的应用程序。
分配当前线程
首先,我们必须补充
Thread.currentThread().setContextClassLoader(classLoader);
或者你将无法加载存储在jar中的资源(比如spring / context.xml)。
不包括
你的jar放到父类加载器中,否则你将无法理解谁在加载什么。
另请参阅使用URLClassLoader重新加载jar的问题
但是,OSGi框架仍然是最好的方法。
使用URLClassLoader
的答案在Java 9中不再适用,因为所使用的类加载器已经更改。 要添加到系统类加载器,您可以通过代理使用Instrumentation api。
创build一个代理类:
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()); } }
我个人发现java.util.ServiceLoader的工作非常好。 你可以在这里得到一个例子。