1. 引言

Google GuiceSpring 是两个功能强大的依赖注入(Dependency Injection, DI)框架。两者都支持完整的依赖注入特性,但它们在配置和实现方式上有所不同。

本文将从配置、依赖注入类型支持、对象识别机制等方面,深入对比 Guice 与 Spring 的差异。

2. Maven 依赖配置

我们首先在 pom.xml 文件中添加 Guice 和 Spring 的依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.4.RELEASE</version>
</dependency>

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

你可以随时从 Maven Central 获取最新的 spring-contextguice 依赖。

3. 依赖注入配置方式对比

依赖注入是一种编程技术,用于降低类与类之间的耦合。接下来我们重点分析 Spring 和 Guice 在配置方式上的不同。

3.1. Spring 的依赖注入配置

Spring 使用配置类来定义依赖注入规则,该类需标注 @Configuration 注解。Spring 容器会将该类作为 Bean 定义的来源。

Spring 管理的类被称为 Spring Bean

Spring 使用 @Autowired 注解进行自动装配@Autowired 是 Spring 内置的核心注解之一,可作用于字段、setter 方法或构造函数。

此外,Spring 也支持 Java CDI 标准中的 @Inject 注解。

示例:自动注入字段

@Component
public class UserService {
    @Autowired
    private AccountService accountService;
}
@Component
public class AccountServiceImpl implements AccountService {
}

接着创建一个配置类:

@Configuration
@ComponentScan("com.baeldung.di.spring")
public class SpringMainConfig {
}

⚠️ 注意:@ComponentScan 告诉 Spring 扫描哪些包下的组件。

Spring 能够自动将 AccountServiceImpl 映射到 AccountService 接口,即使我们只标注了实现类。

然后创建应用上下文:

ApplicationContext context = new AnnotationConfigApplicationContext(SpringMainConfig.class);

测试获取 Bean:

UserService userService = context.getBean(UserService.class);
assertNotNull(userService.getAccountService());

3.2. Guice 的依赖绑定机制

Guice 使用 Module 来管理依赖绑定。Module 类需继承 AbstractModule 并重写 configure() 方法。

Guice 使用绑定(binding)代替 Spring 的 wiring 机制。绑定定义了依赖如何注入类中。

Guice 使用 @Inject 注解进行依赖注入,与 Spring 的 @Autowired 类似。

示例:

public class GuiceUserService {
    @Inject
    private AccountService accountService;
}

创建 Module:

public class GuiceModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(AccountService.class).to(AccountServiceImpl.class);
    }
}

⚠️ 注意:由于接口不能直接实例化,必须通过 bind(...).to(...) 明确指定实现类。

创建 Injector:

Injector injector = Guice.createInjector(new GuiceModule());

测试获取实例:

GuiceUserService guiceUserService = injector.getInstance(GuiceUserService.class);
assertNotNull(guiceUserService.getAccountService());

3.3. Spring 的 @Bean 注解

Spring 提供 @Bean 注解用于在方法级别注册 Bean。该方法返回值会被注册为 Bean。

示例:

@Bean 
public BookService bookServiceGenerator() {
    return new BookServiceImpl();
}

获取 Bean:

BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService);

3.4. Guice 的 @Provides 注解

Guice 提供 @Provides 注解作为 @Bean 的等价物,只能用于方法。

示例:

@Provides
public BookService bookServiceGenerator() {
    return new BookServiceImpl();
}

获取实例:

BookService bookService = injector.getInstance(BookService.class);
assertNotNull(bookService);

3.5. Spring 的组件扫描机制

Spring 使用 @ComponentScan 注解自动扫描指定包下的组件,并注册为 Bean。

该注解通常与 @Configuration 一起使用。

3.6. Guice 是否支持组件扫描?

Guice 默认不支持组件扫描,但可以通过第三方库(如 Governator)模拟实现。

3.7. Spring 的 Bean 命名冲突问题

