我需要一些关于遵循范例的建议。之前,我有这样的东西
package/Instantiator.class
package/instances/GenericInstance.class (abstract)
package/instances/AInstance.class (extends Generic)
package/instances/BInstance.class (extends Generic)
Instantiator
所做的是在package/instances
文件夹中搜索所有的类文件,并使用反射实例化每个类文件,并在所有实例上调用抽象方法并收集输出并保存到DB。
但是,我现在必须将我的代码打包到Jar中,并且Java似乎不允许在Jar中搜索包中的所有类文件(因为它似乎与路径混淆)。
我可以在GenericInstance或其他东西的List中添加所有实例,然后Instantiator可以获得类的列表。
但是我希望其他人能够在那个包中添加一个类,仅此而已。
我应该遵循什么模式呢?有什么代码帮助吗?谢谢!
JDK中有一个内置解决方案,即ServiceLoader
(Java 6+)。但是,它要求用户在META-INF/services
中创建一个包含实现列表的文件。
如果你的基本接口是package.instances.GenericInstance
,那么你的文件将被称为META-INF/services/package.instances.GenericInstance
,它的内容将是:
path.to.implementation1
path.to.implementation2
等。根据您使用的构建系统,这种文件可能会自动生成(maven有一个插件,见这里)。
Java似乎不允许在jar中搜索包中的所有类文件
是的,它可以(但要做到这一点的代码是相当复杂的-你必须创建一个URLClassLoader
等)。
一个解决方案,如果所有的jar在运行时都在你的类路径中,是使用类似反射的东西(奖励:依赖于Guava,所以你得到了它的所有细节),它对类等有有效的过滤器,可以"反射"类加载器而不是"系统"类加载器;
我刚刚发现的方法:
try {
String jarName = new File(Insantiator.class.getProtectionDomain()
.getCodeSource()
.getLocation()
.getPath())
.getName();
JarFile jar = new JarFile(jarName);
Enumeration<JarEntry> entries = jar.entries();
while(entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if(name.contains("package/instances") && !name.contains("GenericInstance.class") && name.contains(".class")) {
name = name.replace("/", ".").replace(".class", "");
try {
Class theClass = Class.forName(name);
GenericInstance instance = (GenericInstance ) theClass.newInstance();
instances.add(instance);
} catch(InstantiationException | IllegalAccessException | ClassNotFoundException e) {
Utilities.writeLog("---- tried: " + name);
Utilities.writeLogException(null, e);
}
}
}
} catch (IOException e) {
Utilities.writeLogException(null, e);
}