Как я могу найти все синглтоны на Guice, которые реализуют определенный тип?
Предположим, у меня есть тип Disposable
, который реализуются некоторыми классами:
class FactoryImpl implements Disposable {}
Я могу связать этот класс как singleton:
bind(Factory.class)
.to(FactoryImpl.class)
.in(Singleton.class);
или как одиночный сингл:
bind(Factory.class)
.to(FactoryImpl.class)
.asEagerSingleton();
Обратите внимание, что реализация имеет тип, а не интерфейс.
Как я могу найти все синглеты, созданные на самом деле Guice и которые реализуют тип Disposable
?
Обратите внимание, что я не хочу слепо называть get()
в провайдере, чтобы не создавать вещи, которые мне не нужны (тем более, что я уничтожаю синглтоны, поэтому создание новых может вызвать проблемы).
Это противоположность вопросов, таких как Как я могу получить все экземпляры singleton из Injector Guice?, которые работают только тогда, когда интерфейс содержит ключи, которые вам нужны.
[EDIT]. Правильно ли этот код?
Сначала мне нужен мой интерфейс.
public interface Disposable {
public void dispose();
}
Волшебство происходит здесь:
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);
}
}
}
Код обертывает другие модули и гарантирует, что DisposableListener
установлен в инжекторе на ранней стадии. Затем он прослушивает новые экземпляры, которые создаются и собирают их в списке.
Код, вероятно, должен проверить, что это все синглтоны, но я не знаю, как это сделать.
Ниже приведены модульные тесты:
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
}
Ответы
Ответ 1
Не изобретать области
Во-первых, вручную "избавление" от одноэлементных привязок Guice несколько ставит телегу перед лошадью. Вместо того, чтобы связывать объекты как одиночные, а затем их нужно регулярно чистить, вы должны использовать более подходящий scope (или определить ваш собственный), чтобы эти объекты имели естественные жизненные циклы в течение времени, в течение которого они должны существовать, например, для одного запроса или теста.
Об этом свидетельствует документация на DisposableListener.dispose()
:
Хорошие места для вызова в конце main()
, в слушателе уничтожения ServletContext
или после теста
Ни одно из этих мест вам не понадобится:
-
Когда .main()
завершается, JVM также скоро закончится (и предположительно ваш injector
выйдет за пределы области видимости), поэтому обычно нет необходимости выполнять такую очистку, прежде чем разрешить завершение двоичного файла.
-
Аналогично, когда a ServletContext
был уничтожен, вы обычно собираетесь прекратить JVM, поэтому просто позвольте ему нормально выйти.
-
В тестах вы обычно должны создавать изолированные инжекторы для каждого теста, тем самым избегая любого кросс-теста загрязнения. Когда тест заканчивается инжектором, и все его привязки выходят за пределы видимости, и очистить не должно.
Управление ресурсами отдельно от Guice
Конечно, вы могли бы создавать объекты, которые необходимо очистить, например, AutoCloseable
, но это не должно" нести ответственность за Guice. Как правило, сайт вызова .getInstance()
, который получает закрытый ресурс, должен нести ответственность за его очистку. В качестве альтернативы модуль (модули) может отвечать за создание и управление этими ресурсами. Затем вы создаете инжектор внутри блока try-with-resources, который управляет жизненным циклом модулей ресурсов.
Если эти параметры недостаточны, и вам действительно нужна более мощная семантика жизненного цикла, используйте правильную структуру жизненного цикла, такую как Guava ServiceManager
, а не кооптирование Guice в один.
Тем не менее, наличие объектов, требующих очистки в Guice, само по себе вообще не является хорошей идеей. Рассмотрим вместо этого привязку типа оболочки, которая позволяет вызывающему пользователю открывать (и закрывать) ресурсы по мере необходимости, а не привязывать к объекту ресурса с продолжительным сроком существования.
Предпочитают явные привязки над слушателями Guice
Если вам действительно нужно собрать несколько несвязанных объектов, связанных с инжектором Guice, сделайте это явно в .configure()
раз, а не неявно через интроспекцию. Использование Multibinder
позволяет вашим модулям явно объявлять, какие объекты нужно удалять, привязывая их к экземпляру Multibinder<Disposable>
, который агрегирует торговый центр. Тогда ваш шаг очистки просто:
for (Disposable resource : injector.getInstance(new Key<Set<Disposable>>() {}) {
resource.dispose();
}
Это позволяет избежать "магии" слушателя, который тихо входит и очищается после вас, вместо этого позволяет авторам модуля определять, как лучше всего обрабатывать ресурсы, которые они связывают, и, при необходимости, используя преимущества этой функции очистки.