向导:从XML文件设置绑定



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

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

我已经试过了:

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

结果:

  1. 不起作用,因为Guice无法验证实现是否是接口的实现。
  2. 给出错误No implementation for interface was bound .
  3. 工作,但它不是想要的,因为我必须编辑我的CustomModule来改变绑定(在这种情况下,如果我想要BarInterfaceFoo的实现)。

我看过这个,但是没有太大的成功,因为没有太多的文档。我也在SO上寻找解决方案,但大多数时候问题都是关于属性或注释的使用。

是否有一种简单的方法来指定文件中的接口/实现,并将其作为"配置"给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并不是为此而设计的。

这个想法是,通过在类中完成它,您获得了能够在类/@Provides方法、Provider<T>实现、AOP等中完成它的所有功能和灵活性。它确实有Named.bindProperties,正如你所观察到的,但这不是你想要做的,因为你已经陈述了原因。

但是,如果您愿意使用原始类型,您实际上可以执行方法#1,然后自己检查类。这不是最干净的代码,但请注意,您的问题是Class<?>中的泛型,而不是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。这个解决方案是有效的,并且注入了正确的实现,但是意味着要扩展/实现仅用于类型目的的接口。

附加功能: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();
    }
}

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

最新更新