Guice:从 XML 文件设置绑定

Guice : Set bindings from an XML file

我正在尝试使用 Guice 并在 XML 文件的帮助下进行所有绑定。在我的模块中(比方说 "CustomModule"),我想加载一个 XML 文件并解析它以设置所有绑定。

我能够加载 XML 文件并检索所有需要的值(下面是我的 XML 文件的示例),但我无法将这些值用于 bind(interfaceValue).to(implementationValue);.

到目前为止我尝试过的:

  1. 加载 XML 文件,检索所有值并将它们用作: bind(Class.fromName(Ivalue)).to(Class.fromName(Value)); 其中 IvalueInterfaceFooValueFoo.
  2. 加载 XML 文件作为属性文件并使用 Names.bindProperties(binder(), properties);.
  3. 手动绑定,这不是我想要的

结果:

  1. 不起作用,因为 Guice 无法验证实现是否是接口的实现。
  2. 报错No implementation for interface was bound
  3. 有效,但不需要它,因为我必须编辑我的 CustomModule 来更改绑定(如果我希望 Bar 成为 InterfaceFoo 的实现) .

I've looked at this,但没有那么成功,因为没有太多的文档。我也在SO上寻找解决方案here,但大多数时候问题是关于属性或注释的使用。

有没有一种简单的方法可以在文件中指定接口/实现并将其作为 "configuration" 提供给 Guice?

我的 XML 文件:

<bindings>
  <binding>
    <interface>interfaces.IReaderService</interface>
    <implementation>implementation.MemsReaderService</implementation>
  </binding>
  <binding>
    <interface>interfaces.IReportService </interface>
    <implementation>implementation.PdfReportService</implementation>
  </binding>
  <binding>
    <interface>interfaces.ISerializerService </interface>
    <implementation>implementation.JsonSerializerService</implementation>
  </binding>
  <binding>
    <interface>interfaces.ILoggerService </interface>
    <implementation>implementation.LoggerService</implementation>
  </binding>
</bindings>

CustomModule.java:

public class GuiceModule extends AbstractModule{

    private HashMap<String, String> classNames = new HashMap<String, String>();

    public GuiceModule(){
    }

