Должны ли провайдеры 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
в качестве сингла. Он будет создан только один раз, когда инжектор будет создан, и этот единственный экземпляр будет инъецирован в нужное место.