Spring 通过名称识别 Bean(类似 Map<String, Object> 结构),因此不允许存在同名 Bean。

示例冲突:

@Bean
public BookService bookServiceGenerator() {
    return new BookServiceImpl();
}
@Bean
public AudioBookService bookServiceGenerator() {
    return new AudioBookServiceImpl();
}

⚠️ 上述代码会导致 AudioBookService 被覆盖,最终抛出异常:

org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'AudioBookService' available

解决方案

  • 使用不同方法名
  • 显式设置 @Bean(name = "xxx")

3.8. Guice 的类型冲突问题

Guice 使用类型作为 Key(类似 Map<Class<?>, Object>,因此不能为同一类型定义多个绑定。

示例:

bind(Person.class).toConstructor(Person.class.getConstructor());
bind(Person.class).toProvider(...);

⚠️ 会抛出异常:

com.google.inject.CreationException: A binding to Person was already configured

解决方案:移除重复绑定,或使用 Binding Annotations 区分。

3.9. Spring 中的可选依赖

Spring 支持可选依赖注入,可通过以下方式实现:

  • 使用 Optional<T> 类型
  • 设置 @Autowired(required = false)

示例:

@Autowired
private Optional<AuthorService> authorService;

或:

@Autowired(required = false)
private AuthorService authorService;

⚠️ 注意:使用可选依赖时要小心空指针。

3.10. Guice 中的可选依赖

✅ **Guice 同样支持 Optional<T>**。

示例:

@Inject
private Optional<Foo> foo;

或者使用 @Nullable

@Inject
@Nullable
private Foo foo;

⚠️ Guice 不支持 @Inject(required = false)

4. 依赖注入类型实现对比

4.1. 构造器注入(Spring)

Spring 支持构造器注入,使用 @Autowired 标注构造函数。

示例:

@Component
public class SpringPersonService {
    private PersonDao personDao;

    @Autowired
    public SpringPersonService(PersonDao personDao) {
        this.personDao = personDao;
    }
}

测试:

SpringPersonService personService = context.getBean(SpringPersonService.class);
assertNotNull(personService);

4.2. 构造器注入(Guice)

Guice 同样支持构造器注入,使用 @Inject

示例:

public class GuicePersonService {
    private PersonDao personDao;

    @Inject
    public GuicePersonService(PersonDao personDao) {
        this.personDao = personDao;
    }
}

测试:

GuicePersonService personService = injector.getInstance(GuicePersonService.class);
assertNotNull(personService);

4.3. Setter 注入(Spring)

Spring 支持 Setter 注入,使用 @Autowired 标注 setter 方法。

示例:

@Component
public class SpringPersonService {
    private PersonDao personDao;

    @Autowired
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }
}

测试:

SpringPersonService personService = context.getBean(SpringPersonService.class);
assertNotNull(personService.getPersonDao());

4.4. Setter 注入(Guice)

Guice 同样支持 Setter 注入

示例:

public class GuicePersonService {
    private PersonDao personDao;

    @Inject
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }
}

测试:

GuicePersonService personService = injector.getInstance(GuicePersonService.class);
assertNotNull(personService.getPersonDao());

4.5. 字段注入(Spring)

Spring 使用 @Autowired 标注字段实现字段注入

4.6. 字段注入(Guice)

Guice 使用 @Inject 标注字段实现字段注入

5. 总结

在这篇文章中,我们从多个维度对比了 Guice 和 Spring 在依赖注入方面的差异,包括:

  • 配置方式(Module vs Configuration)
  • 注入方式(@Inject vs @Autowired)
  • Bean 管理机制(类型 vs 名称)
  • 可选依赖处理
  • 各类注入方式(构造器、Setter、字段)

Spring 更适合大型项目,拥有完善的生态和自动扫描机制。

Guice 更轻量级,适合需要高性能和灵活绑定的场景。

选择哪个框架,取决于项目需求和团队偏好。


原始标题:Guice vs Spring - Dependency Injection | Baeldung