2015-02-06 2 views
13

У меня есть приложение Dropwizard, которое должно генерировать десяток или около того компонентов для каждой из конфигураций в списке конфигурации. Такие вещи, как медицинские осмотры, кварцевые планировщики и т.д.Spring - программно сгенерирует набор бобов

Что-то вроде этого:

@Component 
class MyModule { 
    @Inject 
    private MyConfiguration configuration; 

    @Bean 
    @Lazy 
    public QuartzModule quartzModule() { 
     return new QuartzModule(quartzConfiguration()); 
    } 


    @Bean 
    @Lazy 
    public QuartzConfiguration quartzConfiguration() { 
     return this.configuration.getQuartzConfiguration(); 
    } 

    @Bean 
    @Lazy 
    public HealthCheck healthCheck() throws SchedulerException { 
     return this.quartzModule().quartzHealthCheck(); 
    } 
} 

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

Могу ли я как-то перебирать классы конфигурации и генерировать набор определений компонентов для каждого из них?

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

EDIT: Я хотел бы добавить, что у меня есть и другие компоненты, которые зависят от этих бобов (они вводят Collection<HealthCheck>, например.)

+0

Либо вам нужно регистрировать определения bean-компонентов, либо использовать некоторую магию контекстной иерархии (отдельные контексты для ваших модулей, в которых зависимости могут регистрироваться в родительском контексте), либо просто выполнять поиск служб, а не позволять весне вводить ваши (например, 'ApplicationContext # getBeansOfType'). –

ответ

0

«Лучший» подход, который я мог придумать было обернуть все мои Конфигурация кварца и планировщики в 1 uber bean и подключите его вручную, затем реорганизуйте код для работы с интерфейсом uber bean.

uber bean-компонент создает все объекты, которые мне нужны в PostConstruct, и реализует ApplicationContextAware, чтобы он мог автоматически их подключать. Это не идеально, но это было лучшее, что я мог придумать.

Весна просто не имеет возможности динамически добавлять бобы в виде шрифтов.

4

Вы должны быть в состоянии сделать что-то вроде этого:

@Configuration 
public class MyConfiguration implements BeanFactoryAware { 

    private BeanFactory beanFactory; 

    @Override 
    public void setBeanFactory(BeanFactory beanFactory) { 
     this.beanFactory = beanFactory; 
    } 

    @PostConstruct 
    public void onPostConstruct() { 
     ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory; 
     for (..) { 
      // setup beans programmatically 
      String beanName= .. 
      Object bean = .. 
      configurableBeanFactory.registerSingleton(beanName, bean); 
     } 
    } 

} 
+0

Это не сработает, потому что мне нужно построить bean-компоненты, добавить их и использовать их для создания других bean-компонентов. – noah

0

Вам нужно для создания базового класса конфигурации, который расширяется всеми вашими классами Configuration. Затем, вы можете перебрать все классы конфигурации следующим образом:

// Key - name of the configuration class 
// value - the configuration object 
Map<String, Object> configurations = applicationContext.getBeansWithAnnotation(Configuration.class); 
Set<String> keys = configurations.keySet(); 
for(String key: keys) { 
    MyConfiguration conf = (MyConfiguration) configurations.get(key); 

    // Implement the logic to use this configuration to create other beans. 
} 
3

Просто расширение на Михась ответ - его решение работает, если я поставил его так:

public class ToBeInjected { 

} 

public class PropertyInjected { 

    private ToBeInjected toBeInjected; 

    public ToBeInjected getToBeInjected() { 
     return toBeInjected; 
    } 

    @Autowired 
    public void setToBeInjected(ToBeInjected toBeInjected) { 
     this.toBeInjected = toBeInjected; 
    } 

} 

public class ConstructorInjected { 
    private final ToBeInjected toBeInjected; 

    public ConstructorInjected(ToBeInjected toBeInjected) { 
     this.toBeInjected = toBeInjected; 
    } 

    public ToBeInjected getToBeInjected() { 
     return toBeInjected; 
    } 

} 

@Configuration 
public class BaseConfig implements BeanFactoryAware{ 

    private ConfigurableBeanFactory beanFactory; 

    protected ToBeInjected toBeInjected; 

    @Override 
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 
     this.beanFactory = (ConfigurableBeanFactory) beanFactory; 
    } 

    @PostConstruct 
    public void addCustomBeans() { 
     toBeInjected = new ToBeInjected(); 
     beanFactory.registerSingleton(this.getClass().getSimpleName() + "_quartzConfiguration", toBeInjected); 
    } 

    @Bean 
    public ConstructorInjected test() { 
     return new ConstructorInjected(toBeInjected); 
    } 

    @Bean 
    public PropertyInjected test2() { 
     return new PropertyInjected(); 
    } 

} 

Единственное, что следует отметить, что Я создаю пользовательские компоненты в качестве атрибутов класса конфигурации и инициализируя их в методе @PostConstruct. Таким образом, у меня есть объект, зарегистрированный как bean (так что @Autowire и @Inject работают как ожидалось), и я могу позже использовать тот же экземпляр в инсталляции конструктора для beans, который его требует. Видимость атрибута настроена на защиту, поэтому подклассы могут использовать созданные объекты.

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

