Использование провайдера из двух разных областей

У меня есть следующая проблема с Guice: одно-сервисная служба, внедренная поставщиком контекстно-зависимой информации. До сих пор контекст был связан только с запросами сервлета, поэтому я использовал поставщика @RequestScoped и вводил этого поставщика в службу следующим образом:

@RequestScoped
public class ContextProvider<IContext> implements Provider<IContext> {
  @Override
  public IContext get() { ... } // returns context        
}

@Singleton
public class ServiceImpl implements IService {

  @Inject
  private Provider<IContext> contextProvider;

}

Это отлично работает. Сейчас я работаю над добавлением фоновой обработки задач в приложение. Фоновые задачи не инициируются из веб-запросов, поэтому я не могу использовать ServletScopes.scopeRequest(..). Я написал пользовательскую область (почти точную копию BatchScoped из Giuce doc), чтобы каждая задача выполнялась в своей собственной области. Теперь вопрос - как сделать BatchScoped ContextProvider и настроить Guice для его использования?

Я сделал эту попытку с привязкой EDSL:

line 1 : bind(IContext.class).toProvider(ContextProvider.class).in(RequestScoped.class);
line 2 : bind(IContext.class).toProvider(BatchContextProvider.class).in(BatchScoped.class);

но Guice говорит мне в строке 2, что "привязка к IContext уже настроена в строке 1".

Вопрос в том, как правильно сделать такую ​​инъекцию с помощью Guice?

2 ответа

Аналогичный вопрос: получение нескольких одинаковых одинаковых типов

В общем, проблема здесь в том, что вы хотите связать один и тот же класс с двумя разными провайдерами (и областями, но это на самом деле не относится к делу). Это возможно только в том случае, если вы используете уникальные аннотации привязки для каждого, например, так:

bind(IContext.class)
    .annotatedWith(MyAnnotation1.class)
    .toProvider(ContextProvider.class)
    .in(RequestScoped.class);
bind(IContext.class)
    .annotatedWith(MyAnnotation2.class)
    .toProvider(BatchContextProvider.class)
    .in(BatchScoped.class);

И измените места инъекции, чтобы включить соответствующую аннотацию:

@Inject
@MyAnnotationX
private Provider<IContext> contextProvider;

Вам нужен поддельный запрос, который начинается с вашей фоновой задачи и остается для всего этого. Что это ServletScopes.scopeRequest делает.

public class MyBackgroundTask extends Thread {
    @Override
    public void run() {
        RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap());
        try ( RequestScoper.CloseableScope ignored = scope.open() ) {
            doTask();
        }
    }

    private void doTask() {

    }
}

О, не забудьте использовать провайдеров, чтобы отложить получение ваших зависимостей. Например, расширяя предыдущий пример, чтобы фоновая задача использовала IContext,

public class MyBackgroundTask extends Thread {
    private Provider<IContext> contextProvider;

    @Inject
    public MyBackgroundTask(Provider<IContext> contextProvider) {
        this.contextProvider = contextProvider;
    }

    @Override
    public void run() {
        RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap());
        try ( RequestScoper.CloseableScope ignored = scope.open() ) {
            doTask();
        }
    }

    private void doTask() {

    }
}

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

БОНУС: Возможно, вы заметили пустую карту, отправленную в качестве параметра scopeRequest метод. Проверьте Guice Javadocs. Это те экземпляры, которые вы хотите, чтобы они уже присутствовали в вашей области фальшивых запросов. В зависимости от вашего IContext вам может понадобиться

Другие вопросы по тегам