2012-03-30 17 views
20

Ecco il mio problema:Custom Guice Scope o un approccio migliore?

È innanzitutto importante sapere che sto scrivendo una simulazione. Questa è un'applicazione standalone ed è single-threaded. Ho essenzialmente due classi di oggetti che hanno requisiti di ambito diversi.

  1. Classi che devono essere utilizzate come singleton durante l'intera simulazione. Un'istanza di Random, ad esempio.

  2. Gruppi di classi create insieme e all'interno del gruppo, ciascuna istanza deve essere trattata come un Singleton. Ad esempio, supponiamo che RootObject sia la classe di primo livello e abbia una dipendenza da ClassA e da ClassB, entrambi con una dipendenza da ClassD. Per qualsiasi dato RootObject, entrambe le sue dipendenze (ClassA e ClassB) devono dipendere dalla stessa istanza di ClassD. Tuttavia, le istanze di ClassD non devono essere condivise tra diverse istanze di RootObject.

Speriamo che abbia senso. Posso pensare a due approcci a questo. Uno è quello di contrassegnare tutti gli oggetti iniettati come Singletons, creare l'iniettore di radici e far scattare un iniettore di bambini ogni volta che ho bisogno di creare una nuova istanza RootObject. Quindi, le istanze di RootObject e tutte le relative dipendenze vengono create come Singletons, ma quelle informazioni di ambito vengono eliminate la prossima volta che vado a creare un altro RootObject.

Il secondo approccio consiste nell'implementare un tipo di ambito personalizzato.

La documentazione di Guice fornisce consigli contrastanti ... Da una parte, si dice che si dovrebbe avere un singolo iniettore e che idealmente si chiama una volta per creare una classe di alto livello. D'altra parte, dice di stare lontano dagli ambiti personalizzati.

+0

Ho un'idea che l'iniezione @Assisted potrebbe essere usato qui, ma sto avendo difficoltà a vedere esattamente come dovrebbe essere utilizzato ... – Rich

+0

@Josh: Da dove viene il docu dicono stare lontano dagli obiettivi? –

+0

@ A.H., Http://code.google.com/p/google-guice/wiki/CustomScopes - prima riga. "In genere è consigliabile che gli utenti non scrivano i propri ambiti personalizzati: gli ambiti integrati dovrebbero essere sufficienti per la maggior parte delle applicazioni." Ho scoperto che il primo approccio funziona molto bene - creando gli iniettori del bambino secondo necessità. Dobbiamo solo stare attenti che i "singleton per gruppo" non vengano accidentalmente legati nell'iniettore genitore, ma è abbastanza facile da controllare. – Josh

risposta

0

Posso chiedere perché avete bisogno di avere singleton?

Non è consigliabile creare un ambito personalizzato. Il modo migliore e più semplice per combinare gli ambiti è iniettare provider anziché oggetti. Con i provider è possibile controllare l'ambito del proprio oggetto dalla logica del codice aziendale.

Vedere questo Guice documentation per dettagli.

+0

Non penso che questo sia valido. Se mescolassi oggetti con scope di richiesta con singleton, allora certo ... questo funzionerebbe alla grande. Ma ho bisogno di mescolare i singleton con uno scope che non esiste già. Sembra che il modo più semplice per farlo sia utilizzare iniettori di bambini. – Josh

+0

Per rispondere alla tua domanda su Singleton: abbiamo due livelli di oggetti stateful, di cui le istanze devono essere condivise attraverso l'intera simulazione (come un generatore di numeri casuali) e altre le cui istanze devono essere condivise all'interno di un piccolo sottocomponente del simulazione. Diversi sottocomponenti vengono creati dinamicamente e ogni sottocomponente ha bisogno della propria istanza di questo singleton per componente; tuttavia, altri oggetti all'interno di tale sottocomponente devono accedere alla stessa istanza. – Josh

3

Hai mai pensato di utilizzare un provider? Sarebbe facile scrivere quello che soddisfa le vostre esigenze, ad esempio:

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

È possibile utilizzare il provider due modi:

  1. Bind come un provider con l'interfaccia @Provides o .toProvider() decorazione vincolante .
  2. Inietta direttamente il provider e invocalo per creare le istanze RootObject secondo necessità.

Spero che questo aiuti.

+4

Hmm ... la creazione di istanze con 'new' è il peccato originale in un framework DI. Soprattutto se _queste di quelle classi vogliono più materiale iniettato. –

11

Mi sembra che sia necessario un ambito per ciascuna istanza di RootObject e tutte le sue dipendenze.

In Guice è possibile creare un ambito personalizzato, dicono @ObjectScoped, in questo modo:

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

Ora basta posizionare RootObject, A, B e D in questo ambito:

@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 

Ora ogni RootObject ha il proprio ambito. È possibile implementare questo come una semplice 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); 
    } 

} 

per integrare queste ambiti con Guice avrete bisogno di un com.google.inject.Scope -attuazione, che consente di passare gli scopi e il cablaggio corrispondente nella vostra 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; 
    } 

} 

inizializzare il programma come questo:

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

creare la prima istanza di RootObject e la sua portata corrispondente:

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

Basta accendere la possibilità di un secondo gruppo di oggetti:

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

prova la conformità vostre esigenze:

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

di lavorare con un gruppo di oggetti basta inserire il campo di applicazione e utilizzare l'iniettore:

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

Non so se lasciare uno scope subito dopo la creazione dell'istanza. Qualcosa come lazy instantiation di una dipendenza del fornitore potrebbe non riuscire ... – BrunoJCM

4

Con un po 'di messa a punto, Guice in grado di fornire due livelli ambito senza un ambito personalizzato. Quello esterno è @Singleton e l'interno è @RequestScoped, fornito dall'estensione servlet. Funziona anche se stai parlando di qualcosa di diverso da un contenitore di servlet Java EE.

Avere un iniettore singolo a livello di radice per gestire i tuoi singleton. Essere sicuri di dichiarare la richiesta di applicazione di annotazione nel modulo a livello di root come così:

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

Quando si desidera entrare in un sub-campo di applicazione, si esegue questa operazione:

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

Quello che stiamo facendo utilizza ServletScopes.scopeRequest per eseguire l'anonimo Callable all'interno di un nuovo ambito di richiesta. Lo Callable crea quindi un iniettore figlio e aggiunge un nuovo bind per ogni oggetto seme per richiesta.

Le sementi sono oggetti che @RequestScoped necessitano ma non possono essere creati da Guice da solo, come richieste o ID di iterazione. Il nuovo HashMap passa come secondo argomento a scopeRequest è un altro modo per inserire letteralmente i semi nel nuovo ambito. Preferisco il modo di sottomodulo, dal momento che il bindings for the seeded values are always required comunque.

L'iniettore figlio è quindi "dentro" l'ambito della richiesta e può essere utilizzato per fornire le cose @RequestScoped.

Vedi anche questo: How to use ServletScopes.scopeRequest() and ServletScopes.continueRequest()?

Problemi correlati