我正在寻找一种方式来动态地选择使用谷歌吉斯运行时正确的依赖。
我用例是科特林应用程序,它可以根据与所提供的配置文件或者sqlite的或H2数据库。
在执行应用程序时,文件被读取,如果找不到该数据库,创建并迁移到正确的。
我的数据库结构包含Database
(接口),H2Database: Database
,SQLiteDatabase: Database
和模块绑定类,看起来像这样:
class DatabaseModule: KotlinModule() {
override fun configure() {
bind<Database>().annotatedWith<configuration.H2>().to<H2Database>()
bind<Database>().annotatedWith<configuration.SQLite>().to<SQLiteDatabase>()
}
}
到目前为止,单独SQlite的,我只想请求,利用相关性:
@Inject
@SQLite
private lateinit var database: Database
我将如何让运行在此选择?
没有太多了解你的代码的具体,我会提供三种常用的方法。
(另外,我从来没有使用科特林。我希望Java的样本足够你去理出头绪来。)
这听起来像你需要一些不平凡的逻辑来确定哪个数据库的实现是使用正确的。这是一个ProviderBinding的经典案例。相反,结合Database
到特定的实现,你绑定Database
一类是负责提供情况(一Provider)。例如,你可能有这个类:
public class MyDatabaseProvider.class implements Provider<Database> {
@Inject
public MyDatabaseProvider.class(Provider<SQLiteDatabase> sqliteProvider, Provider<H2Database> h2Provider) {
this.sqliteProvider = sqliteProvider;
this.h2Provider = h2Provider;
}
public Database get() {
// Logic to determine database type goes here
if (isUsingSqlite) {
return sqliteProvider.get();
} else if (isUsingH2) {
return h2Provider.get();
} else {
throw new ProvisionException("Could not determine correct database implementation.");
}
}
}
(附注:此示例代码让你每一次一个新的实例,是相当简单的,使这也返回一个单一实例。)
然后,要使用它,你有两个选择。在你的模块,你就不会绑定Database
到特定的实现,但你DatabaseProvider
。像这样:
protected void configure() {
bind(Database.class).toProvider(MyDatabaseProvider.class);
}
这种方法的好处是,你不需要知道正确的数据库实现,直到吉斯尝试构建需要Database
作为其构造args来一个对象。
你可以创建一个实现DatabaseRoutingProxy
,然后委托给正确的数据库实施Database
类。使用供应商(我用这个模式的专业。我不认为有一个“正式”的名字为这个设计模式,但你可以找到一个讨论here)。这种方法是基于lazy loading with Provider
是吉斯automatically creates(1)每一个绑定类型。
public class DatabaseRoutingProxy implements Database {
private Provider<SqliteDatabse> sqliteDatabaseProvider;
private Provider<H2Database> h2DatabaseProvider;
@Inject
public DatabaseRoutingProxy(Provider<SqliteDatabse> sqliteDatabaseProvider, Provider<H2Database> h2DatabaseProvider) {
this.sqliteDatabaseProvider = sqliteDatabaseProvider;
this.h2DatabaseProvider = h2DatabaseProvider;
}
// Not an overriden method
private Database getDatabase() {
boolean isSqlite = // ... decision logic, or maintain a decision state somewhere
// If these providers don't return singletons, then you should probably write some code
// to call the provider once and save the result for future use.
if (isSqlite) {
return sqliteDatabaseProvider.get();
} else {
return h2DatabaseProvider.get();
}
}
@Override
public QueryResult queryDatabase(QueryInput queryInput) {
return getDatabase().queryDatabase(queryInput);
}
// Implement rest of methods here, delegating as above
}
而你的吉斯模块:
protected void configure() {
bind(Database.class).to(DatabaseRoutingProxy.class);
// Bind these just so that Guice knows about them. (This might not actually be necessary.)
bind(SqliteDatabase.class);
bind(H2Database.class);
}
这种方法的好处是,你并不需要能够知道使用哪些数据库实现,直到你真正做一个数据库调用。
这两种方法都被假定除非后备数据库文件确实存在,你不能实例H2Database的实例或SqliteDatabase。如果这是可能的实例化对象没有后盾的数据库文件,然后你的代码变得更简单。 (只要有一个路由器/代理/委托/不管是采取实际Database
实例作为构造ARGS)。
这种方法是完全不同的,那么最后两个。在我看来,像你的代码实际上是处理两个问题:
如果你甚至可以创建需要知道答案的问题2吉斯注射器之前解决问题1,那么你不需要做复杂的事情。你可以只是有一个数据库模块是这样的:
public class MyDatabaseModule extends AbstractModule {
public enum DatabaseType {
SQLITE,
H2
}
private DatabaseType databaseType;
public MyDatabaseModule(DatabaseType databaseType) {
this.databaseType = databaseType;
}
protected void configure() {
if (SQLITE.equals(databaseType)) {
bind(Database.class).to(SqliteDatabase.class);
} else if (H2.equals(databaseType)) {
bind(Database.class).to(H2Database.class);
}
}
}
既然你已经分离出的问题1和2,当您创建将使用MyDatabaseModule
喷油器,你可以在构造函数的参数适当的值传递。
笔记
Provider<T>
每一个结合T
。我已经成功地创建绑定,而无需创建相应的提供者,因此吉斯必须是自动创建用于构造绑定提供者。 (编辑:我发现more documentation各国这更清楚。)