在 Guice 中动态连接对象集合
Wire collection of objects dynamically in Guice
这里指导新手,场景比较复杂
我的公司有大量给定类型的常量(我们称它们为 Thingy
),它们属于不同的团队并在我们应用程序的不同部分进行维护。但是,我们需要有一个了解所有这些的中央注册表(我们称之为 ThingyService
)。我正在编写一个团队可以扩展或安装的基础模块,目的是允许团队注册他们的 Thingy
,并让他们访问 ThingyService
。该模块将 类 列表作为参数,我可以从中提取 Thingy
常量,这部分工作正常。
我不明白的是我如何才能 a) 让每个模块知道彼此模块的 Thingy
列表和 b) 我如何将我的 ThingyService
创建为一个单例包含我所有的 Thingy
。我已经尝试过共享静态状态和 ThreadLocals,但我总是破坏测试或破坏我的主要(播放)应用程序。以我对 Guice 的天真理解,我认为我需要 Thingy
的 MultiBinder
,但我不知道如何在模块之间共享它。这是我想做的事情:
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);
}
}
我会将此答案标记为已接受,直到其他人提供更地道的答案。
这里指导新手,场景比较复杂
我的公司有大量给定类型的常量(我们称它们为 Thingy
),它们属于不同的团队并在我们应用程序的不同部分进行维护。但是,我们需要有一个了解所有这些的中央注册表(我们称之为 ThingyService
)。我正在编写一个团队可以扩展或安装的基础模块,目的是允许团队注册他们的 Thingy
,并让他们访问 ThingyService
。该模块将 类 列表作为参数,我可以从中提取 Thingy
常量,这部分工作正常。
我不明白的是我如何才能 a) 让每个模块知道彼此模块的 Thingy
列表和 b) 我如何将我的 ThingyService
创建为一个单例包含我所有的 Thingy
。我已经尝试过共享静态状态和 ThreadLocals,但我总是破坏测试或破坏我的主要(播放)应用程序。以我对 Guice 的天真理解,我认为我需要 Thingy
的 MultiBinder
,但我不知道如何在模块之间共享它。这是我想做的事情:
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);
}
}
我会将此答案标记为已接受,直到其他人提供更地道的答案。