    @Override
    protected void configure() {
        /* === Test 1 [NOK : Module doesn't know if A implements B] */
        for(Entry<String, String> entry : classNames.entrySet()){
            try {
                Class<?> itf = Class.forName(entry.getKey());
                Class<?> concrete = Class.forName(entry.getValue());
                bind(itf).to(concrete);
            } catch (ClassNotFoundException ex) {
                Logger.getLogger(GuiceModule.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        /* === Test 2 [NOK : Not bound] */
        try{
            File file = new File(getClass().getResource("guiceBindings.xml").toURI());
            Properties properties = new Properties();
            properties.load(new FileReader(file));
            Names.bindProperties(binder(), properties);
        } catch (Exception ex) {
            Logger.getLogger(GuiceModule.class.getName()).log(Level.SEVERE, null, ex);
        }
        /* === Test 3 [OK : Manual edition] */
        bind(IReaderService.class).to(MemsReaderService.class);
        bind(IReportService.class).to(PdfReportService.class);
        bind(ISerializerService.class).to(JsonSerializerService.class);
        bind(ILoggerService.class).to(LoggerService.class);
    }
}

ServiceProvider.java:

public class ServiceProvider {
    // declaration of the services available [FOR NOW]
    @Inject IReaderService iReaderService;
    @Inject IReportService iReportService;
    @Inject ISerializerService iSerializerService;
    @Inject ILoggerService iLoggerService;

    public ServiceProvider(){
    }

    // getters of the services injected
    public IReaderService getReaderService() {
        return iReaderService;
    }

    public IReportService getReportService() {
        return iReportService;
    }

    public ISerializerService getSerializerService() {
        return iSerializerService;
    }

    public ILoggerService getLoggerService() {
        return iLoggerService;
    }
}

据我对 Guice 的了解,大多数绑定的一致性检查都是在编译时进行的,因此方法 #1 不会像现在这样工作。我认为您可以尝试使用即时绑定 (https://github.com/google/guice/wiki/JustInTimeBindings) and/or providers (https://github.com/google/guice/wiki/InjectingProviders),但最终我认为您不会实现目标。在我看来,最大的限制是您必须在源代码中明确指定要绑定的接口,然后您可以(也许)创建一个解析您的 xml 和 returns 的提供程序通过 Class.forName 的正确实施。我不知道这是否满足您的需求,但也许这是一个起点。

Guice 并不是为此而设计的。

这个想法是,通过在 classes 中进行操作,您可以获得在 class / @Provides 方法中进行操作的所有功能和灵活性,Provider<T> 实现、AOP 等等。正如您所观察到的,它确实有 Named.bindProperties,但这不是您出于上述原因而尝试做的事情。

但是,如果您愿意使用原始类型,您实际上可以执行方法 #1,然后自己检查 classes。这不是最干净的代码,但请注意,您的问题是 Class<?> 中的 generic typing,而不是 Guice。下面是一个示例,注释掉的伪代码指出您需要进行的更改才能使此代码在生产环境中工作。我想如果你已经走到这一步,你可以自己弄清楚。这是说明这个想法的代码:

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

public class DynamicBinding {
  static final String interfaceExample = "DynamicBinding$Foo";
  static final String implementationExample = "DynamicBinding$FooBar";

  public static void main(String... args) throws ClassNotFoundException {
    Injector inj = Guice.createInjector(new CustomModule());
    Foo blue = inj.getInstance(Foo.class);
    blue.doSomething();
  }

  static class CustomModule extends AbstractModule {

    @Override
    protected void configure() {
      // for (Entry<interface, implementation> : xml file) {
      bindFromStrings(interfaceExample, implementationExample);
      // }
    }

    private void bindFromStrings(String interfaceClass, String implClass) {
      try {
        Class fooClass = Class.forName(interfaceClass);
        // I recommend defining a custom exception here with a better message
        if (!fooClass.isInterface()) {
          throw new Exception("fooClass must be an interface!");
        }

        Class fooBarClass = Class.forName(implClass);
        // I recommend defining a custom exception here with a better message
        if (!fooClass.isAssignableFrom(fooBarClass)) {
          throw new Exception("classes must be in same inheritance hierarchy");
        }

        bind(fooClass).to(fooBarClass);
      } catch (Exception e) {
        // Logger.getLogger().log(blah);
        e.printStackTrace();
      }
    }
  }

  public static interface Foo {
    void doSomething();
  }

  public static class FooBar implements Foo {
    @Override
    public void doSomething() {
      System.out.println(this.getClass());
    }
  }
}

输出:

class DynamicBinding$FooBar

我找到了解决问题的办法,正如 durron597 所说,问题出在通用 Class<?> 而不是直接来自 Guice。我设法让一些东西起作用,但它有它的局限性。这是出于理解目的而注释的代码。

CustomModule.class

@Override
protected void configure() {
    // for each entry we got in the xml file
    for(Entry<String, String> entry : classNames.entrySet()){
        try {
            // we create the interface-typed class
            Class<IInterface> itf = (Class<IInterface>) Class.forName(entry.getKey());
            // and the implementation-typed class
            Class<IImplementation> concrete = (Class<IImplementation>) Class.forName(entry.getValue());
            // to finally bind them together
            bind(itf).to(concrete);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(GuiceModule.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

如您所见,我不再使用泛型类型:我所做的是创建了两个新接口(IInterfaceIImplementation)。我想与实现绑定的每个接口 必须 扩展 IInterface,并且每个实现 必须 扩展 IImplementation。该解决方案有效并注入了正确的实现,但暗示 extend/implement 仅用于输入目的的接口。

奖励:XML 解析器,以防万一有人对解决方案感兴趣

private void parseXmlBindings(){
    try {
        File file = new     File(getClass().getResource("guiceBindings.xml").toURI());
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(file);
        doc.getDocumentElement().normalize();
        NodeList nodeList = doc.getElementsByTagName("binding");

        for(int i = 0; i < nodeList.getLength(); i++){
            Node node = nodeList.item(i);
            if(node.getNodeType() == Node.ELEMENT_NODE){
                Element binding = (Element) node;

                NodeList itfList = binding.getElementsByTagName("interface");
                Element itfElement = (Element) itfList.item(0);
                NodeList itfValue = itfElement.getChildNodes();

                NodeList concreteList = binding.getElementsByTagName("implementation");
                Element concreteElement = (Element) concreteList.item(0);
                NodeList concreteValue = concreteElement.getChildNodes();

                classNames.put(itfValue.item(0).getNodeValue().trim(), concreteValue.item(0).getNodeValue().trim());
            }
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}