提取扩展“Randomizer”的类来自类路径中的所有 jarfile
这个主题确实说出了我的目标,但我对实施感到困惑。我有一个程序,它接受扩展我的 Randomizer
类的不同对象。我想这样做,以便人们可以在类路径中放置一个 JAR 文件,程序将在运行时搜索它,并将其添加到主程序中。到目前为止,这是我尝试过的,但当我意识到 java.util.jar.JarFile
无法为您提供 Class
es 或 Method 时,我停止了
。
因为它依赖于它,所以我不妨提到我的类 ArrayPP
类似于 ArrayList
,但具有更多方法。此处所示的 addAll
方法的功能类似于其 add
方法,但具有多个参数或泛型类型 T
的对象数组。
private static Randomizer[] loadExternalRandomizers() throws IOException
{
java.io.File classPath = new java.io.File(System.getProperty("user.dir"));
ArrayPP<Randomizer> r = new ArrayPP<>();
if (classPath.isDirectory())
{
r.addAll(getRandomizersIn(classPath));
}
return r.toArray();
}
private static Randomizer[] getRandomizersIn(File dir) throws IOException
{
ArrayPP<Randomizer> r = new ArrayPP<>();
java.io.File fs[] = dir.listFiles(new java.io.FileFilter() {
@Override
public boolean accept(File pathname)
{
return pathname.isDirectory() || pathname.toString().endsWith(".jar");
}
});
java.util.jar.JarFile jr;
java.util.Enumeration<java.util.jar.JarEntry> entries;
java.util.jar.JarEntry thisEntry;
for (java.io.File f : fs)
{
if (f.isDirectory())
{
r.addAll(getRandomizersIn(f));
continue;
}
jr = new java.util.jar.JarFile(f);
entries = jr.entries();
while (entries.hasMoreElements())
{
thisEntry = entries.nextElement();
//if (the jar file contains a class that extends Randomizer
// add that class to r
}
}
return r.toArray();
}
我正在 Java 7 上构建它,如果有帮助的话。我也希望在不使用任何库的情况下做到这一点。
实现解决方案
我尝试实现 Ryan Stewart 描述的解决方案,如下所示。我正在使用名为 BHR2 - Ranger.jar
的测试 JAR
,其中包含一个扩展 Randomizer
的类,称为 Ranger
,位于包bhr2.plugins
中。 JAR
在其 META-INF\services
文件夹中包含一个名为 bhr2.plugins.Ranger
的文件,其中一行内容为 < code>bhr2.Randomizer # 抽象随机化器。
private static ArrayPP<Randomizer> loadExternalRandomizers() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException
{
java.io.File classPath = new java.io.File(System.getProperty("user.dir"));
ArrayPP<Randomizer> r = new ArrayPP<>();
if (classPath.isDirectory())
{
r.addAll(getRandomizersIn(classPath));
}
return r;
}
private static int depth = 0;
private static ArrayPP<Randomizer> getRandomizersIn(File dir) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException
{
ArrayPP<Randomizer> r = new ArrayPP<>();
java.io.File fs[] = dir.listFiles(new java.io.FileFilter() {
@Override
public boolean accept(File pathname)
{
return pathname.isDirectory() || pathname.toString().endsWith(".jar");
}
});
for (java.io.File f : fs)
{
for (int i=0; i < depth; i++)
System.out.print(" ");
System.out.println(f);
if (f.isDirectory())
{
if (depth < 4)
{
depth++;
r.addAll(getRandomizersIn(f));
depth--;
}
else
System.out.println("Skipping directory due to depth");
continue;
}
java.util.ServiceLoader<Randomizer> sl = ServiceLoader.loadInstalled(Randomizer.class);
for (Randomizer rand : sl)
{
r.add(rand);
System.out.println("adding " + rand);
}
}
return r == null || r.isEmpty() ? new ArrayPP<Randomizer>() : r;
}
但是当我运行它时,这就是在它开始执行其他操作之前我得到的所有输出:
I:\Java\NetBeansProjects\BHRandomizer2\nbproject
I:\Java\NetBeansProjects\BHRandomizer2\nbproject\private
I:\Java\NetBeansProjects\BHRandomizer2\src
I:\Java\NetBeansProjects\BHRandomizer2\src\bhr2
I:\Java\NetBeansProjects\BHRandomizer2\src\bhr2\resources
I:\Java\NetBeansProjects\BHRandomizer2\src\bhr2\randomizers
I:\Java\NetBeansProjects\BHRandomizer2\src\bht
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\test
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\comps
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\comps\gameboard
Skipping directory due to depth
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\effects
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\misc
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\utilities
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\resources
I:\Java\NetBeansProjects\BHRandomizer2\lib
I:\Java\NetBeansProjects\BHRandomizer2\lib\CopyLibs
I:\Java\NetBeansProjects\BHRandomizer2\lib\CopyLibs\org-netbeans-modules-java-j2seproject-copylibstask.jar
I:\Java\NetBeansProjects\BHRandomizer2\lib\swing-layout
I:\Java\NetBeansProjects\BHRandomizer2\lib\swing-layout\swing-layout-1.0.4.jar
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger\META-INF
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger\bhr2
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger\bhr2\plugins
I:\Java\NetBeansProjects\BHRandomizer2\build
I:\Java\NetBeansProjects\BHRandomizer2\build\classes
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bhr2
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bhr2\randomizers
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bhr2\resources
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\comps
Skipping directory due to depth
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\utilities
Skipping directory due to depth
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\effects
Skipping directory due to depth
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\misc
Skipping directory due to depth
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\resources
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\test
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\META-INF
I:\Java\NetBeansProjects\BHRandomizer2\build\empty
I:\Java\NetBeansProjects\BHRandomizer2\build\generated-sources
I:\Java\NetBeansProjects\BHRandomizer2\build\generated-sources\ap-source-output
I:\Java\NetBeansProjects\BHRandomizer2\dist
I:\Java\NetBeansProjects\BHRandomizer2\dist\BHRandomizer2.jar
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger.jar
The subject really says my goal, but I'm stumped as to the implementation. I have a program that takes in different objects that extend my Randomizer
class. I want to make it so that one can place a JAR
file in the classpath and the program will search for it upon running it, and add it to the main program. This is what I tried, so far, but I stopped when I realized that a java.util.jar.JarFile
can't give you Class
es or Method
s.
since it relies on it, I might as well mention that my class ArrayPP<T>
is like ArrayList
, but with alot more methods. Its addAll
method, shown here, functions like its add
method, but with multiple arguments or an array of objects of generic type T
.
private static Randomizer[] loadExternalRandomizers() throws IOException
{
java.io.File classPath = new java.io.File(System.getProperty("user.dir"));
ArrayPP<Randomizer> r = new ArrayPP<>();
if (classPath.isDirectory())
{
r.addAll(getRandomizersIn(classPath));
}
return r.toArray();
}
private static Randomizer[] getRandomizersIn(File dir) throws IOException
{
ArrayPP<Randomizer> r = new ArrayPP<>();
java.io.File fs[] = dir.listFiles(new java.io.FileFilter() {
@Override
public boolean accept(File pathname)
{
return pathname.isDirectory() || pathname.toString().endsWith(".jar");
}
});
java.util.jar.JarFile jr;
java.util.Enumeration<java.util.jar.JarEntry> entries;
java.util.jar.JarEntry thisEntry;
for (java.io.File f : fs)
{
if (f.isDirectory())
{
r.addAll(getRandomizersIn(f));
continue;
}
jr = new java.util.jar.JarFile(f);
entries = jr.entries();
while (entries.hasMoreElements())
{
thisEntry = entries.nextElement();
//if (the jar file contains a class that extends Randomizer
// add that class to r
}
}
return r.toArray();
}
I'm building it on Java 7, if that helps. I'm also looking to do this without using any libraries.
Implementing a solution
I've tried implementing the solution described by Ryan Stewart, and it is shown below. I'm working it with a test JAR
called BHR2 - Ranger.jar
, which contains one class that extends Randomizer
, called Ranger
, in the package bhr2.plugins
. The JAR
contains, within its META-INF\services
folder, one file called bhr2.plugins.Ranger
with one line in it which reads bhr2.Randomizer # Abstract Randomizer
.
private static ArrayPP<Randomizer> loadExternalRandomizers() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException
{
java.io.File classPath = new java.io.File(System.getProperty("user.dir"));
ArrayPP<Randomizer> r = new ArrayPP<>();
if (classPath.isDirectory())
{
r.addAll(getRandomizersIn(classPath));
}
return r;
}
private static int depth = 0;
private static ArrayPP<Randomizer> getRandomizersIn(File dir) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException
{
ArrayPP<Randomizer> r = new ArrayPP<>();
java.io.File fs[] = dir.listFiles(new java.io.FileFilter() {
@Override
public boolean accept(File pathname)
{
return pathname.isDirectory() || pathname.toString().endsWith(".jar");
}
});
for (java.io.File f : fs)
{
for (int i=0; i < depth; i++)
System.out.print(" ");
System.out.println(f);
if (f.isDirectory())
{
if (depth < 4)
{
depth++;
r.addAll(getRandomizersIn(f));
depth--;
}
else
System.out.println("Skipping directory due to depth");
continue;
}
java.util.ServiceLoader<Randomizer> sl = ServiceLoader.loadInstalled(Randomizer.class);
for (Randomizer rand : sl)
{
r.add(rand);
System.out.println("adding " + rand);
}
}
return r == null || r.isEmpty() ? new ArrayPP<Randomizer>() : r;
}
But when I run it, this is all I get as an output before it starts doing other things:
I:\Java\NetBeansProjects\BHRandomizer2\nbproject
I:\Java\NetBeansProjects\BHRandomizer2\nbproject\private
I:\Java\NetBeansProjects\BHRandomizer2\src
I:\Java\NetBeansProjects\BHRandomizer2\src\bhr2
I:\Java\NetBeansProjects\BHRandomizer2\src\bhr2\resources
I:\Java\NetBeansProjects\BHRandomizer2\src\bhr2\randomizers
I:\Java\NetBeansProjects\BHRandomizer2\src\bht
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\test
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\comps
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\comps\gameboard
Skipping directory due to depth
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\effects
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\misc
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\tools\utilities
I:\Java\NetBeansProjects\BHRandomizer2\src\bht\resources
I:\Java\NetBeansProjects\BHRandomizer2\lib
I:\Java\NetBeansProjects\BHRandomizer2\lib\CopyLibs
I:\Java\NetBeansProjects\BHRandomizer2\lib\CopyLibs\org-netbeans-modules-java-j2seproject-copylibstask.jar
I:\Java\NetBeansProjects\BHRandomizer2\lib\swing-layout
I:\Java\NetBeansProjects\BHRandomizer2\lib\swing-layout\swing-layout-1.0.4.jar
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger\META-INF
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger\bhr2
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger\bhr2\plugins
I:\Java\NetBeansProjects\BHRandomizer2\build
I:\Java\NetBeansProjects\BHRandomizer2\build\classes
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bhr2
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bhr2\randomizers
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bhr2\resources
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\comps
Skipping directory due to depth
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\utilities
Skipping directory due to depth
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\effects
Skipping directory due to depth
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\tools\misc
Skipping directory due to depth
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\resources
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\bht\test
I:\Java\NetBeansProjects\BHRandomizer2\build\classes\META-INF
I:\Java\NetBeansProjects\BHRandomizer2\build\empty
I:\Java\NetBeansProjects\BHRandomizer2\build\generated-sources
I:\Java\NetBeansProjects\BHRandomizer2\build\generated-sources\ap-source-output
I:\Java\NetBeansProjects\BHRandomizer2\dist
I:\Java\NetBeansProjects\BHRandomizer2\dist\BHRandomizer2.jar
I:\Java\NetBeansProjects\BHRandomizer2\BHR2 - Ranger.jar
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
此问题的典型解决方案是构建在 META-INF 中包含一个文件的 jar 文件,该文件告诉您的程序要从该 jar 加载哪些类。 Spring 自定义命名空间处理程序和JDBC驱动程序,例如,已加载 这样。
不要弄清楚如何实际扫描 jar 中的所有类(没有人这样做,所以我预计这是不可能/不可行的),而是让您的代码在类路径上的每个 jar 中查找特定文件,其中列出了它包含的 Randomizer 实现。例如,期望名为 META-INF/randomizers.list 的文件具有一个类名列表,每行一个,它们是该 jar 中实现随机化器的类。读取文件,对于每一行,使用 Class.forName() 按名称加载类,然后newInstance() 实例化它。
编辑:用于从类路径中的任何位置加载“列表”文件:
编辑:所以事实证明 JDK 公开了它用于此类事情的机制,我对此以前不知道。只需使用 ServiceLoader 即可。 文档解释了如何使用它,以及我也编写了一个如何使用它的示例。您可以在 github 上找到代码或者直接克隆并自己运行它:
该示例由五个模块组成:一个定义服务接口,三个定义单独的服务实现,一个使用 ServiceLoader 加载实现。最后一个称为“服务使用”,演示如何使用 ServiceLoader 类加载其他三个模块/jar 中定义的三个服务,这些模块/jar 实现第一个模块/jar 中定义的接口。
编辑:由于您似乎在使用示例项目时遇到了问题,因此以下是基础知识。
这些文件就位后,您所需要做的就是获取所有 Randomizer 实例
A typical solution to this problem is to build jar files that have a file in META-INF that tells your program what class(es) to load from that jar. Spring custom namespace handlers and JDBC drivers, for example, are loaded in this way.
Instead of figuring out how to actually scan all the classes in a jar, which nobody does, so I expect isn't possible/feasible, make your code look for a specific file in each jar on the classpath which lists the Randomizer implementations it contains. For instance, expect a file named META-INF/randomizers.list to have a list of class names, one per line, which are classes in that jar that implement your Randomizer. Read the file, and for each line, use Class.forName() to load the class by name, then newInstance() to instantiate it.
Edit: For loading your "list" file from everywhere in the classpath:
Edit: So it turns out the JDK exposes the mechanism it uses for this kind of thing, which I didn't know about before. Just use a ServiceLoader. The docs explain how to use it, and I've coded up an example of how to use it, too. You can find the code on github or simply clone and run it yourself:
The example consists of five modules: one that defines a service interface, three that define separate service implementations, and one that loads the implementations using ServiceLoader. That last is called "service-usage" and demonstrates how to use the ServiceLoader class to load the three services defined in the other three modules/jars that implement the interface defined in the first module/jar.
Edit: Since you seem to be having trouble with the sample project, here are the basics.
With those files in place, all you have to do to get all the Randomizer instances is
从清单中获取类列表当然是更好的解决方案。但如果您不希望罐子包含此类信息,您可以使用以下方法:
Fetching the class list from the Manifest is of course the preferable solution. But if you don't expect the jars to contain such information, you can fall back to this:
这是我用来检索属于包和任何子包的所有类的代码。您只需在返回的类列表中搜索那些使用反射实现 Randomizer 的类即可。
Here here is the code I am using to retrieve all the classes belonging to a package and any sub-packages. You just need to search the list of returned classes for those implementing Randomizer using reflection.
我认为你应该坚持使用 java ServiceLoader。那么你就不需要自己摆弄罐子了。
这是一个很好的教程:
http://java.sun.com/developer/technicalArticles/javase/extensible /index.html
如果您开发可扩展的桌面应用程序,netbeans 平台可能是适合您的解决方案。它的学习曲线较高,但非常值得。
http://netbeans.org/features/platform/index.html
I think you should stick with the java ServiceLoader. Then you don't need to fiddle with jars yourself.
Here's a good tutorial:
http://java.sun.com/developer/technicalArticles/javase/extensible/index.html
If your developing an extensible desktop application the netbeans platform might be the right solution for you. It has a higher learning curve but it is quite worth it.
http://netbeans.org/features/platform/index.html