23 Stimmen

Benutzerdefinierter Guice-Bereich oder ein besseres Konzept?

Hier ist mein Problem:

Es ist zunächst wichtig zu wissen, dass ich eine Simulation schreibe. Dies ist eine eigenständige Anwendung und läuft in einem Thread. Ich habe im Wesentlichen zwei Klassen von Objekten, die unterschiedliche Scoped-Anforderungen haben.

  1. Klassen, die als Singleton in der gesamten Simulation verwendet werden sollten. Ein Beispiel wäre eine Instanz von Random.

  2. Gruppen von Klassen, die zusammen erstellt werden und innerhalb der Gruppe sollte jede Instanz wie ein Singleton behandelt werden. Zum Beispiel hat die Klasse RootObject eine Abhängigkeit von ClassA und ClassB, die beide wiederum von ClassD abhängen. Für ein gegebenes RootObject sollten beide Abhängigkeiten (ClassA und ClassB) von derselben Instanz von ClassD abhängen. Instanzen von ClassD sollten jedoch nicht über verschiedene Instanzen von RootObject geteilt werden.

Hoffentlich macht das Sinn. Mir fallen zwei Ansätze dafür ein. Einer ist, alle injizierten Objekte als Singletons zu kennzeichnen, den Wurzel-Injektor zu erstellen und jedes Mal, wenn ich eine neue RootObject-Instanz erstellen muss, einen Kind-Injektor zu erstellen. Dann werden die Instanzen von RootObject und all seine Abhängigkeiten als Singletons erstellt, aber diese Scoped-Informationen werden beim nächsten Mal, wenn ich ein weiteres RootObject erstelle, verworfen.

Der zweite Ansatz besteht darin, eine Art benutzerdefiniertes Scope zu implementieren.

Die Guice-Dokumentation gibt widersprüchliche Ratschläge... Einerseits heißt es, dass man einen einzelnen Injektor haben sollte, der idealerweise einmal aufgerufen wird, um eine Top-Level-Klasse zu erstellen. Andererseits wird davon abgeraten, benutzerdefinierte Scopes zu verwenden.

14voto

electrodraco Punkte 323

Es scheint mir, dass Sie einen Scope für jede Instanz von RootObject und all seine Abhängigkeiten benötigen.

In Guice können Sie einen benutzerdefinierten Scope erstellen, sagen wir @ObjectScoped, so:

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

Platzieren Sie nun einfach RootObject, A, B and D in diesem Scope:

@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;
    }
}

// Dasselbe gilt für B und D

Jetzt hat jeder RootObject seinen eigenen Scope. Dies können Sie als einfache HashMap implementieren:

public class ObjectScope {

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

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

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

}

Um diese Scopes mit Guice zu integrieren, benötigen Sie eine com.google.inject.Scope-Implementierung, die es Ihnen ermöglicht, die Scopes und das entsprechende Mapping in Ihrem Module zu ändern.

public class GuiceObjectScope implements Scope {

    // Für Multithreading ThreadLocal machen.
    private ObjectScope current = null;

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

            @Override
            public T get() {

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

                    // Instanz erstellen
                    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);
        // Ihre Bindungen
    }

    public GuiceObjectScope getObjectScope() {
        return objectScope;
    }

}

Initialisieren Sie Ihr Programm wie folgt:

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

Erstellen Sie die erste Instanz von RootObject und den entsprechenden Scope:

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

Wechseln Sie einfach den Scope für eine zweite Gruppe von Objekten:

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

Überprüfen Sie, ob Ihre Anforderungen erfüllt sind:

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

Um mit einer Gruppe von Objekten zu arbeiten, geben Sie einfach ihren Scope ein und verwenden Sie den Injector:

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

5voto

phs Punkte 10378

Mit ein wenig Einrichtung kann Guice zweistufiges Scoping ohne einen benutzerdefinierten Scope bereitstellen. Der äußere ist @Singleton und der innere ist @RequestScoped, bereitgestellt von der servlet Erweiterung. Dies funktioniert auch, wenn es um etwas anderes als einen Java EE Servlet-Container geht.

Haben Sie einen einzigen Wurzel-Injector, um Ihre Singletons zu verwalten. Stellen Sie sicher, dass die Request-Scope-Annotation in Ihrem Wurzel-Modul wie folgt deklariert ist:

public class RootModule extends AbstractModule {
  @Override
  protected void configure() {
    // Informieren Sie Guice über den Request-Scope, damit wir @RequestScoped verwenden können
    bindScope(RequestScoped.class, ServletScopes.REQUEST);
  }
}

Wenn Sie in einen Unter-Scope wechseln möchten, tun Sie dies:

private void scopeAndInject(final Object perRequestSeed) {
  try {
    ServletScopes.scopeRequest(new Callable() {
      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, Object>()).call();
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

Was wir hier tun, ist ServletScopes.scopeRequest zu verwenden, um den anonymen Callable in einem neuen Request-Scope auszuführen. Der Callable erstellt dann einen Unter-Injector und fügt eine neue Bindung für alle per-Request Seed-Objekte hinzu.

Die Seeds sind Objekte, die von @RequestScoped-Objekten benötigt würden, aber nicht alleine von Guice erstellt werden könnten, wie Anfragen oder Iterations-IDs. Die neue HashMap, die als zweites Argument an scopeRequest übergeben wird, ist eine weitere Möglichkeit, Seeds buchstäblich in den neuen Scope einzufügen. Ich bevorzuge den Submodul-Weg, da die Bindungen für die gekeimten Werte sowieso immer erforderlich sind.

Der Unter-Injector befindet sich dann "im" Request-Scope und kann verwendet werden, um @RequestScoped-Objekte bereitzustellen.

Siehe auch: Wie verwendet man ServletScopes.scopeRequest() und ServletScopes.continueRequest()?

3voto

Eric Mintz Punkte 39

Haben Sie daran gedacht, einen Anbieter zu verwenden? Es wäre einfach, einen zu schreiben, der Ihren Anforderungen entspricht, z. B .:

import com.google.inject.Provider

class RootObjectProvider implements Provider {

    ...

    @Override
    RootObject get() {
        ClassD d = new ClassD( .... );
        ClassB b = new ClassB( ..., d, ...);
        ClassC c = new ClassC( ..., d, ...); // Beachten Sie, dass b und c d teilen.
        return new RootObject(b, c, ...);
    }
}

Sie können den Anbieter auf zwei Arten verwenden:

  1. Binden Sie ihn entweder als Anbieter mit der @Provides Schnittstelle oder mit der .toProvider() Bindungsdekoration.
  2. Injecten Sie den Anbieter direkt und rufen Sie ihn auf, um RootObject-Instanzen nach Bedarf zu erstellen.

Hoffentlich hilft das.

0voto

Pierre-Henri Punkte 1465

Darf ich fragen, warum du Singleton benötigst?

Ich würde nicht empfehlen, einen benutzerdefinierten Bereich zu erstellen. Der beste und einfachste Weg, um Bereiche zu mischen, besteht darin, Anbieter anstelle von Objekten einzuspritzen. Mit den Anbietern können Sie den Bereich Ihres Objekts steuern, basierend auf Ihrer Geschäftslogik.

Siehe diese Guice-Dokumentation für weitere Details.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X