Создание коллекции beans в Spring с помощью @Configuration

Как создать коллекцию beans, которая будет правильно управляться с помощью Spring с помощью класса с аннотацией @Configuration.

Я хотел бы сделать что-то вроде этого:

@Configuration
public Config {
    @Autowired
    private SomeConfiguration config;

    @Bean
    public List<MyBean> myBeans() {
        List<MyBean> beans = new ArrayList<MyBean>();
        for (Device device : config.getDevices()) {
            beans.add(new MyBean(device));
        }
        return beans;
    }
}

Но экземпляры MyBean не обрабатываются по почте. Таким образом, их @Autowired методы не вызываются, beans не регистрируются как mbeans и т.д. Однако список доступен для того, чтобы я мог autowire список объектов MyBean.

Я не могу использовать что-то вроде:

@Configuration
public Config {
    @Autowired
    private SomeConfiguration config;

    @Bean
    public MyBean myBean1() { ... }

    @Bean
    public MyBean myBean2() { ... }
}

Так как число экземпляров MyBean неизвестно до выполнения. Причина, по которой я хочу это сделать, состоит в том, что мы контролируем физическую машину с переменным количеством компонентов. И я хочу иметь один bean для каждого компонента.

В настоящее время я достигаю своей цели, используя BeanFactoryPostProcessor следующим образом:

@Component
public class MyBeansFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Autowired
    private SomeConfiguration config;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeanException {
        for (Device device : config.getDevices()) {
            createAndRegister(BeanDefinitionRegistry) beanFactory, device);
        }
    }

    private void createAndRegister(BeanDefinitionRegistry registry, Device device) {
        register.registerBeanDefinition("device" + device.getId(), BeanDefinitionBuilder.genericBeanDefinition(MyBean.class).addConstructorArgValue(device).getBeanDefinition());
    }
}

Но это просто кажется очень уродливым взломом.

Ответ 1

Чтобы ввести свой список MyBean, попробуйте @Resource вместо @Autowired. например,

@Resource
public List<MyBean> myBeans

Ответ 2

Невозможно использовать @Configuration для определения более чем одного bean для каждого метода (AFAIK). Таким образом, вам нужно будет использовать BFPP или использовать ApplicationContect.getAutowireCapableBeanFactory().autowire(object);

Ответ 3

Я считаю, что другой вариант в этом случае - использовать @PostConstruct следующим образом:

@Configuration
public Config {

    @Autowired
    private SomeConfiguration config;

    List<MyBean> beans = new ArrayList<MyBean>();

    @Bean
    public List<MyBean> myBeans() {     

        return beans;
    }

    @PostConstruct
    public void init() {
        for (Device device : config.getDevices()) {
            beans.add(new MyBean(device));
        }   
    }
}

Аннотация @PostConstruct полезна для инициализации свойств. Это гарантирует, что аннотированный метод будет вызываться только один раз при создании компонента.

Поправьте меня если я ошибаюсь

Ответ 4

MyBeans не обрабатываются после обработки, поскольку они создаются с помощью new и не инициализируются контейнером Spring.

Вам нужно использовать прототип beans, иметь экземпляр нового bean для каждого запроса, сделанного компонентом.

Вам нужно будет пометить ваше объявление MyBean(Device device) bean, например

@Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Затем вызовите это вместо использования new, где вы заполняете beans.

Ответ 5

Вы можете использовать ConfigurableListableBeanFactory которая поддерживает SmartLifecycle поэтому, если вы зарегистрируете бин до полной инициализации приложения, он вызовет start() для вас и других шагов постобработки.

Однако - если вы вызываете beanFactory.registerSingleton после инициализации spring, вам нужно будет вручную вызвать start() - с другой стороны, хотя ваш компонент все еще полностью подключен к управлению жизненным циклом, и Spring вызовет stop() для вас, когда контекст приложения будет отключение

@Autowired
private ConfigurableListableBeanFactory beanFactory;

@Bean
public List<MyBean> myBeansList() {

    List<MyBean> mylist; // Construct your list dynamically

    while(myCondition) {
        MyBean bean;
        // Manually register each instance with Spring
        beanFactory.registerSingleton("unique-name-for-this-bean",bean);
    }

    // Return your list as a bean so you can still autowire the list of beans
    // but each bean has already been manually added to the context
    return mylist;
}

Ответ 6

В итоге я расширил ArrayList.

@Configuration
public class Config {
    @Autowired
    private SomeConfiguration config;

    @Bean
    public List<MyBean> myBeans() {
        List<MyBean> beans = new MyBeanList();
        for (final Device device : config.getDevices()) {
            beans.add(new MyBean(device));
        }
        return beans;
    }

    private static class MyBeanList extends ArrayList<MyBean> {
        @PostConstruct
        public void init() {
            this.forEach(bean -> bean.init());
        }

        @PreDestroy
        public void close() {
            this.forEach(bean -> bean.close());
        }
    }
}

Это, конечно, хакерство, но оно кажется менее уродливым, чем вопросники.