2012-04-04 6 views
8

Tôi nhận được lỗi sau đây trong mã của tôi lúc khởi động:Guice proxy để hỗ trợ tròn phụ thuộc

thử proxy com.bar.Foo để hỗ trợ một phụ thuộc vòng tròn, nhưng nó là không phải là một giao diện.

Proxy hoạt động chính xác như thế nào? Nếu tôi chỉ ném đủ các lớp sau giao diện, mọi thứ sẽ ổn chứ?

(Tôi biết rằng phụ thuộc vòng tròn thường có mùi mã, nhưng tôi nghĩ rằng trong trường hợp này đó là ok.)

Trả lời

6

Tôi mới đến khái niệm này, nhưng đây là sự hiểu biết của tôi.

Giả sử bạn có giao diện AB và triển khai AiBi.

Nếu Ai có một sự phụ thuộc vào B, và Bi có một sự phụ thuộc vào A, sau đó Guice có thể tạo ra một thực proxy của A (gọi nó là Ap) sẽ tại một số điểm trong tương lai được đưa ra một Ai để uỷ thác cho. Guice cho rằng Ap đến Bi cho sự phụ thuộc của nó trên A, cho phép Bi hoàn thành việc khởi tạo. Sau đó, kể từ khi Bi được khởi tạo, Guice có thể khởi tạo Ai với Bi. Sau đó, kể từ khi Ai hiện hữu ích, Guice yêu cầu Ap ủy quyền cho Ai.

Nếu AB không giao diện (và bạn chỉ có AiBi) này chỉ sẽ không được tốt, vì tạo Ap sẽ yêu cầu bạn mở rộng Ai, mà đã cần một Bi.

Đây là những gì nó có thể trông giống như với mã:

public interface A { 
    void doA(); 
} 

public interface B { 
    void doB(); 
} 

public class Ai implements A { 

    private final B b; 

    @Inject 
    public Ai(B b) { 
     this.b = b; 
    } 

    public void doA() { 
     b.doB(); 
    } 
} 

public class Bi implements B { 
    private final A a; 

    @Inject 
    public Bi(A a) { 
     this.a = a; 
    } 

    public void doB() { 
    } 
} 

Proxy lớp Guice làm sẽ trông như thế này:

public class Ap implements A { 
    private A delegate; 
    void setDelegate(A a) { 
     delegate = a; 
    } 

    public void doA() { 
     delegate.doA(); 
    } 
} 

Và tất cả sẽ được dây sử dụng ý tưởng cơ bản này:

Ap proxyA = new Ap(); 
B b = new B(proxyA); 
A a = new A(b); 
proxyA.setDelegate(a); 

Và đây là điều sẽ xảy ra nếu bạn chỉ có AiBi, không có giao diện AB.

public class Ap extends Ai { 
    private Ai delegate; 

    public Ap() { 
     super(_); //a B is required here, but we can't give one! 
    } 
} 

Nếu tôi chỉ ném đủ các lớp học đằng sau giao diện, tất cả mọi thứ sẽ ổn thôi?

Tôi đoán rằng có những hạn chế nghiêm ngặt về cách proxy có thể tương tác với trong hàm tạo. Nói cách khác, nếu B cố gắng gọi A trước khi Guice có cơ hội để điền proxy của A với A thực, thì tôi sẽ mong đợi một RuntimeException.

7

Trong khi cách tiếp cận "tiêm giao diện" là hoàn toàn hợp lệ và thậm chí có thể là giải pháp tốt hơn trong một số trường hợp, bạn có thể sử dụng giải pháp đơn giản hơn: Nhà cung cấp.

Đối với mỗi lớp "A" guice có thể quản lý, guice cũng cung cấp "Provider<A>". Đây là triển khai nội bộ của giao diện javax.inject.Provider, có thông báo get() sẽ "return injector.getInstance(A.class)". Bạn không phải tự mình thực hiện Giao diện, phần của nó là "ma thuật guice".

