使用guice进行客户端sdk /库设计模式的依赖注入

时间:2014-03-04 22:42:20

标签: java design-patterns dependency-injection guice

我正在为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对象呈现第三方用户?

2 个答案:

答案 0 :(得分:8)

  

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

对于DI来说,这是一个很好的理由。像HTTP客户端这样的外部依赖关系通常具有具体的接口,除了完全依赖之外,没有人实现。我个人无法想象你的程序是如何编写的,因为交换底层HTTP客户端不会影响其架构,也就是说,除非你为它提供你自己的门面

之类的东西
public interface HttpClient {
    HttpResponse send(HttpRequest request);
}

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

相同依赖关系的不同版本通常也不是DI的情况,因为交换版本可以在构建/组装时完成。


如果您希望为用户提供一些能力来提供他们自己的库“移动部件”的实现,那么首先必须为所有这些移动部件定义严格的接口。换句话说,提供一组interface s,您的用户必须扩展这些public class SomeModule extends AbstractModule { @Override protected void configure() { requireBinding(SomeUserAPI.class); // Other bindings which probably use SomeUserAPI in implementations } } ,并将其注入您的课程中。

然后您创建由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);
    }
}

通过声明所需的绑定,您确保没有人能够在您的模块中混合,除非它们提供给定类的某些实现。当然,如果找不到绑定,Guice会失败,但是当你明确要求它时,你会获得更具体的错误信息,以及模块的清晰界面。

然后为图书馆创建特殊的“入口点”,其唯一的责任是创建注入器并为用户提供类的实例。该类接受用户的Guice模块并将其集成到进样器中。

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();

然后用户就像这样使用它:

Module

在这种方法中,您允许用户以整齐且受控的方式使用Guice DI扩展您的库。

但是,您仍然需要向用户公开Guice(因为用户必须实现Library.create(SomeUserAPIImpl.class, SomeUserAPI2Impl.class, ...) 接口)。我不认为你可以完全避免这种情况,除非你做了像

那样奇怪的事情
{{1}}

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

答案 1 :(得分:1)

  1. 您可以执行模块覆盖,不建议用于生产。你可以在这里找到更多Overriding Binding in Guice
    1. 您可以使用@ImplementedBy创建绑定,但显式绑定接口将覆盖该注释绑定。因此,如果可能,您将使用@ImplementeBy创建框架,并且第3方开发人员将在其模块中显式绑定来覆盖它。查找更多https://code.google.com/p/google-guice/wiki/JustInTimeBindings
    2. 无论如何,我对这些方法都不熟悉。我建议创建一个抽象的ApiClient并让第3方开发者实现开放点。也许你应该介绍一些必须实现annotations的{​​{1}} @Client。然后,您的模块将在类路径中搜索具有ClientApi注释的ClientApi的实现。让我们说注释将包含一个值@Client,你将引入一个配置属性'api-client',如果你想使用某些东西,它将被设置为@Client('apache-http')用于默认实现或default不同。好吧,你应该考虑它,因为你的ApiClient的完整性很容易被错误的绑定破坏:)。