Reputation: 6934
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.
Upvotes: 4
Views: 5344
Reputation: 37832
This question is a bit old, but I've recently ran into a similar problem, and managed to solve it quite elegantly by adding a tiny extra layer, a factory.
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 try was to try to make Guice pass an instance of Repository<User>
in the constructor, and then I'd bind it somehow. The problem is that I really don't want to have to add repository bindings and providers for each model. If I were to do that, 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;
}
...
}
What I ended up doing is creating a RepositoryFactory
class, which is not generic by itself, but it 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 have to add a binding for each module. The service that uses a Repository would then look like:
class MyService {
private final Repository<User> userRepository;
@Inject MyService(final RepositoryFactory f) {
this.userRepository = f.getRepository(User.class);
}
...
}
You could also keep the instance of RepositoryFactory instead of already getting the Repository instance.
I hope this can be useful to someone.
Upvotes: 2
Reputation: 301
Beyond what Kohányi has said, you could load the DAO or entity classes reflectively by name, and then bind only the specific types asked for 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 would be
This runner has an AppleDao constructed with entityClass Apple
This runner has an OrangeDao constructed with entityClass Orange
I have changed the example to have entity classes implement an interface, in case they share some functionality that would be useful to Runner or GenericDao. If in fact you don't have such an interface, the technique also works with entity classes like String and Double if you remove the extends Entity
upper bounds (e.g. GenericDao<T>
).
I also removed the <T>
parameter on Runner, as that was providing no benefit due to type erasure. If you were to subclass Runner<T>
, then you might have Guice provide AppleRunner extends Runner<Apple>
or OrangeRunner extends Runner<Orange>
. But the type parameter provides nothing if Runner itself is the only concrete class Guice will be providing.
EDIT Oops, I left the class injections in. They are removed now. Of course, if you have a concrete GenericDao subclass for each entity, then perhaps you don't need to inject the entity classes themselves.
I suppose it's not clear to me whether you can provide concrete GenericDao subclasses for all the entity types ahead of time or not. If not, and you were only using the GenericDao class itself for each different kind of entity class, then you would want to inject concrete entity classes and not concrete DAO classes.
Upvotes: 1
Reputation: 10151
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.
Upvotes: 5
Reputation: 10519
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.
Upvotes: 3