1. 概述

本文将深入探讨 Google 的依赖注入框架 Guice,重点分析 @Provides 注解与 Provider 类的核心区别及应用场景。作为 Java 开发者,理解这两种依赖注入方式的差异能帮助我们更优雅地管理对象创建逻辑。

2. Guice 基础知识

Google 为 Java 5+ 设计了轻量级依赖注入(DI)框架 Guice。虽然不如 Spring 功能全面,但 Guice 特性丰富,旨在简化依赖注入实现。

Guice 通过依赖注入模式将依赖传递到对象中,使用 Java 代码和注解定义对象关联,而非其他 DI 框架常用的配置文件。

Guice 的核心组件包括:

  1. 注解驱动:通过注解标记注入点
  2. 模块系统:定义绑定关系
  3. 绑定机制:连接接口与具体实现
  4. 注入器:负责创建和组装对象

与 Spring 类似,Guice 支持构造器注入、方法注入和字段/参数注入,通过 @Inject 注解标记。

绑定定义了 Guice 如何注入依赖,这些绑定在模块类中定义(需继承 AbstractModule)。

在 Maven 项目中使用 Guice,需添加以下依赖:

<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>7.0.0</version>
</dependency>

3. Guice 中的 Provider 类

Provider 类用于管理对象的创建过程,当实例化过程复杂或需要额外逻辑时特别有用——这些场景用常规依赖注入难以处理。

Provider 类需实现 com.google.inject.Provider 接口,该接口只有一个 T get() 方法,返回所需类型的实例。

3.1. 实现 Provider 接口

以通知场景为例:定义 Notifier 接口,支持邮件和短信两种实现:

public interface Notifier {
    void sendNotification(String message);
}

实现 EmailNotifier 类,并使其作为 Notifier 类型的 Provider:

public class EmailNotifier implements Notifier, Provider<Notifier> {

    private String smtpUrl;
    private String user;
    private String password;
    private EmailNotifier emailNotifier;

    @Override
    public Notifier get() {
        // 执行邮件通知器的初始化
        this.smtpUrl = "smtp://localhost:25";
        emailNotifier = new EmailNotifier();
        return emailNotifier;
    }

    @Override
    public void sendNotification(String message) {
        log.info("发送邮件通知: " + message);
    }
}

注意:我们实现了 com.google.inject.Provider 接口,并在 get() 方法中完成初始化。

3.2. 绑定 Provider 到类型

定义 Provider 后,需在模块类中绑定关联。创建模块类并覆盖 configure() 方法:

public class MyGuiceModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(Notifier.class).to(EmailNotifier.class);
    }
}

这会指示 Guice 始终调用 EmailNotifierget() 方法来注入 Notifier 依赖

验证依赖注入:

@Test
public void givenGuiceProvider_whenInjecting_thenShouldReturnEmailNotifier() {
    // 创建包含 NotifierModule 的 Guice 注入器
    Injector injector = Guice.createInjector(new NotifierModule());
    // 从注入器获取 Notifier 实例
    Notifier notifier = injector.getInstance(Notifier.class);
    // 断言实例类型
    assert notifier != null;
    assert notifier instanceof EmailNotifier;
}

⚠️ 踩坑提醒:当存在多个 Notifier 实现时,这种绑定方式会导致冲突。例如新增 PhoneNotifier 后,Guice 不允许同时绑定两个实现。

解决方案是使用 @Named 注解:Guice 的 @Named 注解通过唯一字符串标识区分同类型的多个绑定

多实现绑定示例:

@Override
protected void configure() {
    bind(Notifier.class).annotatedWith(Names.named("Email"))
      .toProvider(EmailNotifier.class);

    bind(Notifier.class).annotatedWith(Names.named("Phone"))
      .toProvider(PhoneNotifier.class);
}

在服务实现中通过名称指定具体实现:

public class MyService {
    private final Notifier emailNotifier;
    private final Notifier phoneNotifier;

    @Inject
    public MyService(@Named("Email") Notifier emailNotifier, @Named("Phone") Notifier phoneNotifier) {
        this.emailNotifier = emailNotifier;
        this.phoneNotifier = phoneNotifier;
    }
}

4. Guice 中的 @Provides 注解

@Provides 注解提供了比 Provider 类更简洁的替代方案。该注解用于标记模块类中作为绑定提供者的方法,并支持在方法参数中声明额外依赖。

4.1. @Provides 的实现

@Provides 注解必须定义在模块类的方法上,方法的返回类型即为绑定的类型。

Logger 接口为例:

public interface Logger {
    void log(String message);
}

使用 @Provides 注解替代 Provider 类:

@Provides
public Logger provideLogger() {
    return new Logger() {
        @Override
        public void log(String message) {
            log.info("记录日志: " + message);
        }
    };
}

关键点说明:

  1. 可直接在方法内创建实现类实例(这里用匿名类简化代码)
  2. 无需在模块类的 configure() 方法中显式声明绑定

验证依赖注入:

@Test
public void givenGuiceProvider_whenInjectingWithProvides_thenShouldReturnCustomLogger() {
    Injector injector = Guice.createInjector(new MyGuiceModule());
    Logger logger = injector.getInstance(Logger.class);
    assert logger != null;
    Assertions.assertNotNull(logger.log("Hello world"));
}

5. @Provides 与 Provider 的核心差异

对比维度 @Provides 注解 Provider 类
代码简洁性 ✅ 简单粗暴,适合一次性绑定场景 ❌ 需要更多样板代码
绑定声明 ✅ 无需显式绑定,注入器自动识别 ❌ 必须在模块中显式绑定
方法类型 ✅ 支持静态方法(更高效)或实例方法 ❌ 只能是实例方法
适用场景 ✅ 简单实例化逻辑 ✅ 复杂实例化逻辑
动态实例化 ❌ 不适合基于运行时条件创建不同实例 ✅ 适合运行时条件或第三方集成场景
延迟加载 ❌ 非天生支持延迟加载 ✅ 支持延迟实例化

6. 总结

本文深入分析了 Guice 中 Provider 类和 @Provides 注解的用法及差异。两种方式各有优势:

  • @Provides:适合简单场景,代码更简洁
  • Provider 类:适合复杂逻辑,支持动态实例化和延迟加载

实际开发中,根据实例化逻辑的复杂度和运行时需求选择合适方案。完整代码示例可在 GitHub 获取。


原始标题:@Provides vs Provider Classes in Guice | Baeldung