2011-11-16 57 views
4

如果我有下面的類:使用吉斯在運行時的參數傳遞給構造器

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在運行時提供的 - 我該如何使用吉斯綁定的說鍵入一個接口?

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直到運行的方式。

+0

你能澄清「在運行時提供」的句子嗎?如何以及何時提供'entityClass'? – jfpoilpret

+0

@jfpoilpret - 通過cli參數 – wulfgarpro

回答

5

這不是Guice慣用的,它也不是它的主要焦點。

jfpoilpret說的一切,可以說,但我想從另一個方向,在那裏你必須失去類型安全(可能)解決您的問題的選項處理這個問題。

所以,在你的代碼,你問吉斯讓你Runner<T, V>類的一個實例是這樣

injector.getInstance(Runner.class); 

但這不能由吉斯得到解決,因爲Runner<T, V>GenericDAO<T, V>的依賴,但你沒有確切的實現它。正如jfpoilpret所說,你必須在你的模塊中綁定一些的具體實現。

我猜你想要根據一些輸入數據確定你傳遞給Runner<T, V>的確切GenericDAO<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到BSON的映射非常有效,但我無法以任何明智的方式做到這一點,所以我選擇了一個更簡單的解決方案。

3

你寫它的方式,entityClass只能是Object.class(== Class<Object>),沒有別的。

因此,首先,你ObjectDAOMongoDBImpl應該是通用的:

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

的問題的一部分是與Java,不吉斯。

現在對於吉斯部分,需要定義的結合包括通用的類型,通過使用吉斯TypeLiteral即:

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

其中T和V必須在上面的代碼是已知的(不能只是那裏的通用參數)。

看着this問題也可能會給你更多的細節與你的情況有關。

+0

謝謝概述我的DAO規範中的問題。 – wulfgarpro

+0

_「在上面的代碼中必須知道T和V」_...這就是我確切的問題。這些類型直到運行時纔會知道。 – wulfgarpro

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

和輸出是

​​

我改變了這個例子讓實體類實現一個接口,以防他們共享一些對Runner或GenericDao有用的功能。如果實際上你沒有這樣的接口,那麼如果你刪除了extends Entity的上限(例如GenericDao<T>),該技術也適用於像String和Double這樣的實體類。

我還刪除了Runner上的<T>參數,因爲這種類型擦除沒有提供任何好處。如果你是Runner<T>的子類,那麼你可能有Guice提供AppleRunner extends Runner<Apple>OrangeRunner extends Runner<Orange>。但是,如果Runner本身是Guice將提供的唯一具體類,則類型參數不會提供任何內容。

編輯糟糕,我離開了課堂注射。他們現在被刪除。當然,如果您爲每個實體都有一個具體的GenericDao子類,那麼您可能不需要自己注入實體類。

我想我不清楚你是否可以提前爲所有實體類型提供具體的GenericDao子類。如果不是,並且您只爲每種不同類型的實體類使用GenericDao類,那麼您將希望注入具體的實體類而不是具體的DAO類。

+0

我不知道(或者至少完全忘記了)Guice的'Types'類。好用! –

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的實例,而不是已經獲取庫實例。

我希望這可以對某人有用。

相關問題