如何在Google Guice中动态为Bean注入不同接口实现类?
这个需求在Guice里其实有几种很常用的实现方式,我给你拆解一下,你可以根据自己的场景选最合适的:
方案1:用绑定注解区分不同实现(适合同一上下文需同时支持两种场景)
如果你的代码需要在同一个Injector里同时拿到依赖A和依赖B的MyClass实例,用@Named或者自定义注解来标记绑定是最直接的方式。
第一步:配置Guice模块
先在模块里把MyInterface的两个实现分别绑定到不同的注解,再给MyClass创建对应的Provider或者直接绑定带注解的实例:
public class MyModule extends AbstractModule { @Override protected void configure() { // 给两个MyInterface实现打上命名标记 bind(MyInterface.class).annotatedWith(Names.named("versionA")).to(MyOtherClassA.class); bind(MyInterface.class).annotatedWith(Names.named("versionB")).to(MyOtherClassB.class); // 绑定两个不同的MyClass实例,分别对应各自的依赖 bind(MyClass.class).annotatedWith(Names.named("withA")).toProvider(MyClassWithAProvider.class); bind(MyClass.class).annotatedWith(Names.named("withB")).toProvider(MyClassWithBProvider.class); } // 自定义Provider,负责创建依赖A的MyClass static class MyClassWithAProvider implements Provider<MyClass> { private final MyInterface myInterface; @Inject public MyClassWithAProvider(@Named("versionA") MyInterface myInterface) { this.myInterface = myInterface; } @Override public MyClass get() { return new MyClass(myInterface); } } // 同理,创建依赖B的Provider static class MyClassWithBProvider implements Provider<MyClass> { private final MyInterface myInterface; @Inject public MyClassWithBProvider(@Named("versionB") MyInterface myInterface) { this.myInterface = myInterface; } @Override public MyClass get() { return new MyClass(myInterface); } } }
第二步:在代码中获取对应实例
需要用A的地方:
Injector injector = Guice.createInjector(new MyModule()); MyClass myClassWithA = injector.getInstance(Key.get(MyClass.class, Names.named("withA")));
需要用B的地方:
MyClass myClassWithB = injector.getInstance(Key.get(MyClass.class, Names.named("withB")));
如果觉得@Named的字符串不够语义化,你还可以自定义专属注解,比如:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) @BindingAnnotation public @interface UseVersionA {} // 再创建一个@UseVersionB注解
之后把代码里的@Named("versionA")替换成@UseVersionA就行,可读性会更好。
方案2:用子Injector覆盖绑定(适合场景互斥的情况)
如果你的两种场景是互斥的(同一时间只需要用A或者只需要用B),用子Injector会更简洁——它基于父Injector的配置,只覆盖你需要切换的绑定,不需要修改MyClass的代码。
第一步:创建基础模块
先配置好所有通用的依赖(除了MyInterface之外的其他绑定):
public class BaseModule extends AbstractModule { @Override protected void configure() { // 这里放所有通用的绑定,比如其他服务、工具类等 } }
第二步:在不同代码路径创建子Injector
需要用A的代码路径:
// 先创建基础Injector Injector baseInjector = Guice.createInjector(new BaseModule()); // 创建子Injector,覆盖MyInterface的绑定为A Injector injectorWithA = baseInjector.createChildInjector(new AbstractModule() { @Override protected void configure() { bind(MyInterface.class).to(MyOtherClassA.class); } }); // 从子Injector拿MyClass,它会自动注入A MyClass myClassWithA = injectorWithA.getInstance(MyClass.class);
需要用B的代码路径:
// 同样基于基础Injector创建子Injector,覆盖为B Injector injectorWithB = baseInjector.createChildInjector(new AbstractModule() { @Override protected void configure() { bind(MyInterface.class).to(MyOtherClassB.class); } }); MyClass myClassWithB = injectorWithB.getInstance(MyClass.class);
这种方式的好处是完全不需要改动MyClass的构造函数,只需要在不同路径下创建不同的子Injector即可,非常干净。
方案3:用私有模块封装绑定(适合需要隔离实现细节的场景)
如果你想把两种实现的绑定逻辑完全隔离,避免污染全局Injector,可以用PrivateModule来封装:
public class MyModule extends AbstractModule { @Override protected void configure() { // 封装依赖A的绑定,只暴露带@UseVersionA的MyClass install(new PrivateModule() { @Override protected void configure() { bind(MyInterface.class).to(MyOtherClassA.class); bind(MyClass.class).annotatedWith(UseVersionA.class).to(MyClass.class); // 必须显式暴露需要外部访问的类 expose(MyClass.class).annotatedWith(UseVersionA.class); } }); // 同理封装依赖B的绑定 install(new PrivateModule() { @Override protected void configure() { bind(MyInterface.class).to(MyOtherClassB.class); bind(MyClass.class).annotatedWith(UseVersionB.class).to(MyClass.class); expose(MyClass.class).annotatedWith(UseVersionB.class); } }); } }
获取实例的方式和方案1一样,通过带注解的Key来获取:
MyClass myClassWithA = injector.getInstance(Key.get(MyClass.class, UseVersionA.class));
小总结
- 如果需要在同一个上下文里同时用两种MyClass实例:选方案1或方案3
- 如果两种场景完全互斥,同一时间只需要一种:选方案2,代码最简洁
- 尽量不要动态修改全局Injector的绑定,Guice更偏向静态配置,子Injector是更安全的动态切换方式
内容的提问来源于stack exchange,提问作者More Than Five
相关产品推荐
相关产品推荐

