Должны ли провайдеры Guice с дорогостоящими экземплярами участников аннотироваться с @Singleton?

Должны ли Провайдеры Guice аннотироваться с помощью @Singleton? Мое оправдание: если Поставщик предоставляет объект другим классам Singleton, а сам объект относительно дорог для создания, то не имеет смысла использовать провайдер Singleton, который строит дорогостоящий объект в своем конструкторе @Inject -marked, сохранить его как члена и просто вернуть эту уже сохраненную глобальную переменную в getter? Что-то вроде этого:

@Singleton
public class MyProvider extends Provider<ExpensiveObject> {
    private ExpensiveObject obj;

    @Inject
    public MyProvider() {
        /* Create the expensive object here, set it to this.obj */
    }

    @Override
    public ExpensiveObject get() {
        return obj;
    }
}


Update

Позвольте мне пояснить немного больше здесь. Речь идет не о том, следует ли использовать @Singleton или .in(Singleton.class). Это должно сделать больше с "кэшированием" созданного объекта.

Предположим, что для создания объекта потребовалось несколько RPC для завершения, например десериализация JSON или создание HTTP-запросов. Это может занять довольно много времени. Если я собираюсь использовать этот Провайдер для ввода в классы несколько раз, то разве не имеет смысла только создавать такой объект один раз?

Также обратите внимание, что я должен иметь возможность использовать Провайдера, потому что мне нужно иметь возможность вводить в Провайдер.

Ответы

Ответ 1

Если ваш вопрос заключается в том, следует ли создавать привязку привязанного поставщика, или если вы должны вручную кэшировать экземпляры в своих поставщиках, то действительно, не пытайтесь быть умнее, чем Guice:) Вы действительно не хотите делать ничего больше, чем просто создайте свой дорогой объект в методе get(). Простой тестовый пример:

public class MyProvider implements Provider<String> {
    public String get() {
        System.out.println("Called MyProvider.get()");
        return "abcd";
    }
}

public class MyModule extends AbstractModule {
    protected void configure() {
        bind(String.class).toProvider(MyProvider.class).in(Singleton.class);
    }
}

Injector injector = Guice.createInjector(new MyModule());
String abcd1 = injector.getInstance(String.class);  // Prints "Called MyProvider.get()
String abcd2 = injector.getInstance(String.class);  // Prints nothing!
// Or, if you want, comment out above two lines and try the following:
Provider<String> abcdProvider = injector.getProvider(String.class);
abcdProvider.get();  // Prints "Called MyProvider.get()"
abcdProvider.get();  // Prints nothing

Вы видите, потому что сообщение было напечатано только один раз, метод MyProvider.get() был вызван только один раз, именно потому, что String привязан в области Singleton.

Ключевая концепция для понимания здесь заключается в том, что поставщики и привязки не являются отдельными объектами. При каждой привязке есть связанный провайдер (при создании простых привязок с to() для вас создается неявный поставщик). Это легко заметить из подписи метода getProvider() - он принимает Class<T> или Key<T> для фактического класса, который вы хотите получить, а не для поставщика, которого вы связали. Когда вы создаете привязку к определенному провайдеру, вы не настраиваете этого провайдера, вы настраиваете привязку. Guice достаточно умен, чтобы учитывать охват, даже если вы используете явные провайдеры, поэтому вам просто не нужно изобретать колесо и развертывать собственный синглтон.

Если ваш вопрос касается использования аннотации @Singleton (в отличие от bind() DSL), то я не знаю, дает ли его присутствие в классе провайдера какой-либо эффект, но при условии, что вы должны использовать bind().toProvider() чтобы связать этого провайдера, я не думаю, что это действительно имеет значение. Просто используйте метод in(), он, безусловно, будет работать.

Ответ 2

Обратите внимание, что существует большая разница между привязкой вашего провайдера к области Singleton с помощью .in(Singleton.class) и использования аннотации @Singleton в вашем классе поставщика.

  • В первом случае метод get() вызывается только один раз, и результат будет сохранен в области Singleton.
  • Во втором случае экземпляр поставщика создается только один раз, но метод get() вызывается для каждой точки инъекции в вашем приложении.
    Если вы используете этот подход, было бы разумно вручную кэшировать ваш дорогой объект. Но на самом деле нет смысла делать это. Просто используйте первый подход, и вы в порядке.

Вы могли бы даже объединить две стратегии, например. путем аннотирования поставщика с помощью @Singleton и привязки результата поставщика для запроса области с помощью .in(RequestScoped.class). Без аннотации ваш провайдер будет инициирован для каждого запроса, что может иметь значение, если он хранит данные с состоянием.

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

Ответ 3

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