Guice 是 Google 推出的一个轻量级的依赖注入框架(dependency injection),可以帮助我们管理 Java 应用程序中的对象依赖关系。如果我们使用 Spring Framework 那可以直接使用 Spring 提供的 DI,但如果是使用 [[Javalin]] 或者自己的小型项目,但是也想使用灵活的 DI,但又不想要引用庞大的 Spring,就可以考虑 Guice。

Guice 根据’Apache 许可证版本 2’(Apache License version 2)发布,允许任何人免费使用、修改和重新发布,用于商业和非商业用途。

Guice 的特性

最著名的依赖注入的实现是 Spring 框架。在最初开发时,Spring 框架是一个实现 DI 和 AOP 的轻量级框架,发展到今天,Spring 已经是一个具有众多功能的庞大框架。 除了 Spring 框架,DI 也可以通过 Seasar2 和 Java EE 来实现,但这两个框架都非常复杂,不能称为轻量级框架。 如果目标是只实现 DI,可以考虑选择 Guice,它是轻量级的。

特点

  • 轻量,简单
  • 基于注释
  • 可与 Struts 和 Spring 集成
  • JSR 330 参考实现

和工厂模式将客户端和实现类解耦类似,依赖注入是一种设计模式,将行为和依赖解析分离。

在 Guice 的说明文档中有一个非常清晰的说明,为什么我们需要依赖注入。

Guice 核心概念

@Inject 注解的 Java 构造函数可以为 Guice 调用,称为「构造函数注入」

class Greeter {
  private final String message;
  private final int count;

  // Greeter declares that it needs a string message and an integer
  // representing the number of time the message to be printed.
  // The @Inject annotation marks this constructor as eligible to be used by
  // Guice.
  @Inject
  Greeter(@Message String message, @Count int count) {
    this.message = message;
    this.count = count;
  }

  void sayHello() {
    for (int i=0; i < count; i++) {
      System.out.println(message);
    }
  }
}

例子中,Greeter 类有一个构造函数,当应用程序请求 Guice 创建 Greeter 实例时,构造函数会被调用,Guice 将创建这两个所需参数,然后调用构造函数。应用通过 Module 来告诉 Guice 如何满足这些依赖。

通过 Module 来定义依赖

/**
 * Guice module that provides bindings for message and count used in
 * {@link Greeter}.
 */
import com.google.inject.Provides;

class DemoModule extends AbstractModule {
  @Provides
  @Count
  static Integer provideCount() {
    return 3;
  }

  @Provides
  @Message
  static String provideMessage() {
    return "hello world";
  }
}

@Provides 注解来指定依赖项。实际应用中,对象的依赖图会更加复杂,Guice 会自动创建所有传递依赖关系。

Guice injectors 注入器

创建一个 Guice Injector 一个或者多个模块。

在 main 函数或者对应初始化的方法中。

  public static void main(String[] args) {
    // Creates an injector that has all the necessary dependencies needed to
    // build a functional server.
    Injector injector = Guice.createInjector(
        new RequestLoggingModule(),
        new RequestHandlerModule(),
        new AuthenticationModule(),
        new DatabaseModule(),
        ...);
    // Bootstrap the application by creating an instance of the server then
    // start the server to handle incoming requests.
    injector.getInstance(MyWebServer.class)
        .start();
  }

Guice DSL syntax

  • bind(key).toInstance(value)
  • bind(key).toProvider(provider)

使用

@Named 注解是 Guice 中的一种绑定注解,用于标记一个依赖项的名称,以便在注入时进行区分。

使用 @Named 注解时,需要在注入时指定相应的名称,例如:

public class MyService {
  private final String message;

  @Inject
  public MyService(@Named("message") String message) {
    this.message = message;
  }
}

在这个例子中,我们使用 @Named 注解标记了一个名为”message”的依赖项。在注入时,我们可以使用 @Named 注解指定相应的名称,例如:

public class MyAppModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(String.class)
        .annotatedWith(Names.named("message"))
        .toInstance("Hello, world!");
  }
}

在这个例子中,我们使用 bind 方法将一个字符串”Hello, world!”绑定到名称为”message”的依赖项上。在注入时,我们可以使用@Named 注解指定相应的名称,例如:

public class MyApp {
  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new MyAppModule());
    MyService myService = injector.getInstance(MyService.class);
    System.out.println(myService.getMessage()); // 输出 "Hello, world!"
  }
}

在这个例子中,我们通过 Guice 创建了一个 Injector 对象,并使用 getInstance 方法获取了一个 MyService 对象。在 MyService 的构造函数中,我们使用 @Named 注解指定了名称为”message”的依赖项,Guice 会自动将名称为”message”的依赖项注入到构造函数中。最后,我们调用 myService.getMessage() 方法输出了”Hello, world!”。

Multibindings

Guice 中的 MultiBinder 是一个多实例绑定。一个类型(Type) 有多个 Set<TypeImpl> 的实现。

比如可以定义一个接口,可能有很多个实现。

interface UriSummarizer {
  /**
   * Returns a short summary of the URI, or null if this summarizer doesn't
   * know how to summarize the URI.
   */
  String summarize(URI uri);
}

比如有一个实现

class FlickrPhotoSummarizer implements UriSummarizer {
  private static final Pattern PHOTO_PATTERN
      = Pattern.compile("http://www\\.flickr\\.com/photos/[^/]+/(\\d+)/");

  public String summarize(URI uri) {
    Matcher matcher = PHOTO_PATTERN.matcher(uri.toString());
    if (!matcher.matches()) {
      return null;
    } else {
      String id = matcher.group(1);
      Photo photo = lookupPhoto(id);
      return photo.getTitle();
    }
  }
}

通过 Module 来注册绑定关系。

public class FlickrPluginModule extends AbstractModule {
  public void configure() {
    Multibinder<UriSummarizer> uriBinder = Multibinder.newSetBinder(binder(), UriSummarizer.class);
    uriBinder.addBinding().to(FlickrPhotoSummarizer.class);

    ... // bind plugin dependencies, such as our Flickr API key
  }
}

最后注册 Module

public class PrettyTweets {
  public static void main(String[] args) {
    Injector injector = Guice.createInjector(
        new GoogleMapsPluginModule(),
        new BitlyPluginModule(),
        new FlickrPluginModule()
        ...
    );

    injector.getInstance(Frontend.class).start();
  }
}