主题确实说明了我的目标,但我对实施感到困惑。我有一个程序,它接受扩展我的Randomizer
类的不同对象。我想这样做,以便可以在类路径中放置一个JAR
文件,程序将在运行它时搜索它,并将其添加到主程序中。到目前为止,这是我尝试过的,但当我意识到java.util.jar.JarFile
无法给你Class
es或Method
时,我就停止了。
因为它依赖于它,我不妨提一下我的班级ArrayPP<T>
就像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描述的解决方案,如下所示。我正在使用名为JAR
的测试BHR2 - Ranger.jar
,其中包含一个在Randomizer
包中扩展Ranger
bhr2.plugins
的类。 JAR
在其META-INF\services
文件夹中包含一个名为bhr2.plugins.Ranger
的文件,其中一行显示为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;
}
但是当我运行它时,这是我在开始做其他事情之前得到的所有内容:
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
答案 0 :(得分:4)
此问题的典型解决方案是构建具有META-INF文件的jar文件,该文件告诉程序从该jar加载哪些类。例如,Spring custom namespace handlers和JDBC drivers以这种方式加载。
而不是弄清楚如何实际扫描jar中的所有类,没有人这样做,所以我期望不可行/不可行,让你的代码在类路径的每个jar中查找特定文件,列出Randomizer它包含的实现。例如,期望一个名为META-INF / randomizers.list的文件有一个类名列表,每行一个,这是实现Randomizer的jar中的类。阅读文件,对于每一行,使用Class.forName()按名称加载类,然后newInstance()实例化它。
编辑:从类路径中的任何位置加载“列表”文件:
Enumeration<URL> resources = getClassLoader().getResources(
"/META-INF/randomizers.list");
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
// Load the Randomizer(s) specified in this file
}
编辑:所以事实证明JDK公开了它用于此类事情的机制,我以前不知道。只需使用ServiceLoader即可。 The docs解释了如何使用它,我也编写了一个如何使用它的例子。您可以find the code on github或者只是自己克隆并运行它:
git clone git://github.com/zzantozz/testbed.git tmp
cd tmp
mvn install -pl serviceloader-example/service-usage -am
mvn -q exec:java -D exec.mainClass=rds.serviceloader.ServiceLoaderExample -pl serviceloader-example/service-usage
该示例包含五个模块:一个定义服务接口,三个定义单独的服务实现,另一个使用ServiceLoader加载实现。最后一个称为“服务使用”,并演示如何使用ServiceLoader类加载在实现第一个模块/ jar中定义的接口的其他三个模块/ jar中定义的三个服务。
修改:由于您似乎遇到了示例项目的问题,以下是基础知识。
有了这些文件,您需要做的就是获取所有Randomizer实例
ServiceLoader<Randomizer> loader = ServiceLoader.load(Randomizer.class);
for (Randomizer randomizer : loader) {
randomizer.doWhatever();
}
答案 1 :(得分:3)
从Manifest中获取类列表当然是更好的解决方案。但是如果你不希望罐子包含这些信息,你可以回到这个:
private static void scanClasses(File file) throws MalformedURLException, IOException {
ClassLoader classLoader = new URLClassLoader(new URL[]{ file.toURI().toURL() });
JarFile jar = new JarFile(file.getAbsoluteFile());
Enumeration<JarEntry> jarEntries = jar.entries();
while (jarEntries.hasMoreElements()) {
JarEntry je = jarEntries.nextElement();
if (je.getName().endsWith(".class")) {
String clazzName = je.getName();
clazzName = clazzName.substring(0, clazzName.length() - ".class".length()).replaceAll("/", ".");
clazzName = clazzName.replaceAll("/", ".");
try {
Class clazz = Class.forName(clazzName, false, classLoader);
if (Randomizer.class.isAssignableFrom(clazz)) {
System.out.println("Found Randomizer: " + clazz);
}
} catch (ClassNotFoundException ex) {
// this really should not happen,
// since we *know* the class exists
throw new AssertionError(ex.getMessage());
}
}
}
}
答案 2 :(得分:1)
这里是我用来检索属于包和任何子包的所有类的代码。您只需要使用反射搜索实现Randomizer的那些返回类的列表。
/**
* Scans all classes accessible from the context class loader which belong
* to the given package and subpackages.
* <p>
* Inspired from post on: {@code http://snippets.dzone.com/posts/show/4831}
*/
public static List<Class<?>> getClasses(String packageName)
throws ClassNotFoundException, IOException {
// Retrieving current class loader
final ClassLoader classLoader
= Thread.currentThread().getContextClassLoader();
// Computing path from the package name
final String path = packageName.replace('.', '/');
// Retrieving all accessible resources
final Enumeration<URL> resources = classLoader.getResources(path);
final List<File> dirs = new ArrayList<File>();
while (resources.hasMoreElements()) {
final URL resource = resources.nextElement();
final String fileName = resource.getFile();
final String fileNameDecoded = URLDecoder.decode(fileName, "UTF-8");
dirs.add(new File(fileNameDecoded));
}
// Preparing result
final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
// Processing each resource recursively
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
// Returning result
return classes;
}
/**
* Recursive method used to find all classes in a given directory and
* subdirs.
*/
public static List<Class<?>> findClasses(File directory, String packageName)
throws ClassNotFoundException {
// Preparing result
final List<Class<?>> classes = new ArrayList<Class<?>>();
if ( directory == null )
return classes;
if (!directory.exists())
return classes;
// Retrieving the files in the directory
final File[] files = directory.listFiles();
for (File file : files) {
final String fileName = file.getName();
// Do we need to go recursive?
if (file.isDirectory()) {
classes.addAll(findClasses(file, packageName + "." + fileName));
} else if (fileName.endsWith(".class") && !fileName.contains("$")) {
Class<?> _class;
_class = Class.forName(packageName + '.'
+ fileName.substring(0, fileName.length() - 6));
classes.add(_class);
}
}
return classes;
}
答案 3 :(得分:1)
我认为你应该坚持使用java ServiceLoader。那你就不需要自己摆弄罐子了。 这是一个很好的教程: http://java.sun.com/developer/technicalArticles/javase/extensible/index.html
如果您正在开发可扩展的桌面应用程序,netbeans平台可能是适合您的解决方案。它具有更高的学习曲线,但非常值得。 http://netbeans.org/features/platform/index.html