Guice : Set bindings from an XML file

删除回忆录丶 提交于 2019-12-05 22:28:08

Guice is not really designed for this.

The idea is that by doing it in classes, you get all the power and flexibility of being able to do it in a class / @Provides methods, Provider<T> implementations, AOP, and so forth. It does have Named.bindProperties, as you've observed, but this is not what you're trying to do for the reasons you've stated.

However, you can actually do method #1 if you're willing to use raw types, and then check the classes yourself. It's not the cleanest code, but note that your problem is the generic typing in Class<?>, not Guice. Here's an example, with commented out pseudocode pointing out the changes you'd need to make to make this code work in production. I figure if you've gotten this far you can figure that out yourself though. Here's the code illustrating the idea:

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());
    }
  }
}

Output:

class DynamicBinding$FooBar

I found a solution to my problem, and as durron597 stated, the problem was coming from the generic Class<?> and not Guice directly. I managed to have something working, but it has it's limitations. Here is the code commented for understanding purposes.

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);
        }
    }
}

As you can see, I don't use generic types anymore : What I've done is that I've created two new interfaces (IInterface and IImplementation). Every Interface I want to bind with an Implementation must extend IInterface, and every Implementation must extend IImplementation. This solution is working and injects the correct implementations, but implies to extend/implement interfaces used only for typing purposes.

Bonus : The XML parser, just in case someone is interested by the solution

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();
    }
}

From what I know about Guice, most of the consistency checks for bindings are made at compile time, so way #1 won't work as it is. I think you could try with just-in-time bindings (https://github.com/google/guice/wiki/JustInTimeBindings) and/or providers (https://github.com/google/guice/wiki/InjectingProviders), but in the end I don't think you'll achieve the goal. It seems to me that the biggest limitation is that you have to specify the interfaces you want to bind explicitly in the source code, and then you can (maybe) create a provider that parses your xml and returns the correct implementation via Class.forName. I don't know if this satisfies your needs, but maybe it's a starting point.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!