You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java依赖注入:如何通过Provider获取子类或合并多个Provider

问题:如何合并多个子类Provider为单一父类Provider,或消除多个Provider?

你现在的场景是:抽象类Foo有三个子类FooA、FooB、FooC,都通过相同构造函数实现依赖注入,目前用javax.inject.Provider分别获取这三个子类的实例,想把这些Provider合并成一个Provider<Foo>,或者找到其他方式消除多个Provider的冗余。

刚好之前碰到过类似的场景,给你几个可行的方案参考:

方案1:用限定符+工厂类封装(最通用的方式)

直接用Provider<Foo>的话,DI容器没法知道该返回哪个子类实例,所以我们需要给每个子类打上限定标记,再用一个工厂类来统一管理这些Provider,这样代码里只需要注入工厂类就行。

步骤1:定义限定符注解

先写一个自定义的限定符,用来标记不同的Foo子类:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface FooType {
    String value(); // 用来区分不同子类,比如"A"、"B"、"C"
}

步骤2:给子类添加限定符

给每个Foo子类加上这个注解,明确标记类型:

@FooType("A")
public class FooA extends Foo {
    public FooA(Dependency requiredDep) { // 假设构造函数需要注入Dependency
        super(requiredDep);
    }
}

@FooType("B")
public class FooB extends Foo {
    public FooB(Dependency requiredDep) {
        super(requiredDep);
    }
}

// FooC同理,加上@FooType("C")

步骤3:实现Foo工厂类

这个工厂类会自动收集所有带@FooType标记的Foo子类Provider,然后提供统一的获取方法:

public class FooFactory {
    private final Map<String, Provider<Foo>> fooProviderMap;

    // 注入所有Foo类型的实例,通过DI容器的Instance API收集带限定符的Provider
    @Inject
    public FooFactory(@Any Instance<Foo> fooInstance) {
        this.fooProviderMap = fooInstance.select(new FooTypeLiteral())
                .stream()
                .collect(Collectors.toMap(
                        instance -> instance.getAnnotation(FooType.class).value(),
                        Instance::provider
                ));
    }

    // 自定义Literal类,用来匹配@FooType注解类型
    private static class FooTypeLiteral extends AnnotationLiteral<FooType> implements FooType {
        @Override
        public String value() {
            return ""; // 这里只需要匹配注解类型,实际值由子类的注解提供
        }
    }

    // 对外提供获取指定子类实例的方法
    public Foo getFoo(String type) {
        Provider<Foo> provider = fooProviderMap.get(type);
        if (provider == null) {
            throw new IllegalArgumentException("找不到对应的Foo类型:" + type);
        }
        return provider.get();
    }
}

之后你只需要在业务代码里注入FooFactory,通过fooFactory.getFoo("A")就能拿到FooA的实例,不用再维护多个Provider变量了。

方案2:直接注入Provider Map(依赖DI容器支持)

如果用的是Guice、CDI这类支持集合注入的容器,你可以直接注入一个Map来管理所有Provider,不用自己写工厂类:

以Guice为例:

先在模块里绑定子类并指定命名:

public class FooModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Foo.class).annotatedWith(Names.named("A")).to(FooA.class);
        bind(Foo.class).annotatedWith(Names.named("B")).to(FooB.class);
        bind(Foo.class).annotatedWith(Names.named("C")).to(FooC.class);
    }
}

然后直接注入Map<Named, Provider<Foo>>:

public class YourService {
    private final Map<Named, Provider<Foo>> fooProviders;

    @Inject
    public YourService(Map<Named, Provider<Foo>> fooProviders) {
        this.fooProviders = fooProviders;
    }

    public void doSomething() {
        // 获取FooA的实例
        Foo fooA = fooProviders.get(Names.named("A")).get();
        // 使用fooA...
    }
}

以CDI为例:

用@Named注解标记子类后,直接注入Map<String, Provider<Foo>>,key就是@Named的值:

@Named("A")
public class FooA extends Foo { /* ... */ }

// 注入时
@Inject
private Map<String, Provider<Foo>> fooProviders;

// 使用时
Foo fooA = fooProviders.get("A").get();

方案3:如果不需要动态获取,直接注入子类

如果你的业务代码里是固定需要某个子类,其实可以直接注入FooA、FooB本身,而不是用Provider——除非你需要延迟加载或者解决循环依赖问题。但如果确实需要Provider,那前面两种方案更合适。


内容的提问来源于stack exchange,提问作者Absent

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:35:17