2012-03-30 11 views
20

Oto mój problem:Custom Guice Scope, czy lepsze podejście?

Przede wszystkim należy pamiętać, że piszę symulację. Jest to samodzielna aplikacja i jest jednowątkowa. Mam zasadniczo dwie klasy obiektów, które mają różne wymagania dotyczące zakresu.

  1. Klasy, które powinny być używane jako singletony podczas całej symulacji. Przykład Random, jako przykład.

  2. Grupy klas, które są tworzone razem, oraz w grupie, każda instancja powinna być traktowana jak Singleton. Na przykład powiedzmy, że RootObject jest klasą najwyższego poziomu i ma zależność od ClassA i ClassB, które mają zależność od ClassD. Dla każdego podanego RootObject, obie jego zależności (ClassA i ClassB) powinny zależeć od tego samego wystąpienia ClassD. Jednak instancji ClassD nie należy udostępniać w różnych instancjach RootObject.

Mam nadzieję, że ma to sens. Mogę wymyślić dwa podejścia do tego. Jednym z nich jest zaznaczenie wszystkich wstrzykniętych obiektów jako Singletonów, utworzenie wtryskiwacza głównego i wyodrębnienie wtryskiwacza podrzędnego za każdym razem, gdy trzeba utworzyć nową instancję RootObject. Następnie instancje RootObject i wszystkie jego zależności są tworzone jako Singletony, ale informacje dotyczące zakresu są wyrzucane przy następnym uruchomieniu w celu utworzenia kolejnego RootObject.

Drugie podejście polega na zastosowaniu pewnego rodzaju niestandardowego zakresu.

Dokumentacja Guice daje sprzeczne porady ... Z jednej strony mówi, że powinieneś mieć jeden wtryskiwacz, i to najlepiej, że zostanie wywołany jeden raz, aby stworzyć klasę najwyższego poziomu. Z drugiej strony mówi, aby trzymać się z dala od niestandardowych zakresów.

+0

Mam pomysł, że wstrzyknięcie @Assisted mogłyby zostać wykorzystane tutaj, ale mam problemy z widzeniem, jak dokładnie należy stosować ... – Rich

+0

@Josh: Skąd docu powiedzieć trzymać się z daleka od teleskopów? –

+0

@ A.H., Http://code.google.com/p/google-guice/wiki/CustomScopes - pierwsza linia. "Ogólnie zaleca się, aby użytkownicy nie pisali własnych niestandardowych zakresów - wbudowane zakresy powinny wystarczać dla większości aplikacji." Odkryłem, że pierwsze podejście działa bardzo dobrze - w razie potrzeby można stworzyć wtryskiwacze dla dzieci. Musimy tylko uważać, aby "singletony na grupę" nie przypadkowo dostały się do rodzicielskiego iniektora, ale jest to dość łatwe do sprawdzenia. – Josh

Odpowiedz

0

Czy mogę zapytać, dlaczego potrzebujesz singletonów?

Nie polecam tworzenia niestandardowego zakresu. Najlepszym i najłatwiejszym sposobem mieszania zakresów jest wstrzykiwanie dostawców zamiast obiektów. Dzięki usługodawcom możesz kontrolować zakres swojego obiektu dzięki logice kodu biznesowego.

Aby uzyskać szczegółowe informacje, zapoznaj się z tym Guice documentation.

+0

Nie sądzę, że to dotyczy. Gdybym mieszał obiekty o zadanym zakresie z singletonami, to na pewno ... to by działało świetnie. Ale muszę mieszać singletony z zakresem, który jeszcze nie istnieje. Wydaje się, że najprostszym sposobem, abyśmy to zrobili, będzie używanie wtryskiwaczy dla dzieci. – Josh

+0

