3

I'm trying to use Guice and make all the bindings with the help of an XML file. In my module (let's say "CustomModule"), I would like to load an XML file and parse it to set all the bindings.

I'm able to load the XML file and retrieve all the values needed (below is an exemple of my XML file), but I'm unable to use those values to bind(interfaceValue).to(implementationValue);.

What I've tried so far:

  1. Load the XML file, retrieve all the values and use them as : bind(Class.fromName(Ivalue)).to(Class.fromName(Value)); where Ivalue is InterfaceFoo and Value is Foo.
  2. Load the XML file as a Properties file and use Names.bindProperties(binder(), properties);.
  3. Bind manually, which is not what I want.

Results:

  1. Doesn't work, because Guice cannot verify if the implementation is an implementation of the interface.
  2. Gives an error No implementation for interface was bound.
  3. Works, but it is not wanted since I have to edit my CustomModule to change the bindings (in the case if I want Bar to be the implementation of InterfaceFoo).

I've looked at this, but with not so much success since there is not very much documentation on it. I also looked for a solution here on SO, but most of the time the questions are about properties or the use of annotation.

Is there a simple way to specify the Interfaces / Implementations in a file and give it to Guice as a "configuration"?

My XML file:

<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;
    }
}
durron597
  • 31,968
  • 17
  • 99
  • 158
Jacks
  • 587
  • 5
  • 28
  • 1
    Welcome to StackOverflow! I'm glad you've decided to start posting questions and answers; this was an extremely well written question for your first one. Keep it up! Aside: note that this is a site for expert questions and answers, **not** a forum - saying things like "I am a beginner" or "this is my first question" are generally frowned upon because they detract from the question itself, so I have removed it. – durron597 May 21 '15 at 14:52
  • Okay, thank you durron597 ! I'll keep that in mind for the next questions I would ask. – Jacks May 22 '15 at 07:53

3 Answers3

3

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
Community
  • 1
  • 1
durron597
  • 31,968
  • 17
  • 99
  • 158
  • Thank you for your answer @durron597. It is true that it provides some more flexibility and `Providers` are really interesting, but it seems that we have to "hardcode" what we want to provide. I want to have a kind of library and just pass it a configuration file, and the library does the magic-bindings. I've found another solution, but I will accept yours since it is more clean. Again, thank you, and tell me if I said something wrong about `Providers`. – Jacks May 22 '15 at 08:07
  • I've looked at the `Providers` as you stated, and you were right. I understand now why you're talking about flexibility and power, they can provide a plenty of implementations all coming from a single interface (with the help of some configuration saved somewhere). I will post an example if I manage to get it working, but your answer helped me a lot to understand how and why Guice is so powerful. – Jacks May 22 '15 at 09:19
1

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();
    }
}
Jacks
  • 587
  • 5
  • 28
  • If you don't check for `isAssignableFrom` as I did in my answer, you risk [polluting the heap](http://programmers.stackexchange.com/questions/155994/java-heap-pollution). Try to run your module with classes that aren't in the same hierarchy and see what happens. – durron597 May 22 '15 at 13:52
  • You are right @durron597, I know what you mean by polluting the heap. Plus, my solution is a bit "dangerous" : it implies to perfectly know the classes / interfaces and their relations. Finally, I gave up on this solution and used Providers / ProvidedBy annotation instead. If someone is still looking for a solution about "dynamic guice bindings" I'll post it here. – Jacks May 25 '15 at 13:45
0

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.

francesco foresti
  • 2,004
  • 20
  • 24
  • Thank your for your suggestion. Just-in-time bindings are not well-suited for my case, because it means that my interface will only be implemented by a specific class, which is wrong since it can change depending the choosen configuration. The best solution would be to have dynamic bindings depending only on the XML file provided. I will take a deeper look at the providers, thank you. – Jacks May 21 '15 at 12:47