【问题标题】:Dependency injection using guice for a client sdk/library design pattern使用 guice 进行客户端 sdk/库设计模式的依赖注入
【发布时间】:2014-03-04 22:42:19
【问题描述】:

我正在为 Web API 构建客户端 SDK,并尝试通过 guice 应用依赖注入。此 Java 客户端将被第三方用作访问我们 API 的一种方式。

我希望能够注入我的外部依赖项(使用的 HTTP 客户端等),并为开发人员提供一种方法来注入这些依赖项的不同版本,如果他们想要或者如果我想自己更改实现(一个好的依赖注入的情况对吗?)。

但是,为了连接依赖项,我必须让我的库的用户创建一个注入器等,如下所示:

Injector injector = Guice.createInjector(new MyAPIClientModule(url, username, password));
        this.service = injector.getInstance(MyAPIService.class);

我不想将它推送给我的库的用户,但我仍然希望让用户能够选择不同的实现或底层 HTTP 库等。

我是否以某种方式错过了 guice 或 DI 的意义?这是使用 guice 时的标准做法吗?

或者我应该将它包装在另一个执行注入的类中,并向第三方用户提供一个示例 Java 对象?

【问题讨论】:

  • 您可以让您的“注入”类使用 javax.inject API,并使用 Guice 提出注入实现;您会错过 javax.inject 不提供的一些高级 Guice 功能,但这可能是也可能不是问题。

标签: java design-patterns dependency-injection guice


【解决方案1】:

我希望能够注入我的外部依赖项(http 客户端 使用等)并为开发人员提供注入不同版本的方法 如果他们想要或者我曾经想要更改这些依赖项 自己实现(依赖注入的一个很好的例子吗?)。

这对于 DI 来说是一个很好的案例,这一点值得商榷。像 HTTP 客户端这样的外部依赖项通常具有具体的接口,除了该依赖项之外没有人实现。我个人无法想象你的程序是如何编写的,因为交换底层 HTTP 客户端不会影响它的架构,也就是说,除非你为它提供你自己的外观,像

public interface HttpClient {
    HttpResponse send(HttpRequest request);
}

其中HttpRequestHttpResponse 也是自定义类/接口。但是向最终用户提供这种扩展点很少合适,特别是如果您没有一些参考实现(这意味着用户必须为他/她的依赖项创建这个外观想要)。这在极少数情况下是合适的,但很可能这不是你的情况。

相同依赖项的不同版本通常也不适用 DI,因为交换版本可以在构建/组装时完成。


如果您想向用户公开一种能力,以提供他们自己的一些库“移动部件”的实现,那么首先您必须为所有这些移动部件定义严格的接口。换句话说,提供一组interfaces,您的用户必须对其进行扩展并注入到您的类中。

然后您创建由 Guice 模块组成的“绑定空间”,并在这些模块中声明对这些接口的要求:

public class SomeModule extends AbstractModule {
    @Override
    protected void configure() {
        requireBinding(SomeUserAPI.class);
        // Other bindings which probably use SomeUserAPI in implementations
    }
}

通过声明所需的绑定,您可以确保没有人能够混入您的模块,除非它们提供给定类的某种实现。当然,如果 Guice 找不到绑定,它无论如何都会失败,但是当您明确要求它时,您会获得更具体的错误消息,以及清晰的模块接口。

然后您为您的库创建特殊的“入口点”,其唯一职责是创建注入器并为用户提供您的类的实例。这个类接受来自用户的 Guice 模块并将其集成到注入器中。

public class Library {
    private final Injector injector;

    private Library(Module userModule) {
        // SomeModule and AnotherModule are modules defined in the library
        // and they are not the part of public interface of your library
        this.injector = Guice.createInjector(userModule, new SomeModule(), new AnotherModule());
    }

    public static Library create(Module userModule) {
        return new Library(userModule);
    }

    public MyAPIService myAPIService() {
        return injector.getInstance(MyAPIService.class);
    }
}

然后用户这样使用它:

Library library = Library.create(new AbstractModule() {
    @Override
    protected void configure() {
        // recall requireBinding(SomeUserAPI.class) statement we wrote previously,
        // here we are "implementing" it
        bind(SomeUserAPI.class).to(SomeUserAPIImpl.class);
        // other bindings for your exposed interfaces
    }
});
MyAPIService service = library.myAPIService();

在这种方法中,您允许用户使用 Guice DI 以一种简洁且可控的方式扩展您的库。

但是,您仍然必须向用户公开 Guice(因为用户必须实现 Module 接口)。我不认为你可以完全避免这种情况,除非你做一些奇怪的事情,比如

Library.create(SomeUserAPIImpl.class, SomeUserAPI2Impl.class, ...)

也就是说,接受表示扩展点实现的类对象(然后将它们绑定到某个内部模块中)。但我认为从库界面中删除 Guice 并不值得。

【讨论】:

  • 谢谢!但是,您是否会说,在构建模块/客户端时,最好将所有 DI/guice 保留在客户端中而不暴露任何内容?
  • 抱歉,我不确定我是否理解您的意思。程序不同部分之间的 DI 暴露程度很大程度上取决于您实际工作的内容。如果您的库非常简单,我认为您根本不需要 DI 容器。如果库很复杂,则取决于其预期用途。例如,如果它应该在一个已经基于 Guice 的系统中使用,那么只提供 Guice 模块就足够了。
【解决方案2】:
  1. 您可以执行不建议用于生产的模块覆盖。你可以在这里找到更多Overriding Binding in Guice
    1. 您可以使用@ImplementedBy 创建绑定,但显式绑定接口将覆盖该注释绑定。因此,如果可能的话,您将使用@ImplementeBy 创建您的框架,并且第 3 方开发人员将通过在其模块中显式绑定来覆盖它。查找更多https://code.google.com/p/google-guice/wiki/JustInTimeBindings
    2. 无论如何我都不熟悉这些方法。我建议创建一个摘要 ApiClient 并让第 3 方开发人员来实施开放点。也许你应该介绍一些annotations,比如@Client,它必须实现ClientApi。然后,您的模块将在类路径中搜索带有 @Client 注释的 ClientApi 的实现。假设注释将包含一个值@Client('apache-http'),您将引入一个配置属性“api-client”,默认实现将设置为default,如果您想使用不同的设置,则设置为apache-http。好吧,您应该考虑一下,因为错误的绑定很容易破坏您的 ApiClient 的完整性:)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多