9

我有一个由执行文件处理的类实现的接口,比如搜索或其他。

public interface FileProcessorInterface {

    public void processFile(String fileName);

}

然后我对每种文件类型都有不同的实现:

public class TxtProcessor implements FileProcessorInterface {

    @Override public void processFile(String fileName) { //do the work }

}

因此,我有处理器的 Utilizer,它有一个允许注册每个类的方法,如下所示:

class Utilizer {

Map <String, Class> registered = new HashMap<>();

public void registerClass(String fileExt, Class clazz) {

    registered.put(fileExt, clazz);

}

public void processFile(String fileName) {

    //1) get the registered class from registered map (omitted because easy and not relevant)

    //2) create an instance of the class using reflection (omitted because easy and not relevant)
    FileProcessorInterface p = ....

    p.processFile(fileName);             

}

到目前为止一切正常。

现在,我提供了我的接口的许多实现。

而且我很想为每个实现类提供一个静态初始化程序,该初始化程序在 Utilizer 中注册自身,在我之前的 TxtProcessor 的情况下,它将是:

class TxtProcessor implements FileProcessorInterface {

    //previous code

    static {
        Utilizer.registerClass("txt", TxtProcessor.class);
    }

}

问题是这个静态方法永远不会被调用,因为在应用程序的“静态可达”代码中没有对我的 TxtProcessor 类的引用,因为它是通过反射实例化的。所以jvm不调用静态初始化器。

假设我有两个部分:作为 Utilizer 的“通用代码”,另一方面是实现;它必须被认为是动态提供的东西,因此 Utilizer 部分不知道它。
事实上,这个想法正是每个类都会注册自己,而不会触及 Utilizer。
我很难想出一个不将某种形式的实现“知识”放在 Utilizer 端(并且保持简单)的解决方案,只是因为未调用静态初始化程序的问题。如何克服这一点?

4

6 回答 6

5

您可以查看Reflections库。它允许您找到所有实现接口、具有注释或扩展类的类。

于 2013-08-06T21:09:48.730 回答
5

在这里使用反射似乎是最合适的。这就像准备这样做。

你所需要的只是一个小的静态Utilizer

static {

    Reflections reflections = new Reflections(
                new ConfigurationBuilder()
               .setUrls(ClasspathHelper.forPackage("path.to.all.processors.pkg"))
               .setScanners(new SubTypesScanner())
           );

    reflections.getSubTypesOf(path.to.all.processors.pkg.FileProcessor.class);
}

如果您不想要第三方依赖项,只需将FileProcessors.properties文件添加到您的类路径

txt=path.to.all.processors.pkg.TxtProcessor
doc=path.to.all.processors.pkg.DocProcessor
pdf=path.to.all.processors.pkg.PdfProcessor

然后将所有列出的类注册Utilizer

static {
    Properties processors = new Properties();
    try {
        processors.load(Utilizer.class
                  .getResourceAsStream("FileProcessors.properties"));
    } catch (IOException e) {
        e.printStackTrace();
    }
    for (String ext : processors.stringPropertyNames()) {
        Utilizer.registerClass(ext, Class.forName(processors.getProperty(ext));
    }
}

现在不再需要静态FileProcessor

于 2013-08-06T22:39:24.723 回答
3

你可以...

使用与 JDBC 相同的概念来加载它的驱动程序。这将要求您在Class#forName第一次加载程序时使用来初始化类。虽然这确实意味着从您的实用程序类的角度来看,实现仍然是动态的,但它是由您的应用程序在运行时指定的......

这使您可以控制可能要使用的实现

你可以...

java.awt.Toolkit使用与初始化实例时类似的概念相同的概念。

它基本上是查找资源(在本例中为System属性),然后使用Class.

就个人而言,我通常会寻找一个命名资源(通常是一个属性文件)并从中加载一个键。

类似的东西getClass().getResource("/some/gloabl/configFile");,每个实现都需要提供。

然后,如果可用,请阅读属性文件并找到我想要的密钥。

但是,如果链接了多个实现,则无法保证将加载哪个实现。

于 2013-08-06T21:15:55.237 回答
1

快速而肮脏:您可以Utilizer使用正确的关联静态初始化 in main() 。
更好的解决方案:在资源文件关联中外部化,例如

txt=path.to.package.TxProcessor

加载它Utilizer并加载FileProcessorInterface实现者Class.forName()

于 2013-08-06T21:19:17.473 回答
1

您可以通过 Class.forName(fqn, true, classLoader) 或简短形式 Class.forName(fqn) 强制静态初始化

于 2013-08-06T21:20:10.613 回答
1

您可能有一个注册表文件(例如,一些 XML 文件),其中包含您支持的所有类的列表:

<item type="txt">somepackage.TxtProcessor</item>
<item type="gif">somepackage.GIFProcessor</item>
...

Utilizer会将此文件加载到其注册表中。

于 2013-08-06T21:22:08.600 回答