2012-03-30 12 views
20

Hier ist mein Problem:Custom Guice Scope oder ein besserer Ansatz?

Es ist zunächst wichtig zu wissen, dass ich eine Simulation schreibe. Dies ist eine eigenständige Anwendung und ist single-threaded. Ich habe im Wesentlichen zwei Klassen von Objekten, die unterschiedliche Scoping-Anforderungen haben.

  1. Klassen, die während der gesamten Simulation als Singletons verwendet werden sollen. Eine Instanz von Random als Beispiel.

  2. Gruppen von Klassen, die zusammen erstellt werden, und innerhalb der Gruppe sollte jede Instanz wie ein Singleton behandelt werden. Beispiel: RootObject ist die oberste Klassenklasse und hat eine Abhängigkeit von ClassA und ClassB, die beide eine Abhängigkeit von ClassD haben. Für jedes gegebene RootObject sollten seine beiden Abhängigkeiten (ClassA und ClassB) von derselben Instanz ClassD abhängen. Instanzen von ClassD sollten jedoch nicht für verschiedene Instanzen von RootObject freigegeben werden.

Hoffentlich macht das Sinn. Ich kann mir zwei Ansätze vorstellen. Eine besteht darin, alle injizierten Objekte als Singletons zu markieren, den Root-Injektor zu erstellen und jedes Mal, wenn ich eine neue RootObject-Instanz erstellen muss, einen Kind-Injektor auszugeben. Dann werden die Instanzen von RootObject und alle ihre Abhängigkeiten als Singletons erstellt, aber diese Scoping-Informationen werden beim nächsten Mal weggeworfen, wenn ich gehe, um einen anderen RootObject zu erstellen.

Der zweite Ansatz besteht darin, eine Art von benutzerdefinierten Bereich zu implementieren.

Die Guice-Dokumentation gibt widersprüchliche Ratschläge ... Auf der einen Seite heißt es, dass Sie einen einzigen Injektor haben sollten, und dass es idealerweise einmal aufgerufen wird, um eine Top-Level-Klasse zu erstellen. Auf der anderen Seite heißt es, sich von benutzerdefinierten Bereichen zu entfernen.

+0

Ich habe eine Idee, dass @Assisted Injektion hier verwendet werden könnte, aber ich habe Probleme genau zu sehen, wie es verwendet werden soll ... – Rich

+0

@ Josh: Wo kommt der Doku sagen Abstand halten von Scopes? –

+0

@ A.H., Http://code.google.com/p/google-guice/wiki/CustomScopes - erste Zeile. "Es wird im Allgemeinen empfohlen, dass Benutzer keine eigenen benutzerdefinierten Bereiche schreiben - die integrierten Bereiche sollten für die meisten Anwendungen ausreichen." Ich habe festgestellt, dass der erste Ansatz sehr gut funktioniert - Kinder injizieren nach Bedarf. Wir müssen nur darauf achten, dass die "Singletons pro Gruppe" nicht versehentlich in den Eltern-Injektor gebunden werden, aber das ist ziemlich einfach zu überprüfen. – Josh

Antwort

0

Darf ich fragen, warum brauchen Sie Singletons?

Ich würde nicht empfehlen, benutzerdefinierten Bereich zu erstellen. Die beste und einfachste Möglichkeit zum Mischen von Bereichen besteht darin, Provider anstelle von Objekten zu injizieren. Mit den Providern können Sie den Umfang Ihres Objekts aus Ihrer Business-Code-Logik heraus steuern.

Einzelheiten finden Sie unter Guice documentation.

+0

Ich glaube nicht, dass dies zutrifft. Wenn ich Request-Scoped-Objekte mit Singletons mixe, dann würde das sicher großartig funktionieren. Aber ich muss Singletons mit einem Bereich mischen, der noch nicht existiert. Es scheint, als wäre der einfachste Weg für uns, Kinder injizieren zu können. – Josh

+0

