[英]Spring - Programmatically generate a set of beans
我有一個Dropwizard應用程序,需要為配置列表中的每個配置生成十幾個bean。 健康檢查,石英調度等等。
像這樣的東西:
@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實例都需要像這樣的bean。 現在我必須復制並粘貼這些定義,並為每個新配置重命名它們。
我可以以某種方式迭代我的配置類並為每個類生成一組bean定義嗎?
我可以使用子類化解決方案或類型安全的任何東西,而不會讓我復制並粘貼相同的代碼,並在我必須添加新服務時重命名方法。
編輯:我應該補充一點,我有其他依賴這些bean的組件(例如,他們注入Collection<HealthCheck>
。)
所以你需要動態地聲明新的bean並將它們注入到Spring的應用程序上下文中,好像它們只是普通的bean,這意味着它們必須經過代理,后處理等,即它們必須服從Spring bean生命周期。
請參閱BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry()
方法 javadocs。 這正是您所需要的,因為它允許您在加載普通bean定義之后 但 在實例化任何單個bean之前修改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...
}
這有效地聲明了您需要的bean並將它們注入到Spring的應用程序上下文中,每個配置都有一組bean。 你必須依賴一些命名模式 ,然后在需要的地方通過名稱自動裝配你的bean :
@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;
...
}
筆記:
如果您通過手動從文件中讀取配置名稱,請使用Spring的ClassPathResource.getInputStream()
。
如果你自己掃描類路徑,我強烈建議你使用驚人的Reflections庫 。
您必須手動設置每個bean定義的所有屬性和依賴項。 每個bean定義都與其他bean定義無關,即你不能重用它們,將它們放在另一個bean中,等等。把它們想象成你用舊的XML方式聲明bean。
檢查BeanDefinitionBuilder javadocs和GenericBeanDefinition javadocs以獲取更多詳細信息。
你應該可以做這樣的事情:
@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);
}
}
}
只是擴展Michas的答案 - 如果我這樣設置它,他的解決方案是有效的:
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();
}
}
需要注意的一點是,我將自定義bean創建為配置類的屬性,並在@PostConstruct方法中初始化它們。 這樣我就將對象注冊為bean(因此@Autowire和@Inject按預期工作),之后我可以在構造函數注入中為需要它的bean使用相同的實例。 屬性可見性設置為protected,以便子類可以使用創建的對象。
由於我們持有的實例實際上不是Spring代理,因此可能會出現一些問題(方面沒有觸發等)。 在注冊bean之后檢索bean實際上是個好主意,如:
toBeInjected = new ToBeInjected();
String beanName = this.getClass().getSimpleName() + "_quartzConfiguration";
beanFactory.registerSingleton(beanName, toBeInjected);
toBeInjected = beanFactory.getBean(beanName, ToBeInjected.class);
我會在這里籌碼。 其他人提到你需要創建一個注入配置的bean。 然后,該bean將使用您的配置創建其他bean並將它們插入到上下文中(您還需要以一種或另一種形式注入)。
我認為其他人沒有想到的是,你已經說過其他的bean將依賴於這些動態創建的bean。 這意味着必須在依賴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
}
..
您需要創建一個基本配置類,該類由所有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.
}
我能提出的“最佳”方法是將所有Quartz配置和調度程序包裝在1個超級bean中並手動連接,然后重構代碼以使用uber bean接口。
uber bean在其PostConstruct中創建了我需要的所有對象,並實現了ApplicationContextAware,因此它可以自動連接它們。 這不是理想的,但它是我能想到的最好的。
Spring根本沒有一種以類型安全的方式動態添加bean的好方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.