本文解析为何在 guice 中使用 @singleton + @provides 方法仍被多次调用,并指出错误初始化模式(如静态 injector 重复创建、构造函数内日志误判)导致的伪“多实例”问题,给出线程安全、符合 di 原则的修复方案。
本文解析为何在 guice 中使用 @singleton + @provides 方法仍被多次调用,并指出错误初始化模式(如静态 injector 重复创建、构造函数内日志误判)导致的伪“多实例”问题,给出线程安全、符合 di 原则的修复方案。
问题核心并非 Guice 的 @Singleton 失效,而是代码逻辑违背了依赖注入的基本原则:将外部状态管理(如静态 Injector 初始化)耦合在构造函数中,且未做并发控制与幂等校验。
在您提供的 Library 类中,每次调用 initializeLib() 都会新建一个 Library 实例,进而反复执行构造函数:
private Library(SomeClass someclass) {
log.info("Bean getting created"); // ❌ 错误:这不是 Bean 创建,而是 Injector 初始化!
if (Objects.nonNull(injector)) { // ❌ 逻辑错误:应为 if (injector == null)
injector = createInjector(new LibraryModule()); // ❌ 每次都覆盖 injector,且条件反了
}
}
此处存在三处关键缺陷:
- 条件判断逻辑错误:if (Objects.nonNull(injector)) 表示“如果 injector 已存在,则重新创建”,这与单例目标完全相悖;正确应为 if (injector == null);
- 缺乏线程安全:多线程环境下,多个线程可能同时进入 injector == null 分支,导致多次 createInjector() 调用;
- 日志误导性:"Bean getting created" 实际记录的是 Injector 初始化动作,而非 SomeExposedComponent 实例化——而后者才受 @Singleton 约束,且仅在首次 getInstance() 时真正创建。
此外,@Provides @Singleton 方法本身是 Guice 的“提供者方法”,其返回值由 Guice 容器管理生命周期。但您在该方法中主动调用 Library.initializeLib(...),等于绕过 Guice 的绑定机制,使 @Singleton 形同虚设——因为每次调用 getSomeExposedComponent() 都会触发一次 initializeLib(),进而触发一次 new Library(...) 和潜在的 Injector 重建。
✅ 正确做法:将 Library 的初始化委托给 Guice,而非手动管理静态 Injector
推荐方案如下(兼容 Spring/Guice 双环境):
// 1. 将 Library 封装为 Guice Module(推荐)
public class LibraryModule extends AbstractModule {
private final SomeClass someClass;
public LibraryModule(SomeClass someClass) {
this.someClass = someClass;
}
@Override
protected void configure(Binder binder) {
binder.bind(SomeClass.class).toInstance(someClass);
// 其他绑定...
}
}
// 2. 在 ServiceModule 中声明依赖并复用 Injector
public class ServiceModule extends AbstractModule {
private final SomeClass someClass;
public ServiceModule(SomeClass someClass) {
this.someClass = someClass;
}
@Override
protected void configure(Binder binder) {
// 直接绑定 Library 所需依赖
binder.bind(SomeClass.class).toInstance(someClass);
}
@Provides
@Singleton
public SomeExposedComponent provideSomeExposedComponent(Injector injector) {
// ✅ 利用 Guice 自身 Injector 获取单例实例(确保 Singleton 语义)
return injector.getInstance(SomeExposedComponent.class);
}
}
若必须保留 Library.initializeLib() 作为对外 API(例如供 Spring 服务调用),则应彻底重构为惰性、线程安全、幂等的初始化:
public final class Library {
private static volatile Injector injector;
private static final Object LOCK = new Object();
public static SomeExposedComponent initializeLib(SomeClass someClass) {
// 双重检查锁定,确保 injector 仅创建一次
if (injector == null) {
synchronized (LOCK) {
if (injector == null) {
injector = Guice.createInjector(new LibraryModule(someClass));
// ✅ 日志放在这里更准确:Injector 初始化完成
log.info("Guice Injector initialized for Library");
}
}
}
return injector.getInstance(SomeExposedComponent.class); // ✅ Guice 保证 @Singleton 实例唯一
}
}
⚠️ 注意事项:
- 不要在 @Provides 方法中调用 Library.initializeLib() —— 这破坏了 DI 容器的控制流;
- @Singleton 作用域仅对 Guice 管理的绑定生效,手动 new 或外部调用不受约束;
- 若需跨框架集成(如 Spring + Guice),建议通过 SpringBridgeModule 或适配器模式桥接,而非混合静态状态与 DI 生命周期。
总结:@Singleton 从未失效,失效的是初始化逻辑。修复的关键在于——让 Injector 的创建真正单例化,让组件的获取完全交由 Guice 托管,消除手动 new 和重复赋值。











