Using guice to pass in the run-time parameter for the 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 is entityClass provided at runtime - how can I use guice to bind a specified 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 nice to define mongo , morphia and dbName as literals for the RunnerModule (is there a cleaner way?), But I don't know what entityClass up to.

+4
source share
4 answers

This is not feasible with Guice idiomatically, and this is not his main focus.

jfpoilpret said everything that can be said, but I would like to approach the problem from a different direction, where you have the opportunity (possibly) to solve your problem, having lost type safety.

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

 injector.getInstance(Runner.class); 

but this cannot be resolved by Guice, because Runner<T, V> has a dependency on GenericDAO<T, V> , but you did not bind the exact implementation to it. As jfpoilpret said, you need to link some specific implementations for it in your module.

I assume that you want to determine the exact implementation of GenericDAO<T, V> that you pass to your Runner<T, V> based on some input whose data type is unknown at compile time. Now suppose 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 types 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 another implementation will be found. This, of course, is pretty ugly and could be improved with checking subclasses and stuff, but I think you get the idea. The bottom line is that you cannot get around this.

If you manage to do this (get around this), write me an email, because I would like to know about it! I encountered the same problem that you have been facing not so long ago. I wrote a simple BSON codec where I wanted to load certain implementations of a universal interface based on the type of some arbitrary input. This affected Java-BSON mappings well, but I couldn't do it the other way around in a reasonable way, so I chose a simpler solution.

+5
source

As you wrote it, entityClass can only be Object.class (== Class<Object> ), and nothing more.

Therefore, first of all, your ObjectDAOMongoDBImpl should be shared:

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

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

Now for the Guice part, you need to define a binding, including generic types, i.e. using Guice TypeLiteral :

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

where T and V should be known in the code above (there cannot be only general parameters there).

Considering this issue, you can also give more detailed information about your situation.

+3
source

This question is a bit outdated, but I recently ran into a similar problem and managed to solve it quite elegantly by adding a tiny extra factory layer.

Consider the following repository:

 public interface Repository<T extends Model<T>> { void save(T t); T load(long key); } class SomeDbRepositoryImpl<T extends Model<T>> implements Repository<T> { private final SomeDbConnection db; private final Class<T> type; RepositoryImpl(final Class<T> type, final SomeDbConnection db) { this.db = db; this.type = type; } ... } 

Then suppose I have a service that needs an instance of Repository<User> . My first attempt was to get Guice to pass an instance of Repository<User> in the constructor, and then I would somehow bind it. The problem is that I really don't want to add repository and provider bindings for each model. If I did this, the code would look like this:

 // Won't work. class MyService { private final Repository<User> userRepository; @Inject MyService(final Repository<User> userRepository) { this.userRepository = userRepository; } ... } 

As a result, I created the RepositoryFactory class, which in itself is not generic, but contains a generic method.

 public interface RepositoryFactory { <T extends Model<T>> Repository<T> getRepository(Class<T> type); } class SomeDbRepositoryFactoryImpl implements RepositoryFactory { private final SomeDbConnection db; @Inject SomeDbRepositoryFactoryImpl(final SomeDbConnection db) { this.db = db; @Override <T extends Model<T>> Repository<T> getRepository(Class<T> type) { return new SomeDbRepositoryImpl(type, db); } } 

So this is completely type safe, and I don't need to add a binding for each module. A service using the repository will look like this:

 class MyService { private final Repository<User> userRepository; @Inject MyService(final RepositoryFactory f) { this.userRepository = f.getRepository(User.class); } ... } 

You can also save an instance of RepositoryFactory, rather than getting an instance of a repository.

I hope this can be helpful to someone.

+2
source

Besides what Kohani said, you can load DAO or entity classes reflectively by name, and then only bind the specific types specified in the command line arguments:

 package com.example; public class App { public static void main(final String[] args) { final Injector appleInjector = Guice.createInjector(new DynamicDaoModule(getClass("com.example.AppleDao"))); appleInjector.getInstance(Runner.class); final Injector orangeInjector = Guice.createInjector(new DynamicDaoModule( getClass("com.example.OrangeDao"))); orangeInjector.getInstance(Runner.class); // final Injector commandLineInjector = Guice.createInjector(new DynamicDaoModule(getClass(args[0]))); // commandLineInjector.getInstance(Runner.class); } private static Class getClass(final String className) { try { return Class.forName(className); } catch (final ClassNotFoundException e) { throw new RuntimeException(e); } } } class DynamicDaoModule extends AbstractModule { private final Class<? extends GenericDao<? extends Entity>> daoClass; public DynamicDaoModule(final Class<? extends GenericDao<? extends Entity>> daoClass) { this.daoClass = daoClass; } @Override protected void configure() { // bind GenericDao<? extends Entity> to daoClass final TypeLiteral<GenericDao<? extends Entity>> daoOfEntity = (TypeLiteral) TypeLiteral.get(Types.newParameterizedType(GenericDao.class, Types.subtypeOf(Entity.class))); bind(daoOfEntity).to(daoClass); } } interface Entity { } class Apple implements Entity { } class Orange implements Entity { } class Runner { @Inject public Runner(final GenericDao<? extends Entity> dao) { System.out.println("This runner has an " + dao); } } class GenericDao<T extends Entity> { private final Class<? extends Entity> entityClass; protected GenericDao(final Class<? extends Entity> entityClass) { this.entityClass = entityClass; } @Override public String toString() { return String.format("%s constructed with entityClass %s", getClass().getSimpleName(), entityClass.getSimpleName()); } } class AppleDao extends GenericDao<Apple> { @Inject public AppleDao() { super(Apple.class); } } class OrangeDao extends GenericDao<Orange> { @Inject public OrangeDao() { super(Orange.class); } } 

And the output will be

 This runner has an AppleDao constructed with entityClass Apple This runner has an OrangeDao constructed with entityClass Orange 

I modified this example so that object classes implement the interface if they have some functions that would be useful for Runner or GenericDao. If you don’t actually have such an interface, this method also works with entity classes such as String and Double, if you remove the extends Entity upper bounds (e.g. GenericDao<T> ).

I also removed the <T> parameter on Runner, as it was of no use due to type erasure. If you were a subclass of Runner<T> , then you might have Guice AppleRunner extends Runner<Apple> or OrangeRunner extends Runner<Orange> . But the type parameter does not give anything if Runner itself is the only concrete class that Guice will provide.

EDIT . Sorry, I left class injections. They are now deleted. Of course, if you have a specific subclass of GenericDao for each object, then you probably do not need to enter the entity classes themselves.

I suppose it's not clear to me if you can provide specific subclasses of GenericDao for all entity types ahead of time or not. If not, and you used only the GenericDao class for each type of entity class, then you would like to introduce specific entity classes, not specific DAO classes.

+1
source

Source: https://habr.com/ru/post/1381431/


All Articles