[英]How to create recursive object graphs with Guice?
假設我有5個類A,B,C,D,E,它們都實現了一個公共接口X.B,C,D和E類中的每一個都有一個X類型的字段(所以它們可以看作是包裝類)。
創建哪些實例是在運行時確定的,因此我可以使用以下對象圖之一:
E -> D -> C -> B -> A
D -> B -> A
E -> A
A
(訂單是固定的,最里面的實例總是類型A,但是沒有限制。)
不,我想用Guice創建這個對象,以避免手工提供所有其他依賴項。
最簡單的方法是什么? 目前似乎我必須這樣做
有沒有更簡單的方法? 我是否可能以某種方式自動注冊X的子類的新實例作為每次創建X的綁定?
編輯:澄清“ 在運行時確定創建了哪些實例 ”:
我當前的代碼如下所示:
X createX() {
X x = new A(dependencies);
if (useB) x = new B(x, some, dependencies);
if (useC) x = new C(x, further, dependencies);
if (useD) x = new D(x, dependency);
if (useE) x = new E(x, yet, other, dependencies);
return x;
}
標志useB,useC,useD和useE的值來自屬性文件。
我的主要目標是手動保存為構造函數提供所有依賴項。
編輯:解決方案:
我添加了自己的解決方案,我在此期間找到了解決方案。 感謝所有的回答者!
改進我的解決方案的一種方法是可以刪除構造函數參數上的@InInstance
注釋。 我已經嘗試過類型監聽器,但我還沒有找到辦法。 提示是受歡迎的。
我已經為這個問題開發了以下解決方案:
首先,我創建一個標記注釋@InInstance
,它將Class
對象作為值。 我使用這個注釋來注釋所有包裝類中的類型X的參數(即B到E)。 例:
class B {
B(@InInstance(B.class) X x,
Other dependencies) {
...
}
}
現在,如果我想要一個完整鏈的實例(A到E),我綁定
然后調用createInstance(X.class)
。
這解決了讓Guice創建我的實例並提供依賴關系的問題。
現在針對根據屬性文件在運行時創建適當綁定的問題,我創建了一個Guice擴展,允許我創建“包裝器綁定”。 這樣的綁定需要一個類型(這里是X),一個包裝器實現類型(這里是B到E之一)和一個基本模塊,它需要包含X的綁定。然后它創建一個包含兩個綁定的新模塊:
annotatedWith(InInstance(wrapper type))
然后可以使用這樣的模塊來覆蓋基本模塊與Modules.override(base module).with(wrapper module)
的綁定。
使用一些不錯的Guice風格的EDSL,它看起來像:
Module baseModule = ... // contains binding for X to A
if (useB) {
Module wrapperModule = new ChainingModule() {
void configure() {
wrap(X.class).from(baseModule).with(B.class); // possible to use .in(...) etc. here
// In this case, this line is equivalent to
// bind(X.class).annotatedWith(InInstance(B.class)).to(A.class);
// bind(X.class).to(B.class);
// It has the advantage of automatically looking up the current binding
// for X in the base module, which makes it easy to chain this.
}
}
baseModule = Modules.override(baseModule).with(wrapperModule);
}
...
對於常見情況,有一些輔助方法,它看起來像:
Module module = ... // contains binding for X to A
if (useB) {
module = Chaining.wrap(X.class).from(module).with(B.class);
}
if (useC) {
module = Chaining.wrap(X.class).from(module).with(C.class);
}
if (useD) {
module = Chaining.wrap(X.class).from(module).with(D.class);
}
if (useE) {
module = Chaining.wrap(X.class).from(module).with(E.class);
}
Injector injector = Guice.createInjector(module);
X x = injector.createInstance(X.class);
每行創建適當的包裝器模塊並返回包含給定模塊的新模塊,其中一些綁定由包裝器模塊覆蓋。
“在運行時”增加了相當多的復雜性。 我假設這意味着你創建的每個 X實例,你對包裝A的對象鏈做出一些決定。
如果你在施工期間沒有將X注入E,D,C和B怎么樣? 而是添加setNext(X)方法。 我不確定這對你有多現實,但是聽我說。 您可以創建如下工廠:
class XFactory {
@Inject Provider<A> a;
@Inject Provider<B> b;
@Inject Provider<C> c;
@Inject Provider<D> d;
@Inject Provider<E> e;
public X create(Data state) {
// the 'state' object is just whatever information you use to
// decide what objects you need to create;
X result = a.get();
if(state.useB()) {
B head = b.get();
head.setNext(result);
result = head;
}
if(state.useC()) {
C head = c.get();
head.setNext(result);
result = head;
}
if(state.useD()) {
D head = d.get();
head.setNext(result);
result = head;
}
if(state.useE()) {
E head = e.get();
head.setNext(result);
result = head;
}
return result;
}
}
你可以考慮引入一個像DelegatingX或WrapperX這樣的接口來保存setNext()方法。 它可能會減少一些重復。
此外,如果“在運行時”實際上是應用程序啟動,因此X的每個實例應該是相同的對象鏈,那么您還有一個選項:
class MyModule extends AbstractModule {
public void configure() {
Multibinder<DelegatingX> chain
= Multibinder.newSetBinder(binder(), DelegatingX.class);
if(useB()) chain.addBinding().to(B.class);
if(useC()) chain.addBinding().to(C.class);
if(useD()) chain.addBinding().to(D.class);
if(useE()) chain.addBinding().to(E.class);
}
@Provides X provideX(Set<DelegatingX> chain, A a) {
X result = a;
// 'item' is B, then C...
for(X item : chain) {
item.setNext(result);
result = item;
}
return result; // this is E
}
}
我知道這個答案非常抽象,但我希望它能讓你更接近解決方案。
我還發現你的定義中的“運行時”部分不清楚。
但是,讓我們假設我們首先想象這些鏈是如何在沒有Guice的情況下制作的。
class chainHolder {
public final X chain1 = new E(new D(new C(new B(new A()))));
public final X chain2 = new D(new B(new A()));
public final X chain3 = new E(new A());
public final X chain4 = new A();
}
但是你的問題是,你想要注入的每個構造函數中還有很多其他東西要傳遞給你嗎? 好的,讓我們為這些設置一些注入:
//Pseudo multi-class def here
classes B...E extends X {
private final X nextInChain;
@Inject
public B...E(TheStuffINeedToo NotMentionedAbove, X nextInChain) {
super(TheStuffINeedToo);
this.nextInChain = nextInChain;
}
}
class A extends X {
@Inject
public A(TheStuffINeedToo NotMentionedAbove) {
super(TheStuffINeedToo);
}
}
class chainHolder {
public final X chain1;
public final X chain2;
public final X chain3;
public final X chain4;
@Inject
public chainHolder(@Chain1 X c1, @Chain2 X c2, @Chain3 X c3, @Chain4 X c4) {
chain1 = c1;
chain2 = c2;
chain3 = c3;
chain4 = c4;
}
}
class chainModlule extends AbstractModule {
public void configure() {
//The other stuff you wanted provided gets bound here.
bind(TheStuffINeedToo.class).to(TheStuffNotMentionedInTheQuestion.class);
//Maybe A should always be the same terminating instance.
bind(A).in(Singleton.class);
bind(X).annotatedWith(Chain4.class).to(A.class);
}
@Provides @Chain1
public X providesChain1X(@Chain1 Provider<E> e) {
return e.get();
}
@Provides @Chain1
public E providesChain1E(@Chain1 Provider<D> d, TheStuffINeedToo stuff) {
return new E(stuff, d.get());
}
@Provides @Chain1
public D providesChain1D(@Chain1 Provider<C> c, TheStuffINeedToo stuff) {
return new D(stuff, c.get());
}
@Provides @Chain1
public C providesChain1C(@Chain1 Provider<B> b, TheStuffINeedToo stuff) {
return new C(stuff, b.get());
}
@Provides @Chain1
public B providesChain1B(Provider<A> a, TheStuffINeedToo stuff) {
return new B(stuff, a.get());
}
// ... Similarly for chain2 then ...
@Provides @Chain3
public X providesChain3X(@Chain3 Provider<E> e) {
return e.get();
}
@Provides @Chain3
public E providesChain3E(Provider<A> a, TheStuffINeedToo stuff) {
return new E(stuff, a.get());
}
}
現在。 你看,這不是保存/很多/工作。 如果所有的類都采用了你想要注入的相同的東西(比如我的偽設置),那么你可以將整個鏈轉移到一個提供者並重用那個東西實例,或者提供東西。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.