1. 引言
Google Guice 和 Spring 是两个功能强大的依赖注入(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-context 或 guice 依赖。
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 更轻量级,适合需要高性能和灵活绑定的场景。
选择哪个框架,取决于项目需求和团队偏好。