Подтвердить что ты не робот

Custom Guice Scope или лучший подход?

Здесь моя проблема:

Прежде всего важно знать, что я пишу симуляцию. Это автономное приложение и однопоточное. У меня есть по существу два класса объектов, которые имеют разные требования к области охвата.

  • Классы, которые должны использоваться в качестве синглонов на протяжении всего моделирования. Например, экземпляр Random.

  • Группы классов, созданные вместе, и внутри группы, каждый экземпляр должен обрабатываться как Singleton. Например, скажем, RootObject - это класс верхнего уровня и имеет зависимость от ClassA и ClassB, оба из которых имеют зависимость от ClassD. Для любого заданного RootObject обе его зависимости (ClassA и ClassB) должны зависеть от того же экземпляра ClassD. Однако экземпляры ClassD не должны использоваться для разных экземпляров RootObject.

Надеюсь, это имеет смысл. Я могу придумать два подхода к этому. Один из них заключается в том, чтобы пометить все введенные объекты как Singletons, создать инжектор корня и открутить дочерний инжектор каждый раз, когда мне нужно создать новый экземпляр RootObject. Затем экземпляры RootObject и всех его зависимостей создаются как синглтоны, но эта информация о масштабах выбрасывается в следующий раз, когда я иду, чтобы создать еще один RootObject.

Второй подход - реализовать некоторый тип настраиваемой области.

Документация Guice дает противоречивые советы... С одной стороны, в ней говорится, что у вас должен быть один инжектор, и в идеале он один раз вызывается для создания класса высшего уровня. С другой стороны, он говорит, чтобы держаться подальше от пользовательских областей.

4b9b3361

Ответ 1

Мне кажется, что вам нужен объем для каждого экземпляра RootObject и всех его зависимостей.

В Guice вы можете создать настраиваемую область, например @ObjectScoped, например:

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

Теперь просто поместите RootObject, A, B и D в эту область:

@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

Теперь каждый RootObject имеет свою собственную область. Вы можете реализовать это как простой 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);
    }

}

Чтобы интегрировать эти области с Guice, вам понадобится com.google.inject.Scope -implementation, которая позволяет вам переключаться между областями и соответствующей проводкой в ​​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;
    }

}

Инициализируйте свою программу следующим образом:

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

Создайте первый экземпляр RootObject и его соответствующую область:

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

Просто переключите область для второй группы объектов:

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

Проверьте, соблюдены ли ваши требования:

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

Чтобы работать с группой объектов, просто введите его область действия и используйте инжектор:

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

Ответ 2

С небольшой настройкой, Guice может обеспечить двухуровневую область без специальной области. Внешний - @Singleton, а внутренний - @RequestScoped, предоставляемый расширением servlet. Это работает, даже если вы говорите о чем-то, кроме контейнера сервлетов Java EE.

У вас есть один инжектор корневого уровня для обработки ваших синглетов. Обязательно объявите аннотацию области запроса в корневом уровне следующим образом:

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

Если вы хотите ввести подпункт, вы выполните следующее:

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

Мы используем ServletScopes.scopeRequest для запуска анонимного Callable внутри новой области запроса. Затем Callable создает дочерний инжектор и добавляет новое связывание для любых объектов-объектов каждого запроса.

Семена - это объекты, которые нужны @RequestScoped, но не могут быть созданы только Guice, например, запросами или идентификаторами итераций. Новый HashMap, переданный как второй аргумент scopeRequest, является еще одним способом буквально вставить семена в новую область. Я предпочитаю путь подмодуля, так как привязки для засеваемых значений всегда требуются в любом случае.

Затем дочерний инжектор "находится" в области запроса и может использоваться для предоставления @RequestScoped вещей.

Смотрите также: Как использовать ServletScopes.scopeRequest() и ServletScopes.continueRequest()?

Ответ 3

Рассматривали ли вы использование провайдера? Было бы легко написать то, которое соответствует вашим требованиям, например:

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

Вы можете использовать провайдер двумя способами:

  • Привяжите его как поставщика с интерфейсом @Provides или декорированием привязки .toProvider().
  • Ввести поставщика напрямую и вызвать его для создания экземпляров RootObject по мере необходимости.

Надеюсь, что это поможет.

Ответ 4

Могу ли я спросить, почему вам нужны синглеты?

Я бы не рекомендовал создавать настраиваемую область. Самый лучший и простой способ смешивания областей - это внедрить поставщиков вместо объектов. С поставщиками вы можете управлять областью вашего объекта из логики бизнес-кода.

Подробнее см. в документации Guice.