Google Guice运行时依赖项注入

桑德罗德克利

我正在寻找一种使用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>()
    }
}

到目前为止,仅凭SQlite,我将使用以下命令简单地请求依赖项:

@Inject 
@SQLite
private lateinit var database: Database

在运行期间如何进行选择?

马修·波普

在不完全了解您的代码特定性的情况下,我将提供三种通用方法。

(此外,我从未使用过Kotlin。我希望Java示例足以让您了解问题。)


第一种方法

听起来您需要一些非平凡的逻辑来确定哪种数据库实现才是正确的选择。这是ProviderBinding的经典案例绑定DatabaseDatabase负责提供实例的类(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);
}

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


第二种方法

您可以创建一个DatabaseRoutingProxy实现Database并委托给正确数据库实现的类。(我已经专业地使用过这种模式。我认为该设计模式没有“正式”名称,但是您可以在这里找到讨论。)这种方法基于惰性加载,Provider使用了Guice自动创建的提供者( 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
}

在您的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);
}

这种方法的优点是,您无需知道要进行哪种数据库调用,就可以真正进行数据库调用。

这两种方法都假定您不能实例化H2Database或SqliteDatabase的实例,除非实际存在备份数据库文件。如果可以在没有备份数据库文件的情况下实例化该对象,那么您的代码将变得更加简单。(只要有一个将实际Database实例作为构造函数args的router / proxy / delegator / )。


第三种方法

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

  1. 数据库实际上存在吗?(如果没有,则制作一个。)
  2. 哪个数据库存在?(并获得与之交互的正确的类。)

如果您甚至可以在创建需要了解问题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可以为构造函数参数传递适当的值。


笔记

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

使用Google Guice在运行时注入依赖项

Guice运行时依赖项参数重新注入

如何使用Google Guice在运行时基于注释注入接口实现

Google Guice与PicoContainer的依赖注入

使用依赖项注入在运行时确定实现

空指针异常 - 依赖注入 - Google Guice

Android依赖项'com.google.android.gms:play-services-tasks'对于编译(16.0.1)和运行时(17.0.0)类路径具有不同的版本

当依赖项需要运行时值时,如何注入依赖项?

Google guice的依赖注入在Scala中不起作用

如何在运行时控制OSGi中的依赖项注入

ASP.NET Core依赖项注入:服务在运行时使用Func委托解析

将运行时依赖项注入到nix包中

温莎城堡在嵌套类中注入依赖项,根接口在运行时解析

如何使用简单的注入器在运行时更改依赖项

Google Colab:本地运行时使用

Google Cloud Storage 运行时错误

使用Guice注入运行时生成的值

依赖注入可解决运行时数据的依赖

Android依赖com.google.firebase:firebase-core的不同版本,用于编译(12.0.1)和运行时(11.4.2)

App运行时Google Flutter Google Map API问题

如何实现春天依赖注入在运行时?

C#依赖注入运行时(动态)注册

在运行时初始化依赖项

安全删除“未使用的”运行时依赖项

gradle 从运行时依赖项中排除特定的 jars

Spring从依赖项中选择运行时实现

在Python轮子中包括运行时依赖项

OSGi中仅运行时依赖项的最佳实践

如何在运行时动态更改依赖项