在 Guice 中动态连接对象集合

Wire collection of objects dynamically in Guice

这里指导新手,场景比较复杂

我的公司有大量给定类型的常量(我们称它们为 Thingy),它们属于不同的团队并在我们应用程序的不同部分进行维护。但是,我们需要有一个了解所有这些的中央注册表(我们称之为 ThingyService)。我正在编写一个团队可以扩展或安装的基础模块,目的是允许团队注册他们的 Thingy,并让他们访问 ThingyService。该模块将 类 列表作为参数,我可以从中提取 Thingy 常量,这部分工作正常。

我不明白的是我如何才能 a) 让每个模块知道彼此模块的 Thingy 列表和 b) 我如何将我的 ThingyService 创建为一个单例包含我所有的 Thingy。我已经尝试过共享静态状态和 ThreadLocals,但我总是破坏测试或破坏我的主要(播放)应用程序。以我对 Guice 的天真理解,我认为我需要 ThingyMultiBinder,但我不知道如何在模块之间共享它。这是我想做的事情:

class ThingyModule extends AbstractModule{
   final Set<Class<?>> myThingyClasses; // this is populated in the constructor

   private Set<Thingy> extractThingiesFromThingyClasses(){
      // I have this working
   }

   @Provides @Singleton ThingyService thingyService(
       Set<Thingy> thingys // all thingys, from all such modules
   ){
       return new ThingyService(thingys);
   }

   protected void configure(){
       extractThingiesFromThingyClasses().forEach(thingy->
           // bind thingy to a global MultiBinder?
       );
   }
}

如何使我的 ThingyService 具有整个应用程序中的所有 Thingy 唯一性和全局性?注意:我不一定需要我的 Thingy 由 Guice 管理,我唯一需要它们的地方是 ThingyService。此外,如果有区别的话,这是一个 play / scala 应用程序,但我的 ThingyModule 代码存在于用 Java.

编写的库中

原来我漏掉了一个重要的细节,Thingy有一个类型参数,实际上是Thingy<T>,所以之前没有生效。通过作弊并将 Thingy 注册为原始类型,然后将其注入原始类型,我让它工作了。

这是一个使用 JUnit 5 和 AssertJ 的完整工作示例:

class ThingyModuleTest {

    static class Thingy<T>{
        private final T value;
        Thingy(final T value) {this.value = value;}
        @Override public boolean equals(final Object o) {
            if (this == o) { return true; }
            if (o == null || getClass() != o.getClass()) { return false; }
            final Thingy<?> thingy = (Thingy<?>) o; return Objects.equals(value, thingy.value); }
        @Override public int hashCode() { return Objects.hash(value); }
    }

    @Singleton
    static class ThingyService{
        final Set<Thingy<?>> thingies;
        @SuppressWarnings({"unchecked", "rawtypes"}) @Inject
        ThingyService(Set<Thingy> thingies) {
            this.thingies = ImmutableSet.copyOf((Set)thingies);
        }
        public Set<Thingy<?>> getThingies() { return thingies; }
    }

    abstract static class ThingyModule extends AbstractModule {
        private final Set<Class<?>> classesToScan;
        public ThingyModule(Class<?>... classes) {
            this.classesToScan = ImmutableSet.copyOf(classes);
        }

        private Set<Thingy<?>> scanForThingies(){
                return classesToScan.stream()
                         .flatMap(c-> Arrays.stream(c.getDeclaredFields()))
                         .filter(f->f.getType().isAssignableFrom(Thingy.class))
                         .filter(f-> Modifier.isStatic(f.getModifiers())&&Modifier.isFinal(f.getModifiers()))
                         .map(this::readThingy)
                         .filter(Optional::isPresent)
                         .map(Optional::get)
                         .collect(Collectors.toSet());
        }

        @SuppressWarnings("unchecked")
        private Optional<Thingy<?>> readThingy(final Field field) {
            try{
                field.setAccessible(true);
                return Optional.ofNullable(field.get(null))
                    .filter(Thingy.class::isInstance)
                    .map(Thingy.class::cast);
            } catch (IllegalAccessException e) { return Optional.empty(); }
        }

        @Override protected void configure() {
            bind(ThingyService.class);
            @SuppressWarnings("rawtypes") Multibinder<Thingy> multibinder = Multibinder.newSetBinder(binder(), Thingy.class);
            scanForThingies().forEach(thingy -> multibinder.addBinding().toInstance(thingy));
        }
    }

    static class ThingyModule1 extends ThingyModule {
        public ThingyModule1() { super(Thingies1.class); }
        static class Thingies1{
            static final Thingy<Boolean> BooleanThingy = new Thingy<>(true);
            static final Thingy<Integer> IntThingy = new Thingy<>(123);
        }
    }

    static class ThingyModule2 extends ThingyModule {
        public ThingyModule2() { super(Thingies2.class); }
        static class Thingies2{
            static final Thingy<String> StringThingy = new Thingy<>("hello");
            static final Thingy<Long> LongThingy = new Thingy<>(123L);
        }
    }

    @Test void validateThingyService() {
        ThingyService thingyService = Guice.createInjector(new ThingyModule1(), new ThingyModule2())
                                           .getProvider(ThingyService.class)
                                           .get();
        assertThat(thingyService).isNotNull()
                                 .extracting(ts -> ImmutableList.copyOf(ts.getThingies()))
                                 .asList()
                                 .containsExactlyInAnyOrder(BooleanThingy, IntThingy, StringThingy, LongThingy);
    }
}

我会将此答案标记为已接受,直到其他人提供更地道的答案。