如果我有以下课程:
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);
}
}
可以将mongo
,morphia
和dbName
定义为RunnerModule
的文字(有更简洁的方式吗?),但我无法知道entityClass
直到运行时。
答案 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类。