Vì vậy bạn có thể rút ngắn thời A-> B, BA ví dụ để:

public class CircularDepTest { 

static class A { 
    private final Provider<B> b; 
    private String name = "A"; 

    @Inject 
    public A(Provider<B> b) { 
     this.b = b; 
    } 
} 

static class B { 

    private final Provider<A> a; 
    private String name = "B"; 

    @Inject 
    public B(Provider<A> a) { 
     this.a = a; 

    } 
} 

@Inject 
A a; 

@Inject 
B b; 

@Before 
public void setUp() { 
    Guice.createInjector().injectMembers(this); 
} 


@Test 
public void testCircularInjection() throws Exception { 
    assertEquals("A", a.name); 
    assertEquals("B", a.b.get().name); 
    assertEquals("B", b.name); 
    assertEquals("A", b.a.get().name); 
}} 

tôi thích điều này, bởi vì nó dễ đọc hơn (bạn không bị lừa để tin rằng các nhà xây dựng đã nắm giữ một thể hiện của "B ") và vì bạn có thể tự mình triển khai Nhà cung cấp, nó sẽ vẫn hoạt động" bằng tay ", bên ngoài ngữ cảnh guice (để thử nghiệm ví dụ).

+0

Tôi cũng thích cách tiếp cận này. Nó có nghĩa là bạn không cần phải tạo giao diện khi bạn không cần chúng. Nó cũng nhanh hơn/ít có khả năng lộn xộn để thay đổi tiêm cho một nhà cung cấp hơn là tạo giao diện. – specialtrevor

+0

Làm cách nào để có sự phụ thuộc rõ ràng vào 'Guice' trong mã ứng dụng của bạn? Tôi đã luôn luôn nghĩ rằng nó là tốt đẹp để ở độc lập của DI framework. Trước khi bạn giới thiệu 'Nhà cung cấp' cho mã của bạn, bạn có thể đã chuyển sang nói' Spring' nhưng điều này là không thể được nữa. –

+0

Tôi đang nói về javax.inject.Provider , một giao diện chuẩn JSR330. Không yêu cầu phụ thuộc guice. –

2

Dưới đây là câu trả lời @ Jan-galinski của, redone trong Scala:

import javax.inject.Inject 
import com.google.inject.{Guice, Injector, Provider} 
import net.codingwell.scalaguice.InjectorExtensions._ 

/** Demonstrates the problem by failing with `Tried proxying CircularDep1$A to support a circular dependency, but it is not an interface. 
    while locating CircularDep1$A for parameter 0 at CircularDep1$B.<init>(CircularDep.scala:10) 
    while locating CircularDep1$B for parameter 0 at CircularDep1$A.<init>(CircularDep.scala:6) 
    while locating CircularDep1$A` */ 
object CircularDep1 extends App { 
    class A @Inject() (val b: B) { 
    val name = "A" 
    } 

    class B @Inject() (val a: A) { 
    val name = "B" 
    } 

    val injector: Injector = Guice.createInjector() 
    val a: A = injector.instance[A] 
    val b: B = injector.instance[B] 

    assert("A" == a.name) 
    assert("B" == a.b.name) 
    assert("B" == b.name) 
    assert("A" == b.a.name) 
    println("This program won't run!") 
} 

/** This version solves the problem by using `Provider`s */ 
object CircularDep2 extends App { 
    class A @Inject() (val b: Provider[B]) { 
    val name = "A" 
    } 

    class B @Inject() (val a: Provider[A]) { 
    val name = "B" 
    } 

    val injector: Injector = Guice.createInjector() 
    val a: A = injector.instance[A] 
    val b: B = injector.instance[B] 

    assert("A" == a.name) 
    assert("B" == a.b.get.name) 
    assert("B" == b.name) 
    assert("A" == b.a.get.name) 
    println("Yes, this program works!") 
}