Используя Spring Java Config, мне нужно получить / создать экземпляр bean-компонента с прототипом с аргументами конструктора, которые доступны только во время выполнения. Рассмотрим следующий пример кода (упрощенный для краткости):
@Autowired
private ApplicationContext appCtx;
public void onRequest(Request request) {
//request is already validated
String name = request.getParameter("name");
Thing thing = appCtx.getBean(Thing.class, name);
//System.out.println(thing.getName()); //prints name
}
где класс Thing определяется следующим образом:
public class Thing {
private final String name;
@Autowired
private SomeComponent someComponent;
@Autowired
private AnotherComponent anotherComponent;
public Thing(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
Обратите внимание , name
это final
: он может быть поставлен только через конструктор, и гарантирует неизменность. Другие зависимости являются зависимостями Thing
класса, зависящими от реализации , и не должны быть известны (тесно связаны) с реализацией обработчика запросов.
Этот код отлично работает с конфигурацией Spring XML, например:
<bean id="thing", class="com.whatever.Thing" scope="prototype">
<!-- other post-instantiation properties omitted -->
</bean>
Как добиться того же с конфигурацией Java? Следующее не работает с использованием Spring 3.x:
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
Теперь я мог бы создать Factory, например:
public interface ThingFactory {
public Thing createThing(String name);
}
Но это лишает смысла использование Spring для замены шаблонов проектирования ServiceLocator и Factory , которые были бы идеальными для этого варианта использования.
Если бы Spring Java Config мог это сделать, я бы смог избежать:
- определение интерфейса Factory
- определение реализации Factory
- написание тестов для реализации Factory
Это тонна работы (условно говоря) для чего-то столь тривиального, что Spring уже поддерживает через конфигурацию XML.
Thing
реализация на самом деле более сложная и имеет зависимости от других bean-компонентов (я просто опустил их для краткости). Таким образом, я не хочу, чтобы реализация обработчика запросов знала о них, поскольку это жестко связывает обработчик с API / bean-компонентами, которые ему не нужны. Я обновлю вопрос, чтобы отразить ваш (отличный) вопрос.@Qualifier
параметры для установщика с помощью@Autowired
самого установщика.@Bean
works.@Bean
Метод вызывается с соответствующими аргументами , переданнымиgetBean(..)
.Ответы:
В
@Configuration
классе такой@Bean
методиспользуется для регистрации определения bean-компонента и предоставления фабрики для создания bean-компонента . Бин, который он определяет, создается только по запросу с использованием аргументов, которые определяются либо напрямую, либо путем его сканирования
ApplicationContext
.В случае
prototype
bean-компонента каждый раз создается новый объект и, следовательно,@Bean
также выполняется соответствующий метод.Вы можете получить компонент из метода
ApplicationContext
через егоBeanFactory#getBean(String name, Object... args)
метод, который указываетДругими словами, для этого
prototype
bean-компонента с заданной областью действия вы предоставляете аргументы, которые будут использоваться не в конструкторе класса bean-компонента, а при@Bean
вызове метода.По крайней мере, это верно для версий Spring 4+.
источник
@Bean
метод ручным вызовом. Если вы когда - либо метод будет вызван , вероятно , умирает от невозможности вводить параметр. То же самое, если вы . Я нашел это немного хрупким.@Autowire Thing
@Bean
@Autowire List<Thing>
@Autowire
?С Spring> 4.0 и Java 8 вы можете сделать это более безопасно:
Использование:
Итак, теперь вы можете получить свой bean-компонент во время выполнения. Это, конечно, фабричный шаблон, но вы можете сэкономить время на написании определенного класса, например
ThingFactory
(однако вам придется написать собственный,@FunctionalInterface
чтобы передать более двух параметров).источник
fabric
вместоfactory
, мой плохой :)Provider
или вObjectFactory
, или я ошибаюсь? И в моем примере вы можете передать ему строковый параметр (или любой параметр)@Bean
иScope
аннотации кThing thing
методу. Более того, этот метод можно сделать частным, чтобы скрыть себя и оставить только factory.Начиная с Spring 4.3, есть новый способ сделать это, который был вшит для этой проблемы.
ObjectProvider - он позволяет вам просто добавить его как зависимость к вашему «аргументированному» компоненту Prototype с областью видимости и создать его экземпляр с помощью аргумента.
Вот простой пример того, как его использовать:
Это, конечно, напечатает строку приветствия при вызове usePrototype.
источник
ОБНОВЛЕНО за комментарий
Во-первых, я не уверен, почему вы говорите «это не работает» для чего-то, что отлично работает в Spring 3.x. Я подозреваю, что что-то не так в вашей конфигурации.
Это работает:
- Файл конфигурации:
- Тестовый файл для выполнения:
Использование Spring 3.2.8 и Java 7 дает следующий результат:
Таким образом, компонент «Singleton» запрашивается дважды. Однако, как и следовало ожидать, Spring создает его только один раз. Во второй раз он видит, что у него есть этот компонент, и просто возвращает существующий объект. Конструктор (метод @Bean) не вызывается второй раз. В связи с этим, когда компонент «Prototype» запрашивается из одного и того же объекта контекста дважды, мы видим, что ссылка изменяется в выходных данных И что конструктор (метод @Bean) вызывается дважды.
Итак, вопрос в том, как внедрить синглтон в прототип. В приведенном выше классе конфигурации показано, как это сделать! Вы должны передать все такие ссылки в конструктор. Это позволит созданному классу быть чистым POJO, а также сделать содержащиеся в нем ссылочные объекты неизменными, какими они должны быть. Таким образом, услуга трансфера может выглядеть примерно так:
Если вы напишете модульные тесты, вы будете очень счастливы, что создали классы без @Autowired. Если вам действительно нужны компоненты с автоматическим подключением, сохраните их локально в файлах конфигурации java.
Это вызовет метод ниже в BeanFactory. Обратите внимание в описании на то, как это предназначено для вашего конкретного случая использования.
источник
Вы можете добиться аналогичного эффекта, просто используя внутренний класс :
источник
в вашем XML-файле beans используйте атрибут scope = "prototype"
источник
Поздний ответ с немного другим подходом. Это продолжение этого недавнего вопроса, который касается самого вопроса.
Да, как было сказано, вы можете объявить компонент-прототип, который принимает параметр в
@Configuration
классе, который позволяет создавать новый компонент при каждой инъекции.Это сделает этот
@Configuration
класс фабрикой, и, чтобы не возлагать на эту фабрику слишком много обязанностей, он не должен включать другие компоненты.Но вы также можете ввести этот компонент конфигурации для создания
Thing
s:Это одновременно и типобезопасный, и лаконичный.
источник
@Configuration
этого механизма.BeanFactory#getBean()
. Но это намного хуже с точки зрения связывания, поскольку это фабрика, которая позволяет получать / создавать экземпляры любых bean-компонентов приложения, а не только того, который нужен текущему bean-компоненту. При таком использовании вы можете очень легко смешивать обязанности вашего класса, поскольку зависимости, которые он может использовать, неограничены, что на самом деле не рекомендуется, но является исключительным случаем.