Search code examples
javaxmldependency-injectionguicedynamic-binding

Guice : Set bindings from an XML file


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

Solution

  • 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