主题确实说明了我的目标,但我对实现感到困惑。我有一个程序,它接受不同的对象来扩展我的Randomizer
类。我想这样做,这样就可以在类路径中放置一个JAR
文件,程序在运行它时会搜索它,并将它添加到主程序中。到目前为止,这是我尝试过的,但当我意识到java.util.jar.JarFile
不能给你Class
es或Method
s时,我停止了。
由于它依赖于它,我不妨提到我的类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();
}
如果有帮助的话,我会在Java7上构建它。我也希望在不使用任何库的情况下做到这一点。
实施解决方案
我尝试过实现Ryan Stewart描述的解决方案,如下所示。我正在使用一个名为BHR2 - Ranger.jar
的测试JAR
来处理它,它在包bhr2.plugins
中包含一个扩展Randomizer
的类,称为Ranger
。JAR
在其META-INFservices
文件夹中包含一个名为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:JavaNetBeansProjectsBHRandomizer2nbproject
I:JavaNetBeansProjectsBHRandomizer2nbprojectprivate
I:JavaNetBeansProjectsBHRandomizer2src
I:JavaNetBeansProjectsBHRandomizer2srcbhr2
I:JavaNetBeansProjectsBHRandomizer2srcbhr2resources
I:JavaNetBeansProjectsBHRandomizer2srcbhr2randomizers
I:JavaNetBeansProjectsBHRandomizer2srcbht
I:JavaNetBeansProjectsBHRandomizer2srcbhttest
I:JavaNetBeansProjectsBHRandomizer2srcbhttools
I:JavaNetBeansProjectsBHRandomizer2srcbhttoolscomps
I:JavaNetBeansProjectsBHRandomizer2srcbhttoolscompsgameboard
Skipping directory due to depth
I:JavaNetBeansProjectsBHRandomizer2srcbhttoolseffects
I:JavaNetBeansProjectsBHRandomizer2srcbhttoolsmisc
I:JavaNetBeansProjectsBHRandomizer2srcbhttoolsutilities
I:JavaNetBeansProjectsBHRandomizer2srcbhtresources
I:JavaNetBeansProjectsBHRandomizer2lib
I:JavaNetBeansProjectsBHRandomizer2libCopyLibs
I:JavaNetBeansProjectsBHRandomizer2libCopyLibsorg-netbeans-modules-java-j2seproject-copylibstask.jar
I:JavaNetBeansProjectsBHRandomizer2libswing-layout
I:JavaNetBeansProjectsBHRandomizer2libswing-layoutswing-layout-1.0.4.jar
I:JavaNetBeansProjectsBHRandomizer2BHR2 - Ranger
I:JavaNetBeansProjectsBHRandomizer2BHR2 - RangerMETA-INF
I:JavaNetBeansProjectsBHRandomizer2BHR2 - Rangerbhr2
I:JavaNetBeansProjectsBHRandomizer2BHR2 - Rangerbhr2plugins
I:JavaNetBeansProjectsBHRandomizer2build
I:JavaNetBeansProjectsBHRandomizer2buildclasses
I:JavaNetBeansProjectsBHRandomizer2buildclassesbhr2
I:JavaNetBeansProjectsBHRandomizer2buildclassesbhr2randomizers
I:JavaNetBeansProjectsBHRandomizer2buildclassesbhr2resources
I:JavaNetBeansProjectsBHRandomizer2buildclassesbht
I:JavaNetBeansProjectsBHRandomizer2buildclassesbhttools
I:JavaNetBeansProjectsBHRandomizer2buildclassesbhttoolscomps
Skipping directory due to depth
I:JavaNetBeansProjectsBHRandomizer2buildclassesbhttoolsutilities
Skipping directory due to depth
I:JavaNetBeansProjectsBHRandomizer2buildclassesbhttoolseffects
Skipping directory due to depth
I:JavaNetBeansProjectsBHRandomizer2buildclassesbhttoolsmisc
Skipping directory due to depth
I:JavaNetBeansProjectsBHRandomizer2buildclassesbhtresources
I:JavaNetBeansProjectsBHRandomizer2buildclassesbhttest
I:JavaNetBeansProjectsBHRandomizer2buildclassesMETA-INF
I:JavaNetBeansProjectsBHRandomizer2buildempty
I:JavaNetBeansProjectsBHRandomizer2buildgenerated-sources
I:JavaNetBeansProjectsBHRandomizer2buildgenerated-sourcesap-source-output
I:JavaNetBeansProjectsBHRandomizer2dist
I:JavaNetBeansProjectsBHRandomizer2distBHRandomizer2.jar
I:JavaNetBeansProjectsBHRandomizer2BHR2 - Ranger.jar
这个问题的一个典型解决方案是构建jar文件,该文件在META-INF中有一个文件,"告诉"您的程序从该jar加载什么类。例如,Spring自定义名称空间处理程序和JDBC驱动程序就是以这种方式加载的。
与其弄清楚如何真正扫描一个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。文档解释了如何使用它,我也编写了一个如何使用它的示例。你可以在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实现了第一个模块/jar中定义的接口。
编辑:由于您在示例项目中似乎遇到了问题,以下是基本信息。
- 每个包含一个或多个Randomizer实现的jar文件都应该包含一个名为META-INF/services/com.foo.Randomizer的文件(其中com.foo是您的包名(
- 这个文件应该包含一个类名列表,每行一个,每个类名都是Randomizer的实现
有了这些文件,你所要做的就是获得所有Randomizer实例
ServiceLoader<Randomizer> loader = ServiceLoader.load(Randomizer.class); for (Randomizer randomizer : loader) { randomizer.doWhatever(); }
从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());
}
}
}
}
这里是我用来检索属于一个包和任何子包的所有类的代码。您只需要在返回类的列表中搜索那些使用反射实现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;
}
我认为您应该坚持使用java ServiceLoader。那你就不需要自己摆弄罐子了。这里有一个很好的教程:http://java.sun.com/developer/technicalArticles/javase/extensible/index.html
如果您正在开发一个可扩展的桌面应用程序,那么netbeans平台可能是适合您的解决方案。它有一个更高的学习曲线,但它是非常值得的。http://netbeans.org/features/platform/index.html