Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using guice to pass in a run-time parameter to constructor

If I have the following class:

public class ObjectDAOMongoDBImpl<T> extends GenericDAOMongoDBImpl<T, ObjectId> implements ObjectDAO<T> {
    public ObjectDAOMongoDBImpl(Class<T> entityClass, Mongo mongo, Morphia morphia, String dbName) {
        super(entityClass, mongo, morphia, dbName);
    }
}

Where, entityClass is provided at run-time - how can I use guice to bind the said type to an interface?

public class RunnerModule extends AbstractModule {      
    @Override
    protected void configure() {
        bind(GenericDAO.class).to(ObjectDAOMongoDBImpl.class);
    }
}

public class Runner<T, V> {
    GenericDAO<T, V> dao;

    @Inject
    public Runner(GenericDAO<T, V> dao) {
        this.dao = dao;
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new RunnerModule());
        injector.getInstance(Runner.class);
    }
}

It's fine to define mongo, morphia, and dbName as literals to RunnerModule (is there a cleaner way?), but I have no way of knowing what entityClass is until runtime.

like image 599
wulfgarpro Avatar asked Dec 21 '22 06:12

wulfgarpro


2 Answers

This isn't doable with Guice idiomatically, and it isn't its primary focus either.

jfpoilpret have said everything that can be said, but I would like to approach the problem from another direction, where you have the option to (possibly) solve your problem by losing type-safety.

So, in your code, you ask Guice to get an instance of your Runner<T, V> class like this

injector.getInstance(Runner.class);

but this can't be resolved by Guice, because Runner<T, V> has a dependency on GenericDAO<T, V>, but you didn't bind an exact implementation for it. So as jfpoilpret has said, you have to bind some concrete implementations for it in your module.

I'm guessing that you want to determine the exact GenericDAO<T, V> implementation that you pass to your Runner<T, V> based on some input data, which data's type isn't known at compile time. Now, let's assume you have two implementations.

bind(new TypeLiteral<GenericDAO<String, ObjectID>>(){}).to(StringDAO.class);
bind(new TypeLiteral<GenericDAO<Double, ObjectID>>(){}).to(IntegerDAO.class);

Based on different type of inputs you can do this

Injector injector = Guice.createInjector(new RunnerModule());

// possible input which you get from *somewhere* dynamically
Object object = 1.0;

TypeLiteral<?> matchedTypeLiteral = null;
for (Key<?> key : injector.getAllBindings().keySet()) {
  TypeLiteral<?> typeLiteral = key.getTypeLiteral();
  Type type = typeLiteral.getType();
  if (type instanceof ParameterizedType) {
    ParameterizedType parameterizedType = (ParameterizedType) type;
      if (parameterizedType.getRawType() == GenericDAO.class) {
        List<Type> actualTypeArguments =    Arrays.asList(parameterizedType.getActualTypeArguments());
        if (actualTypeArguments.get(0) == object.getClass())
          matchedTypeLiteral = typeLiteral;
    }
  }
};

Runner<?, ?> runner = new Runner<>((GenericDAO<?, ?>) injector.getInstance(Key.get(matchedTypeLiteral)));
System.out.println(runner.dao.getClass()); // IntegerDAO.class

If Object object = "string";, then the other implementation will be found. This is of course rather ugly and can be improved with checking for sub-classes and stuff, but I think you get the idea. The bottom-line is that you can't get around this.

If you manage to do it (getting around it), please drop me an e-mail because I would like to know about it! I had faced the same problem as you're facing not too long ago. I've written a simple BSON codec where I wanted to load specific implementations of a generic interface based on the type of some arbitrary input. This worked well with Java-to-BSON mappings, but I couldn't do it the other way around in any sensible way, so I've opted for a simpler solution.

like image 189
Kohányi Róbert Avatar answered Dec 24 '22 00:12

Kohányi Róbert


The way you wrote it, entityClass can only be Object.class (== Class<Object>), and nothing else.

Hence, first of all, your ObjectDAOMongoDBImpl should be generic:

public class ObjectDAOMongoDBImpl<T> 
    extends GenericDAOMongoDBImpl<T, ObjectId> ...

That part of the problem is related to java, not Guice.

Now for Guice part, you need to define a binding including the generic types, ie by using Guice TypeLiteral:

bind(new TypeLiteral<GenericDAO<T, V>>(){}).to(...);

where T and V must be known in the code above (can't just be generic parameters there).

Looking at this question may also give you further details related to your situation.

like image 44
jfpoilpret Avatar answered Dec 24 '22 01:12

jfpoilpret