Um Ihre Singleton-Frage zu beantworten - wir haben zwei Ebenen von statusbehafteten Objekten - solche, deren Instanzen über die gesamte Simulation verteilt werden müssen (wie ein Zufallszahlengenerator) und andere, deren Instanzen innerhalb einer kleinen Unterkomponente des Simulation. Verschiedene Unterkomponenten werden dynamisch erstellt, und jede Unterkomponente benötigt eine eigene Instanz dieses Singletons pro Komponente; Andere Objekte innerhalb dieser Unterkomponente müssen jedoch auf dieselbe Instanz zugreifen. – Josh

3

Haben Sie überlegt, einen Anbieter zu verwenden? Es wäre leicht, einen zu schreiben, die Ihre Anforderungen erfüllt, zB:

import com.google.inject.Provider 

class RootObjectProvider implements Provider<RootObject> { 

    ... 

    @Override 
    RootObject get() { 
     ClassD d = new ClassD(....); 
     ClassB b = new ClassB(..., d, ...); 
     ClassC c = new ClassC(..., d, ...); // Note that b and c share d. 
     return new RootObject(b, c, ...); 
    } 
} 

Sie können die Anbieter zwei Arten verwenden:

  1. Bind es als Anbieter entweder mit dem @Provides Schnittstelle oder die .toProvider() Bindung Dekoration .
  2. Den Provider direkt einspeisen und aufrufen, um RootObject Instanzen nach Bedarf zu erstellen.

Ich hoffe, dass dies hilft.

+4

Hmm ... Erstellen von Instanzen mit 'new' ist die ursprüngliche Sünde in einem DI-Framework. Vor allem, wenn _alle_ dieser Klassen etwas mehr Zeug injiziert werden wollen. –

11

Es scheint mir, als ob Sie einen Bereich für jede Instanz von RootObject und alle seine Abhängigkeiten benötigen.

In Guice Sie einen benutzerdefinierten Bereich erstellen können, sagen @ObjectScoped, wie folgt aus:

@Target({ TYPE, METHOD }) 
@Retention(RUNTIME) 
@ScopeAnnotation 
public @interface ObjectScoped {} 

Sie nun nur RootObject, A, B und D in diesem Bereich:

@ObjectScoped 
public class RootObject { 

    private A a; 
    private B b; 

    @Inject 
    public RootObject(A a, B b) { 
     this.a = a; 
     this.b = b; 
    } 

    public A getA() { 
     return a; 
    } 

    public B getB() { 
     return b; 
    } 

} 

@ObjectScoped 
public class A { 

    private D d; 

    @Inject 
    public A(D d) { 
     this.d = d; 
    } 

    public D getD() { 
     return d; 
    } 
} 

// The same for B and D 

Jetzt Jede RootObject hat ihren eigenen Umfang. Sie können dies als eine einfache HashMap implementieren:

public class ObjectScope { 

    private Map<Key<?>,Object> store = new HashMap<Key<?>,Object>(); 

    @SuppressWarnings("unchecked") 
    public <T> T get(Key<T> key) { 
     return (T)store.get(key); 
    } 

    public <T> void set(Key<T> key, T instance) { 
     store.put(key, instance); 
    } 

} 

Um diese Bereiche mit Guice Integration benötigen Sie einen com.google.inject.Scope -Implementierung, die Sie die Bereiche und die entsprechende Verkabelung in Ihrem Module wechseln können.

public class GuiceObjectScope implements Scope { 

    // Make this a ThreadLocal for multithreading. 
    private ObjectScope current = null; 

    @Override 
    public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) { 
     return new Provider<T>() { 

      @Override 
      public T get() { 

       // Lookup instance 
       T instance = current.get(key); 
       if (instance==null) { 

        // Create instance 
        instance = unscoped.get(); 
        current.set(key, instance); 
       } 
       return instance; 

      } 
     }; 
    } 

    public void enter(ObjectScope scope) { 
     current = scope; 
    } 

    public void leave() { 
     current = null; 
    } 

} 

public class ExampleModule extends AbstractModule { 

    private GuiceObjectScope objectScope = new GuiceObjectScope(); 

    @Override 
    protected void configure() { 
     bindScope(ObjectScoped.class, objectScope); 
     // your bindings 
    } 

    public GuiceObjectScope getObjectScope() { 
     return objectScope; 
    } 

} 

