[英]How to load a class which implements a interface from a jar file at runtime?
[英]How to load unknown class from downloaded jar file during runtime?
我正在构建客户端服务器应用程序。 在运行时,客户端应用程序从服务器应用程序加载jar文件并将其存储。 我将客户端和服务器应用程序都作为jar文件运行。 我现在要加载此下载的jar文件中包含的类。
例如,我有一个接口A和一个实现A的类B。客户端应用程序不知道类B,其名称甚至其存在。 客户端应用程序启动后,客户端应用程序将下载一个jar文件,其中包含具有以下内容的jar文件:server / package / B.class其中server和package是文件夹。
现在,客户端应用程序应使用以下代码从下载的jar文件中加载此类B:
URL downloadURL = downloadFolder.toURI().toURL();
URL[] downloadURLs = new URL[] { ruleSetFolderURL };
URLClassLoader loader =
new URLClassLoader(downloadURLs);
Class tmp = loadClass(server.package.B);
但是然后我在最后一行得到ClassNotFoundException
。 我首先必须解压缩jar文件吗? jar文件中的文件夹结构类似于服务器应用程序的bin目录中的文件夹结构。
要从实现了特定接口的jar文件中动态加载类,但您事先不知道将是哪个类,并且jar文件本身未指定任何默认的“插件”类,则可以遍历下载的jar和获取罐子中包含的类的列表,如下所示:
/**
* Scans a JAR file for .class-files and returns a {@link List} containing
* the full name of found classes (in the following form:
* packageName.className)
*
* @param file
* JAR-file which should be searched for .class-files
* @return Returns all found class-files with their full-name as a List of
* Strings
* @throws IOException If during processing of the Jar-file an error occurred
* @throws IllegalArgumentException If either the provided file is null, does
* not exist or is no Jar file
*/
public List<String> scanJarFileForClasses(File file) throws IOException, IllegalArgumentException
{
if (file == null || !file.exists())
throw new IllegalArgumentException("Invalid jar-file to scan provided");
if (file.getName().endsWith(".jar"))
{
List<String> foundClasses = new ArrayList<String>();
try (JarFile jarFile = new JarFile(file))
{
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements())
{
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class"))
{
String name = entry.getName();
name = name.substring(0,name.lastIndexOf(".class"));
if (name.indexOf("/")!= -1)
name = name.replaceAll("/", ".");
if (name.indexOf("\\")!= -1)
name = name.replaceAll("\\", ".");
foundClasses.add(name);
}
}
}
return foundClasses;
}
throw new IllegalArgumentException("No jar-file provided");
}
一旦知道了jar文件中包含的类,就需要加载每个类并检查它们是否实现了所需的接口,如下所示:
/**
* <p>
* Looks inside a jar file and looks for implementing classes of the provided interface.
* </p>
*
* @param file
* The Jar-File containing the classes to scan for implementation of the given interface
* @param iface
* The interface classes have to implement
* @param loader
* The class loader the implementing classes got loaded with
* @return A {@link List} of implementing classes for the provided interface
* inside jar files of the <em>ClassFinder</em>s class path
*
* @throws Exception If during processing of the Jar-file an error occurred
*/
public List<Class<?>> findImplementingClassesInJarFile(File file, Class<?> iface, ClassLoader loader) throws Exception
{
List<Class<?>> implementingClasses = new ArrayList<Class<?>>();
// scan the jar file for all included classes
for (String classFile : scanJarFileForClasses(file))
{
Class<?> clazz;
try
{
// now try to load the class
if (loader == null)
clazz = Class.forName(classFile);
else
clazz = Class.forName(classFile, true, loader);
// and check if the class implements the provided interface
if (iface.isAssignableFrom(clazz) && !clazz.equals(iface))
implementingClasses.add(clazz);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
return implementingClasses;
}
由于您现在可以收集某个接口的所有实现,因此可以通过以下方法简单地初始化一个新实例
public void executeImplementationsOfAInJarFile(File downloadedJarFile)
{
If (downloadedJarFile == null || !downloadedJarFile.exists())
throw new IllegalArgumentException("Invalid jar file provided");
URL downloadURL = downloadedJarFile.toURI().toURL();
URL[] downloadURLs = new URL[] { downloadURL };
URLClassLoader loader = URLClassLoader.newInstance(downloadURLs, getClass().getClassLoader());
try
{
List<Class<?>> implementingClasses = findImplementingClassesInJarFile(downloadedJarFile, A.class, loader);
for (Class<?> clazz : implementingClasses)
{
// assume there is a public default constructor available
A instance = clazz.newInstance();
// ... do whatever you like here
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
请注意,此示例假定A是接口。 如果在Jar文件中找不到实现类,则jar文件将由类加载器加载,但不会实例化任何对象。
还要注意,提供父类加载器(尤其是使用URLClassLoader)始终是一个好习惯。 否则,可能会丢失Jar文件中未包含的某些类,因此尝试访问它们时会收到ClassNotFoundException
。 这是由于类加载器使用的委托机制引起的,该类加载器首先询问其父级是否知道所需类的类定义。 如果是这样,则该类将由父级加载;否则,父级将加载该类。 如果不是,则由创建的URLClassLoader加载该类。
请记住,可以使用不同的ClassLoader多次加载同一个类(对等类加载器)。 但是,尽管类的名称和字节可能相同,但是由于使用了不同的类加载器实例,因此这些类不兼容-因此尝试将由类加载器A加载的实例转换为由类加载器B加载的类型将失败。
@Edit:修改了代码以避免返回空值,而是抛出或多或少的适当异常。 @ Edit2:由于我无法接受代码审查建议,因此我将审查直接编辑到了帖子中
要加载jar,需要引用磁盘或Web上已下载文件的URL:
ClassLoader loader = URLClassLoader.newInstance(
new URL[]{new File("my.jar").toURI().toURL()},
getClass().getClassLoader()
);
Class<?> clazz = Class.forName("mypackage.MyClass", true, loader);
帖子中的更多内容: 如何在运行时加载jar文件 。
更新资料
如何使用Reflections扫描罐子: 如何使用Google Reflections库扫描在运行时加载的JAR?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.