使用guice将运行时参数传递给构造函数

时间:2011-11-16 06:16:30

标签: java dependency-injection guice

如果我有以下课程:

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

在运行时提供entityClass的位置 - 如何使用 guice 将所述类型绑定到接口?

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

可以将mongomorphiadbName定义为RunnerModule的文字(有更简洁的方式吗?),但我无法知道entityClass直到运行时。

4 个答案:

答案 0 :(得分:5)

这不适用于Guice,也不是它的主要焦点。

jfpoilpret 已经说了所有可以说的内容,但我想从另一个方向解决问题,你可以选择(可能)通过失去类型安全来解决你的问题。 / p>

因此,在您的代码中,您要求Guice获取此类Runner<T, V>类的实例

injector.getInstance(Runner.class);

但Guice无法解决这个问题,因为Runner<T, V>依赖于GenericDAO<T, V>,但您没有为它绑定精确的实现。正如jfpoilpret所说,你必须在你的模块中绑定一些的具体实现。

我猜你想根据一些输入数据来确定传递给GenericDAO<T, V>的确切Runner<T, V>实现,这些数据的类型在编译时是未知的。现在,让我们假设你有两个实现。

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

根据不同类型的输入,您可以执行此操作

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

如果Object object = "string";,则会找到其他实施。这当然相当丑陋,可以通过检查子类和东西来改进,但我认为你明白了。最重要的是你无法解决这个问题。

如果你设法做到了(绕过它),请给我发电子邮件,因为我想了解它!我不久前遇到了同样的问题。我写了一个简单的BSON codec,我想根据某些任意输入的类型加载泛型接口的特定实现。这适用于Java-to-BSON映射,但我不能以任何合理的方式做到这一点,所以我选择了一个更简单的解决方案。

答案 1 :(得分:3)

您编写它的方式,entityClass只能是Object.class(== Class<Object>),而不是其他内容。

因此,首先,您的ObjectDAOMongoDBImpl应该是通用的:

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

问题的一部分与java有关,而不是Guice。

现在对于Guice部分,您需要定义包含泛型类型的绑定,即使用Guice TypeLiteral

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

其中必须在上面的代码中知道T和V(不能只是通用参数)。

查看this问题也可能会为您提供与您的情况相关的更多详细信息。

答案 2 :(得分:2)

这个问题有点陈旧,但我最近遇到了类似的问题,并通过添加一个小小的额外层,一个工厂来设法解决它。

考虑以下存储库

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

然后,假设我有一个需要Repository<User>实例的服务。我的第一次尝试是尝试让Guice在构造函数中传递Repository<User>的实例,然后我以某种方式绑定它。问题是我真的不想为每个模型添加存储库绑定和提供程序。如果我这样做,代码将如下所示:

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

我最终做的是创建一个RepositoryFactory类,它本身不是通用的,但它包含一个通用方法。

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

因此,这完全是类型安全的,我不必为每个模块添加绑定。使用存储库的服务将如下所示:

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

您还可以保留RepositoryFactory的实例,而不是已经获取Repository实例。

我希望这对某人有用。

答案 3 :(得分:1)

除了Kohányi所说的,您可以通过名称反复加载DAO或实体类,然后仅绑定命令行参数中要求的特定类型:

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

输出将是

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

我已经更改了示例,让实体类实现一个接口,以防它们共享一些对Runner或GenericDao有用的功能。如果实际上你没有这样的接口,那么如果删除extends Entity上限(例如GenericDao<T>),该技术也可以使用String和Double等实体类。

我还删除了Runner上的<T>参数,因为类型擦除没有带来任何好处。如果你是Runner<T>的子类,那么你可能会让Guice提供AppleRunner extends Runner<Apple>OrangeRunner extends Runner<Orange>。但是如果Runner本身是Guice将提供的唯一具体类,则type参数不提供任何内容。

编辑哎呀,我把课程注入了。他们现在被删除了。当然,如果每个实体都有一个具体的GenericDao子类,那么也许你不需要自己注入实体类。

我想我不清楚你是否可以提前为所有实体类型提供具体的GenericDao子类。如果没有,并且您只为每种不同类型的实体类使用GenericDao类,那么您可能希望注入具体的实体类而不是具体的DAO类。