singleton guice

singleton - guice maven



¿Cómo puedo encontrar todos los singletons en Guice que implementan un tipo específico? (1)

No reinventar los ámbitos

En primer lugar, la "disposición" manual de los enlaces de Guice singleton es poner el carro delante del caballo. En lugar de enlazar objetos como singletons y luego tener que limpiarlos regularmente, debe usar un scope más apropiado (o definir el suyo ) para que estos objetos tengan ciclos de vida naturales durante el tiempo que se espera que existan, como en cuanto a una sola solicitud o prueba.

Esto se evidencia en la documentación de DisposableListener.dispose() :

Los buenos lugares para llamar a esto se encuentran al final de main() , en un detector de destrucción de un ServletContext , o después de una prueba

Ninguno de esos son lugares donde deberías necesitar algo como esto:

  1. Cuando .main() la JVM también terminará pronto (y probablemente su injector quedará fuera del alcance), por lo que generalmente no hay necesidad de realizar dicha limpieza antes de permitir que finalice el binario.

  2. De manera similar, cuando un ServletContext ha sido destruido, generalmente está a punto de terminar la JVM, así que simplemente deje que se cierre normalmente.

  3. En las pruebas, normalmente debería estar construyendo inyectores aislados para cada prueba, evitando así cualquier contaminación de prueba cruzada. Cuando finaliza la prueba, el inyector y todos sus enlaces quedan fuera del alcance, y no debería haber nada que limpiar.

Administrar los recursos por separado de Guice

Por supuesto, podría estar creando objetos que necesitan ser limpiados, como una instancia de AutoCloseable , pero eso no debería ser responsabilidad de Guice. En general, el sitio de llamada .getInstance() que obtiene el recurso de cierre puede ser responsable de limpiarlo. Alternativamente, los módulos podrían ser responsables de crear y administrar estos recursos. Luego, construye el inyector dentro de un bloque de prueba con recursos que administra el ciclo de vida de los módulos de recursos.

Si esas opciones no son suficientes y realmente necesita una semántica más poderosa del ciclo de vida, use un marco de ciclo de vida adecuado como el Administrador de ServiceManager de Guava , en lugar de cooptar Guice en uno.

Dicho esto, tener objetos que requieren limpieza en Guice en general no es una buena idea. En su lugar, considere vincular un tipo de envoltorio que permita a la persona que llama abrir (y cerrar) recursos según sea necesario, en lugar de vincular directamente un objeto de recursos con estado de larga duración.

Prefiere enlaces explícitos sobre los oyentes de Guice

Si realmente necesita recolectar varios objetos no relacionados enlazados en un inyector de Guice, hágalo explícitamente en el momento .configure() , en lugar de hacerlo implícitamente a través de la introspección. El uso de Multibinder permite que sus módulos declaren explícitamente qué objetos deben eliminarse, vinculándolos a una Multibinder<Disposable> que los agrega a todos. Entonces su paso de limpieza es simplemente:

for (Disposable resource : injector.getInstance(new Key<Set<Disposable>>() {}) { resource.dispose(); }

Esto evita la "magia" de un oyente que entra silenciosamente y limpia después de usted, en lugar de permitir que los autores de los módulos determinen la mejor manera de manejar los recursos a los que se unen, y, si es necesario, aproveche esta funcionalidad de limpieza.

Imagina que tengo un tipo Disposable que algunas clases implementan:

class FactoryImpl implements Disposable {}

Puedo unir esta clase como un singleton:

bind(Factory.class) .to(FactoryImpl.class) .in(Singleton.class);

o como un singleton ansioso:

bind(Factory.class) .to(FactoryImpl.class) .asEagerSingleton();

Tenga en cuenta que la implementación tiene el tipo, no la interfaz.

¿Cómo puedo encontrar todos los singletons que Guice ha creado realmente y cuáles implementan el tipo Disposable ?

Tenga en cuenta que no quiero llamar ciegamente a get() en el proveedor para evitar crear cosas que no necesito (especialmente porque estoy destruyendo singletons, por lo que crear nuevas puede causar problemas).

Esto es lo contrario de preguntas como ¿Cómo puedo obtener todas las instancias singleton de un Inyector Guice? que solo funciona entonces la interfaz contiene las claves que necesitas.

[EDITAR] Esto es lo lejos que he llegado. ¿Este código es correcto?

Primero, necesito mi interfaz.

public interface Disposable { public void dispose(); }

La magia sucede aquí:

import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.beust.jcommander.internal.Lists; import com.google.inject.AbstractModule; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.Singleton; import com.google.inject.TypeLiteral; import com.google.inject.spi.InjectionListener; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; import com.google.inject.util.Modules; /** Support for disposable beans. */ @Singleton public class DisposableListener implements InjectionListener<Object> { private static final Logger log = LoggerFactory.getLogger(DisposableListener.class); /** Use this method to create the injector */ public static Module createModule(Module ...modules) { /* Create a new module with ourself at the start. That way, our listeners will see all bindings. */ List<Module> list = Lists.newArrayList(new DisposingModule()); Collections.addAll(list, modules); return Modules.combine(list); } /** To dispose all disposables, call this method. * * <p>Good places to call this is at the end of {@code main()}, * in an destroy listener of a {@link javax.servlet.ServletContext}, or after a test. */ public static void dispose(Injector injector) { injector.getInstance(DisposableListener.class).disposeAll(); } /** Everything that is disposable */ private List<Disposable> beans = Lists.newArrayList(); private void disposeAll() { log.debug("Disposing {} beans", beans.size()); for(Disposable bean: beans) { try { bean.dispose(); } catch(Exception e) { log.warn("Error disposing {}", bean, e); } } } @Override public void afterInjection(Object injectee) { if(injectee instanceof Disposable) { log.debug("Noticed disposable bean {}", injectee); beans.add((Disposable) injectee); } } /** Module which creates the {@link DisposableListener} for the injector and sets everything up. */ private static class DisposingModule extends AbstractModule { @Override protected void configure() { DisposableListener disposableListener = new DisposableListener(); /* Attach a type listener to Guice which will add disposableListener to all types which extend Disposable */ bindListener(TypeMatchers.subclassesOf(Disposable.class), new TypeListener() { @Override public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) { Class<?> clazz = type.getRawType(); log.debug("Found disposable: {}", clazz); encounter.register(disposableListener); } }); /* Add the listener instance to the module, so we can get it later */ bind(DisposableListener.class) .toInstance(disposableListener); } } }

