2012-03-30 7 views
20

Aquí está mi problema:Custom Guice Scope, o un mejor enfoque?

Primero es importante saber que estoy escribiendo una simulación. Esta es una aplicación independiente y tiene un único subproceso. Tengo esencialmente dos clases de objetos que tienen diferentes requisitos de alcance.

  1. clases que se deben utilizar como singletons lo largo de toda la simulación. Una instancia de Random, como un ejemplo.

  2. Grupos de clases que se crean juntas, y dentro del grupo, cada instancia se debe tratar como Singleton. Por ejemplo, digamos RootObject es la clase de nivel superior, y tiene una dependencia a ClassA y ClassB, ambos tienen una dependencia a ClassD. Para cualquier RootObject dado, sus dos dependencias (ClassA y ClassB) deberían depender de la misma instancia de ClassD. Sin embargo, las instancias de ClassD no se deben compartir en diferentes instancias de RootObject.

Espero que tenga sentido. Puedo pensar en dos enfoques para esto. Una es marcar todos los objetos inyectados como Singletons, crear el inyector raíz y derivar un inyector hijo cada vez que necesito crear una nueva instancia de RootObject. Luego, las instancias de RootObject y todas sus dependencias se crean como Singletons, pero esa información de scoping se descarta la próxima vez que vaya a crear otra RootObject.

El segundo enfoque es implementar algún tipo de ámbito personalizado.

La documentación de Guice da consejos contradictorios ... Por un lado, dice que debe tener un solo inyector, y que idealmente se llama una vez para crear una clase de nivel superior. Por otro lado, dice mantenerse alejado de los ámbitos personalizados.

+0

tengo una idea de que la inyección @Assisted podría ser utilizado aquí, pero estoy teniendo problemas para ver exactamente cómo se debe utilizar ... – Rich

+0

@ Josh: ¿Dónde dice la docu para mantenerse alejado de los ámbitos? –

+0

@ A.H., Http://code.google.com/p/google-guice/wiki/CustomScopes - primera línea. "En general, se recomienda que los usuarios no escriban sus propios ámbitos personalizados; los ámbitos integrados deberían ser suficientes para la mayoría de las aplicaciones". He descubierto que el primer enfoque funciona muy bien, creando inyectores para niños según sea necesario. Solo debemos tener cuidado de que los "singletons por grupo" no se unan accidentalmente en el inyector padre, pero eso es bastante fácil de verificar. – Josh

Respuesta

0

¿Puedo preguntar por qué necesita tener singletons?

No recomendaría crear un ámbito personalizado. La mejor y más sencilla forma de combinar ámbitos es inyectar proveedores en lugar de objetos. Con los proveedores puede controlar el alcance de su objeto desde su lógica de código de negocio.

Consulte este Guice documentation para más detalles.

+0

No creo que esto se aplique. Si estuviera mezclando objetos con un objetivo de solicitud con singletons, entonces seguro ... esto funcionaría genial. Pero, necesito mezclar singletons con un alcance que aún no existe. Parece que la forma más fácil para nosotros de hacer esto va a ser el uso de inyectores de niños. – Josh

+0

Para abordar su pregunta de Singleton, tenemos dos niveles de objetos con estado: aquellos cuyas instancias deben compartirse en toda la simulación (como un generador de números aleatorios) y otras cuyas instancias deben compartirse dentro de un pequeño subcomponente de la simulación. Se crean dinámicamente diferentes subcomponentes, y cada subcomponente necesita su propia instancia de este singleton por componente; sin embargo, otros objetos dentro de ese subcomponente necesitan acceder a la misma instancia. – Josh

3

¿Ha considerado utilizar un proveedor? Sería fácil escribir uno que se adapte a sus necesidades, por ejemplo:

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

Usted puede utilizar el proveedor de dos maneras:

  1. obligar a éste como un proveedor, ya sea con la interfaz @Provides o la decoración de unión .toProvider() .
  2. Inyecte el proveedor directamente e inícielo para crear instancias RootObject según sea necesario.

Espero que esto ayude.

+4

Hmm ... crear instancias con 'new' es el pecado original en un marco DI. Especialmente si _todas_de esas clases quieren más cosas inyectadas. –

11

Me parece que necesita un alcance para cada instancia de RootObject y todas sus dependencias.

En Guice puede crear un ámbito personalizado, dicen @ObjectScoped, así:

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

Ahora sólo tiene que colocar RootObject, A, B y D en este ámbito:

@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 

Ahora cada RootObject tiene su propio alcance. Se puede implementar esto como un simple 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); 
    } 

} 

para integrar estos ámbitos con Guice se necesita un com.google.inject.Scope -aplicación que nos permite cambiar los alcances y el cableado correspondiente en su 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; 
    } 

} 

inicializar el programa como este:

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

crear la primera instancia de RootObject y su correspondiente ámbito:

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

Sólo tiene que activar las posibilidades de un segundo grupo de objetos:

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

prueba si se cumplen los requisitos:

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

Para trabajar con un grupo de objetos, introduce su ámbito de aplicación y el uso del inyector:

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

No sé si dejar un ámbito justo después de la creación de la instancia es suficiente. Algo así como la creación de instancias vagas de una dependencia de Proveedor puede fallar ... – BrunoJCM

4

Con un poco de configuración, Guice puede proporcionar de dos niveles alcance sin un alcance personalizado. El exterior es @Singleton, y el interior es @RequestScoped, provisto por la extensión servlet. Esto funciona incluso si está hablando de algo más que un contenedor de servlets de Java EE.

Tiene un solo inyector de nivel raíz para manejar sus singletons. Asegúrese de declarar la solicitud alcance de anotación en su módulo de nivel raíz que así:

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

Cuando se desea introducir un sub-ámbito, hacer esto:

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

Lo que estamos haciendo aquí está usando ServletScopes.scopeRequest para ejecutar el anónimo Callable dentro de un nuevo alcance de solicitud. El Callable luego crea un inyector hijo y agrega un nuevo enlace para cualquier objeto semilla por solicitud.

Las semillas son objetos que @RequestScoped cosas necesitarían pero que no podrían ser creados por Guice solo, como las solicitudes o los ID de iteración. El nuevo HashMap pasado como el segundo argumento a scopeRequest es otra forma de insertar literalmente semillas en el nuevo ámbito. Prefiero el modo submódulo, desde el bindings for the seeded values are always required de todos modos.

El inyector hijo está entonces "en" el ámbito de solicitud y se puede utilizar para proporcionar @RequestScoped cosas.

Ver esto también: How to use ServletScopes.scopeRequest() and ServletScopes.continueRequest()?