toBeInjected = new ToBeInjected(); 
String beanName = this.getClass().getSimpleName() + "_quartzConfiguration"; 
beanFactory.registerSingleton(beanName, toBeInjected); 
toBeInjected = beanFactory.getBean(beanName, ToBeInjected.class); 
22

Таким образом, вы должны объявить новые бобы на лету и ввести их в контекст приложения Spring, как если бы они были только общие beans, то есть они должны быть подвергнуты проксированию, пост-обработке и т. д., то есть они должны быть подвержены жизненному циклу Spring beans.

См. BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry() method javadocs. Это точно то, что вы нуждаетесь в, потому что она позволяет изменять контекст приложения Spring после того, как обычные определения фасоли были загруженыно, прежде чем какой-либо одной боб был экземпляр.

@Configuration 
public class ConfigLoader implements BeanDefinitionRegistryPostProcessor { 

    private final List<String> configurations; 

    public ConfigLoader() { 
     this.configurations = new LinkedList<>(); 
     // TODO Get names of different configurations, just the names! 
     // i.e. You could manually read from some config file 
     // or scan classpath by yourself to find classes 
     // that implement MyConfiguration interface. 
     // (You can even hardcode config names to start seeing how this works) 
     // Important: you can't autowire anything yet, 
     // because Spring has not instantiated any bean so far! 
     for (String readConfigurationName : readConfigurationNames) { 
      this.configurations.add(readConfigurationName); 
     } 
    } 

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { 
     // iterate over your configurations and create the beans definitions it needs 
     for (String configName : this.configurations) { 
      this.quartzConfiguration(configName, registry); 
      this.quartzModule(configName, registry); 
      this.healthCheck(configName, registry); 
      // etc. 
     } 
    } 

    private void quartzConfiguration(String configName, BeanDefinitionRegistry registry) throws BeansException { 
     String beanName = configName + "_QuartzConfiguration"; 
     BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(QuartzConfiguration.class).setLazyInit(true); 
     // TODO Add what the bean needs to be properly initialized 
     // i.e. constructor arguments, properties, shutdown methods, etc 
     // BeanDefinitionBuilder let's you add whatever you need 
     // Now add the bean definition with given bean name 
     registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); 
    } 

    private void quartzModule(String configName, BeanDefinitionRegistry registry) throws BeansException { 
     String beanName = configName + "_QuartzModule"; 
     BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(QuartzModule.class).setLazyInit(true); 
     builder.addConstructorArgReference(configName + "_QuartzConfiguration"); // quartz configuration bean as constructor argument 
     // Now add the bean definition with given bean name 
     registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); 
    } 

    private void healthCheck(String configName, BeanDefinitionRegistry registry) throws BeansException { 
     String beanName = configName + "_HealthCheck"; 
     BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(HealthCheck.class).setLazyInit(true); 
     // TODO Add what the bean needs to be properly initialized 
     // i.e. constructor arguments, properties, shutdown methods, etc 
     // BeanDefinitionBuilder let's you add whatever you need 
     // Now add the bean definition with given bean name 
     registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); 
    } 

    // And so on for other beans... 
} 

Это эффективно объявляет бобы вам нужно, и вводит их в контекст приложения Spring, один набор бобов для каждой конфигурации.Вы должны полагаться на некоторые шаблон именования, а затем autowire ваших бобов по имени везде, где необходимо:

@Service 
public class MyService { 

    @Resource(name="config1_QuartzConfiguration") 
    private QuartzConfiguration config1_QuartzConfiguration; 

    @Resource(name="config1_QuartzModule") 
    private QuartzModule config1_QuartzModule; 

    @Resource(name="config1_HealthCheck") 
    private HealthCheck config1_HealthCheck; 

    ... 

} 

Примечания:

  1. Если вы идете путем чтения имен конфигурации вручную из файл, используйте Spring's ClassPathResource.getInputStream().

  2. Если вы скажете путь к классу самостоятельно, я настоятельно рекомендую вам использовать удивительный Reflections library.

  3. Вы должны вручную установить все свойства и зависимости для каждого определения компонента. Каждое определение бина не зависит от других определений bean-компонентов, т. Е. Вы не можете их повторно использовать, устанавливать их внутри друг друга и т. Д. Думайте о них так, как будто вы объявляете bean-компоненты старым способом XML.

  4. Для получения более подробной информации отметьте BeanDefinitionBuilder javadocs и GenericBeanDefinition javadocs.

1

Я просто чип здесь. Другие отметили, что вам нужно создать компонент, в который вводится ваша конфигурация. Этот bean-компонент затем будет использовать вашу конфигурацию для создания других bean-компонентов и вставки их в контекст (который вам также понадобится для инъекций в той или иной форме).

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

@DependsOn("myCleverBeanFactory") 

Что касается того, что типа объекта Вашего умный боба завода есть, другие рекомендовали лучшие способы сделать это. Но если я правильно помню, вы на самом деле можете сделать это что-то вроде этого в старой весной 2 мира:

public class MyCleverFactoryBean implements ApplicationContextAware, InitializingBean { 
    @Override 
    public void afterPropertiesSet() { 
    //get bean factory from getApplicationContext() 
    //cast bean factory as necessary 
    //examine your config 
    //create beans 
    //insert beans into context 
    } 

..

Смежные вопросы