Aby odpowiedzieć na twoje pytanie Singletona - mamy dwa poziomy obiektów stanowych - te, których wystąpienia muszą być współdzielone w całej symulacji (jak generator liczb losowych) i inne, których wystąpienia muszą być udostępniane w ramach małego podkomponenta symulacja. Różne podkomponenty są tworzone dynamicznie, a każdy podkomponent potrzebuje własnej instancji tego pojedynczego komponentu pojedynczego; jednak inne obiekty w tym podkomponencie muszą uzyskiwać dostęp do tej samej instancji. – Josh

3

Czy rozważałeś skorzystanie z usług dostawcy? Łatwo byłoby napisać taki, który spełnia wymagania, np

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, ...); 
    } 
} 

Można używać dostawcy dwa sposoby:

  1. powiązać go jako dostawcy albo z interfejsem @Provides lub wiążącej dekoracji .toProvider() .
  2. Wstrzyknij dostawcę bezpośrednio i wywołaj go, aby utworzyć instancje RootObject w razie potrzeby.

Mam nadzieję, że to pomoże.

+4

Hmm ... tworzenie instancji z 'new' jest oryginalnym grzechem w strukturze DI. Zwłaszcza jeśli _any_ z tych klas chce wstrzyknąć więcej rzeczy. –

11

Wydaje mi się, że potrzebujesz zakresu dla każdego wystąpienia RootObject i wszystkich jego zależności.

W Guice można utworzyć obszar niestandardowy, powiedzmy @ObjectScoped, tak:

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

Teraz wystarczy umieścić RootObject, A, B i D w tym zakresie:

@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 

teraz każdy RootObject ma swój własny zakres. Można zaimplementować to jako prosty HashMap:

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

} 

Aby zintegrować te zakresy z Guice trzeba będzie com.google.inject.Scope -implementation która umożliwia przełączanie zakresów i odpowiedniego okablowania w swoim Module.

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

} 

Inicjalizacja swój program tak:

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

Tworzenie pierwszej instancji RootObject i odpowiadający jej zakres:

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

Wystarczy przełączyć zakres dla drugiej grupy obiektów:

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

Sprawdź, czy spełnione są twoje wymagania:

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

Aby pracować z grupą obiektów wpisz swój zakres i użyć wtryskiwacz:

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

Nie wiem, czy opuszczenie lunety zaraz po utworzeniu instancji jest wystarczające. Może się nie powieść coś w rodzaju leniwego tworzenia zależności dostawcy zależnego od dostawcy ... – BrunoJCM

4

Przy odrobinie konfiguracji Guice może dostarczyć dwupoziomową zakres bez niestandardowego zakresu. Zewnętrzna to @Singleton, a wewnętrzna to @RequestScoped, dostarczona przez rozszerzenie servlet. Działa to nawet wtedy, gdy mówimy o czymś innym niż kontener serwletu Java EE.

Mieć pojedynczy wtryskiwacz z rdzeniem do obsługi pojedynczych jednostek. Pamiętaj, aby zadeklarować zakres żądania adnotacji w module korzeń poziomie jak tak:

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

Gdy chcesz wprowadzić podzakresu, to zrobić:

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

Co robimy tutaj używa się ServletScopes.scopeRequest do uruchomienia anonimowego Callable wewnątrz nowego zakresu żądania. Następnie Callable tworzy wtryskiwacz potomny i dodaje nowe powiązanie dla dowolnych obiektów początkowych dla żądań.

Ziarna są obiektami wymaganymi przez @RequestScoped, ale nie mogą być tworzone przez samą Guice, jak żądania lub identyfikatory iteracji. Nowy HashMap przekazany jako drugi argument do scopeRequest jest innym sposobem dosłownego wstawiania nasion do nowego zakresu. W każdym razie wolę sposób częściowy, od bindings for the seeded values are always required.

Wtryskiwacz dla dzieci znajduje się wówczas w zasięgu żądania i można go używać do zapewniania rzeczy w liczbie: @RequestScoped.

Zobacz to także: How to use ServletScopes.scopeRequest() and ServletScopes.continueRequest()?

Powiązane problemy