我正在寻找一种使用Google guice在运行时动态选择正确依赖项的方法。
我的用例是一个kotlin应用程序,根据提供的配置文件,它可以与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
在运行时如何选择?
答案 0 :(得分:0)
在不了解您代码的具体细节的情况下,我将提供三种通用方法。
(而且,我从未使用过Kotlin。我希望Java示例足以让您了解情况。)
听起来您需要一些非平凡的逻辑来确定哪种数据库实现才是正确的选择。这是ProviderBinding的经典案例。您将Database
绑定到负责提供实例的类(Provider),而不是将Database
绑定到特定的实现。例如,您可能拥有此类:
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);
}
这种方法的优点是,在Guice尝试构造一个需要Database
作为其构造函数参数之一的对象之前,您不需要知道正确的数据库实现。
您可以创建一个实现DatabaseRoutingProxy
的{{1}}类,然后委托给正确的数据库实现。 (我已经专业地使用过这种模式。我认为该设计模式没有“正式”名称,但是您可以找到讨论here。)该方法基于lazy loading with Provider
,使用为每种绑定类型提供Guice automatically creates(1)的提供程序。
Database
在您的Guice模块中:
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
}
这种方法的优点是,您无需真正进行数据库调用就可以知道要使用哪种数据库实现。
这两种方法都假定除非实例数据库文件确实存在,否则您不能实例化H2Database或SqliteDatabase的实例。如果可以在没有备份数据库文件的情况下实例化对象,那么您的代码将变得更加简单。 (只要有一个将实际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);
}
实例作为构造函数args的router / proxy / delegator /)。
此方法与后两种方法完全不同。在我看来,您的代码实际上正在处理两个问题:
如果您甚至可以在创建需要了解问题2答案的引导注入器之前解决问题1,那么您无需做任何复杂的事情。您可以只拥有一个这样的数据库模块:
Database
由于您已经分离出问题1和2,因此在创建将使用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);
}
}
}
的注入器时,可以为构造函数参数传递适当的值。
注释
MyDatabaseModule
将存在一个Provider<T>
。我已经成功创建了绑定,而没有创建相应的提供程序,因此Guice必须自动为配置的绑定创建提供程序。 (编辑:我发现more documentation可以更清楚地说明这一点。)