El código envuelve los otros módulos y se asegura de que DisposableListener esté instalado en el inyector desde el principio. Luego escucha las nuevas instancias que se crean y las recopila en una lista.

El código probablemente debería verificar que estos son todos singletons pero no sé cómo hacerlo.

Aquí están las pruebas unitarias:

import static org.junit.Assert.*; import java.util.List; import org.junit.Before; import org.junit.Test; import com.beust.jcommander.internal.Lists; import com.google.common.base.Joiner; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Singleton; public class DisposableListenerTest { private static List<String> events = Lists.newArrayList(); @Before public void clearEvents() { events.clear(); } @Test public void testEagerNoGetInstance() { Injector injector = Guice.createInjector(DisposableListener.createModule(new TestEagerSingleton())); // No call to getInstance() DisposableListener.dispose(injector); assertEvents("Foo created", "Foo disposed"); } @Test public void testEagerGetInstance() { Injector injector = Guice.createInjector(DisposableListener.createModule(new TestEagerSingleton())); Foo inst1 = injector.getInstance(Foo.class); Foo inst2 = injector.getInstance(Foo.class); DisposableListener.dispose(injector); assertSame(inst1, inst2); // validate singleton assertEvents("Foo created", "Foo disposed"); } @Test public void testLazyNoGetInstance() { Injector injector = Guice.createInjector(DisposableListener.createModule(new TestLazySingleton())); // No call to getInstance() DisposableListener.dispose(injector); assertEvents(); } @Test public void testLazyGetInstance() { Injector injector = Guice.createInjector(DisposableListener.createModule(new TestLazySingleton())); Foo inst1 = injector.getInstance(Foo.class); Foo inst2 = injector.getInstance(Foo.class); DisposableListener.dispose(injector); assertSame(inst1, inst2); // validate singleton assertEvents("Foo created", "Foo disposed"); } @Test public void testAnnotation() { Injector injector = Guice.createInjector(DisposableListener.createModule(new TestLazySingleton())); FooWithAnnotation inst1 = injector.getInstance(FooWithAnnotation.class); FooWithAnnotation inst2 = injector.getInstance(FooWithAnnotation.class); DisposableListener.dispose(injector); assertSame(inst1, inst2); // validate singleton assertEvents("FooWithAnnotation created", "FooWithAnnotation disposed"); } private void assertEvents(String...expectedEvents) { Joiner joiner = Joiner.on(''/n''); String expected = joiner.join(expectedEvents); String actual = joiner.join(events); assertEquals(expected, actual); } public static class Foo implements Disposable { public Foo() { events.add("Foo created"); } @Override public void dispose() { events.add("Foo disposed"); } } @Singleton public static class FooWithAnnotation implements Disposable { public FooWithAnnotation() { events.add("FooWithAnnotation created"); } @Override public void dispose() { events.add("FooWithAnnotation disposed"); } } public static class TestLazySingleton extends AbstractModule { @Override protected void configure() { bind(Foo.class).in(Singleton.class); } } public static class TestEagerSingleton extends AbstractModule { @Override protected void configure() { bind(Foo.class).asEagerSingleton(); } } // TODO test when bean isn''t a singleton }