Google Guice 运行时依赖注入

san*_*cli 4 guice kotlin

我正在寻找一种在运行时使用 google guice 动态选择正确依赖项的方法。

我的用例是一个 kotlin 应用程序,它可以根据提供的配置文件使用 sqlite 或 h2 数据库。

执行应用程序时会读取该文件,如果未找到数据库,则会创建正确的数据库并将其迁移到其中。

我的数据库结构包含Database(接口)H2Database: DatabaseSQLiteDatabase: Database和模块绑定类,看起来像这样:

class DatabaseModule: KotlinModule() {
    override fun configure() {
        bind<Database>().annotatedWith<configuration.H2>().to<H2Database>()
        bind<Database>().annotatedWith<configuration.SQLite>().to<SQLiteDatabase>()
    }
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,仅使用 SQlite,我只需使用以下命令请求依赖项:

@Inject 
@SQLite
private lateinit var database: Database
Run Code Online (Sandbox Code Playgroud)

我将如何在运行时进行此选择?

Mat*_*ope 7

在不太了解您的代码的具体内容的情况下,我将提供三种通用方法。

(另外,我从来没有用过 Kotlin。我希望 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.");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

(旁注:此示例代码每次都会为您提供一个新实例。让它也返回一个单例实例是相当简单的。)

然后,要使用它,您有两个选择。在您的模块中,您不会绑定Database到特定的实现,而是绑定到您的DatabaseProvider. 像这样:

protected void configure() {
    bind(Database.class).toProvider(MyDatabaseProvider.class);
}
Run Code Online (Sandbox Code Playgroud)

这种方法的优点是,在 Guice 尝试构造一个需要Database作为其构造函数参数之一的对象之前,您不需要知道正确的数据库实现。


第二种方法

您可以创建一个DatabaseRoutingProxy类,该类实现Database然后委托给正确的数据库实现。(我专业地使用了这种模式。我认为这种设计模式没有“官方”名称,但您可以在此处找到讨论。)这种方法基于延迟加载,Provider使用 Guice自动创建的 Providers ( 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
}
Run Code Online (Sandbox Code Playgroud)

在你的 Guice 模块中:

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);
}
Run Code Online (Sandbox Code Playgroud)

这种方法的优点是在您实际进行数据库调用之前,您不需要知道要使用哪个数据库实现。

这两种方法都假设您无法实例化 H2Database 或 SqliteDatabase 的实例,除非后备数据库文件确实存在。如果可以在没有后备数据库文件的情况下实例化对象,那么您的代码就会变得简单得多。(只要有一个路由器/代理/委托人/任何将实际Database实例作为构造函数参数的东西。)


第三种方法

这种方法与后两种完全不同。在我看来,您的代码实际上是在处理两个问题:

  1. 数据库真的存在吗?(如果没有,那就做一个。)
  2. 存在哪个数据库?(并获得正确的类来与之交互。)

如果您甚至可以在创建需要知道问题 2 答案的 guice 注入器之前解决问题 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

由于您已将问题 1 和 2 分开,因此当您创建将使用 的注入器时MyDatabaseModule,您可以为构造函数参数传入适当的值。


笔记

  1. Injector 文档指出,Provider<T>每个绑定都会存在一个T。我在没有创建相应提供程序的情况下成功创建了绑定,因此 Guice 必须为配置的绑定自动创建一个提供程序。(编辑:我发现更多的文档更清楚地说明了这一点。)