Custom Guice Scope или лучший подход? - PullRequest
22 голосов
/ 30 марта 2012

Вот моя проблема:

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

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

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

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

Второй подход заключается в реализации некоторого типа настраиваемой области.

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

Ответы [ 4 ]

12 голосов
/ 29 марта 2013

Мне кажется, вам нужна область для каждого экземпляра 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, позволяющая переключать области и соответствующую проводку в 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;
5 голосов
/ 29 марта 2013

С небольшой настройкой 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 ()?

2 голосов
/ 30 июля 2012

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

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

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

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

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

0 голосов
/ 05 апреля 2012

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

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

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

...