Ihr Programm wie folgt initialisieren:

ExampleModule module = new ExampleModule(); 
Injector injector = Guice.createInjector(module); 
GuiceObjectScope objectScope = module.getObjectScope(); 

Erstellen Sie die erste Instanz von RootObject und seinen entsprechenden Umfang:

ObjectScope obj1 = new ObjectScope(); 
objectScope.enter(obj1); 
RootObject rootObject1 = injector.getInstance(RootObject.class); 
objectScope.leave(); 

einfach den Rahmen für eine zweite Gruppe von Objekten wechseln:

ObjectScope obj2 = new ObjectScope(); 
objectScope.enter(obj2); 
RootObject rootObject2 = injector.getInstance(RootObject.class); 
objectScope.leave(); 

Testen Sie, ob Ihre Anforderungen erfüllt werden:

assert rootObject1 != rootObject2; 
assert rootObject1.getA() != rootObject2.getA(); 
assert rootObject1.getA().getD() == rootObject1.getB().getD(); 
assert rootObject1.getA().getD() != rootObject2.getB().getD(); 

mit einer Gruppe von Objekten arbeiten nur in ihrem Umfang eingeben und den Injektor verwenden:

objectScope.enter(obj1); 
B b1 = injector.getInstance(B.class); 
objectScope.leave(); 
assert rootObject1.getB() == b1; 
+0

Ich weiß nicht, ob ein Bereich verlassen wird, nachdem die Instanz erstellt wurde. Etwas wie faule Instanziierung eines Providers Abhängigkeit kann fehlschlagen ... – BrunoJCM

4

Mit ein wenig Setup können Guice dualistischen liefern Scoping ohne benutzerdefinierten Bereich. Der äußere ist @Singleton, und der innere ist @RequestScoped, zur Verfügung gestellt von der servlet Erweiterung. Dies funktioniert auch, wenn Sie über etwas anderes als einen Java EE-Servlet-Container sprechen.

Verfügen Sie über einen einzigen Root-Level-Injektor für die Verarbeitung Ihrer Singletons. Achten Sie darauf, den Antrag Umfang Anmerkung in Ihrem Root-Level-Modul zu erklären, wie so:

public class RootModule extends AbstractModule { 
    @Override 
    protected void configure() { 
    // Tell guice about the request scope, so that we can use @RequestScoped 
    bindScope(RequestScoped.class, ServletScopes.REQUEST); 
    } 
} 

Wenn Sie einen Teilumfang eingeben möchten, können Sie dies tun:

private void scopeAndInject(final Object perRequestSeed) { 
    try { 
    ServletScopes.scopeRequest(new Callable<Void>() { 
     public Void call() { 
     Injector requestScoped = getRootInjector().createChildInjector(
      new AbstractModule() { 
      @Override 
      protected void configure() { 
       bind(Object.class).toInstance(perRequestSeed); 
      } 
      } 
     ); 

     requestScoped.get(Something.class); 

     return null; 
     } 
    }, new HashMap<Key<?>, Object>()).call(); 
    } catch (Exception e) { 
    throw new RuntimeException(e); 
    } 
} 

Was wir tun Hier wird ServletScopes.scopeRequest verwendet, um den anonymen Callable innerhalb eines neuen Anforderungsbereichs auszuführen. Die Callable erstellt dann einen Kind-Injektor und fügt eine neue Bindung für beliebige pro-Anfrage-Ausgangsobjekte hinzu.

Die Samen sind Objekte, die @RequestScoped Dinge benötigen würden, aber nicht von Guice allein erstellt werden könnten, wie Anfragen oder Iterations-IDs. Das neue HashMap, das als zweites Argument an scopeRequest übergeben wird, ist eine andere Möglichkeit, um Seeds in den neuen Bereich buchstäblich einzufügen. Ich bevorzuge das Submodul übrigens, seit dem bindings for the seeded values are always required sowieso.

Das Kind Injektor ist dann "in" der Anfrage Umfang und kann verwendet werden, um @RequestScoped Dinge zu bieten.

Siehe auch dies: How to use ServletScopes.scopeRequest() and ServletScopes.continueRequest()?

Verwandte Themen