如何以动态方式创建Spring Beans。 使用Quartz SchedulerFactoryBean

我有一个QuartzJobConfig类,我注册了我的Spring-Quartz-Beans

我遵循了SchedulerFactoryBeanJobDetailFactoryBeanCronTriggerFactoryBean的指令。

我的作业在应用程序外部的yaml文件中配置。 意味着我必须在应用程序启动时动态创建Bean。

我的配置:

 channelPartnerConfiguration: channelPartners: - code: Job1 jobConfigs: - schedule: 0 * * ? * MON-FRI name: Job1 daily hotel: false allotment: true enabled: true - schedule: 30 * * ? * MON-FRI name: Job2 weekly hotel: true allotment: false enabled: true ... 

我的配置类:

 @Configuration public class QuartzJobConfig implements IJobClass{ @Autowired ChannelPartnerProperties channelPartnerProperties; @Autowired private ApplicationContext applicationContext; @Bean public SchedulerFactoryBean quartzScheduler() { SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean(); quartzScheduler.setOverwriteExistingJobs(true); quartzScheduler.setSchedulerName("-scheduler"); AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); quartzScheduler.setJobFactory(jobFactory); // point 1 List triggers = new ArrayList(); for(ChannelPartner ch : channelPartnerProperties.getChannelPartners()){ for(JobConfig jobConfig : ch.getJobConfigs()){ triggers.add(jobTrigger(ch, jobConfig).getObject()); } } quartzScheduler.setTriggers(triggers.stream().toArray(Trigger[]::new)); return quartzScheduler; } @Bean public JobDetailFactoryBean jobBean(ChannelPartner ch, JobConfig jobConfig) { JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean(); jobDetailFactoryBean.setJobClass(findJobByConfig(jobConfig)); jobDetailFactoryBean.setGroup("mainGroup"); jobDetailFactoryBean.setName(jobConfig.getName()); jobDetailFactoryBean.setBeanName(jobConfig.getName()); jobDetailFactoryBean.getJobDataMap().put("channelPartner", ch); return jobDetailFactoryBean; } @Bean public CronTriggerFactoryBean jobTrigger(ChannelPartner ch, JobConfig jobConfig) { CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); cronTriggerFactoryBean.setJobDetail(jobBean(ch, jobConfig).getObject()); cronTriggerFactoryBean.setCronExpression(jobConfig.getSchedule()); cronTriggerFactoryBean.setGroup("mainGroup"); return cronTriggerFactoryBean; } @Override public Class findJobByConfig(JobConfig jobConfig) { if(isAllotmentJob(jobConfig) && isHotelJob(jobConfig)){ return HotelAndAllotmentJob.class; } if(isAllotmentJob(jobConfig)){ return AllotmentJob.class; } if(isHotelJob(jobConfig)){ return HotelJob.class; } return HotelAndAllotmentJob.class; } private boolean isAllotmentJob(JobConfig jobConfig){ return jobConfig.isAllotment(); } private boolean isHotelJob(JobConfig jobConfig) { return jobConfig.isHotel(); } } 

我的问题是在迭代(第1点)内创建Bean只做了一次。 在第一次迭代之后,它不再进入jobTrigger(ch, jobConfig)方法。 (如果我是正确的,因为bean名称或多或少清楚)

我在想,因为我使用Spring的Quartz factoriesjobDetailFactoryBean.setBeanName()方法用于创建更多具有不同名称的bean。

不知道我怎么能解决这个问题。 代码正在运行,第一个创建的作业正在执行。 但我需要更多的工作。

如何以动态方式创建不同的作业?


编辑:

我的完整配置类:

 @Configuration @ConfigurationProperties(prefix = "channelPartnerConfiguration", locations = "classpath:customer/channelPartnerConfiguration.yml") public class ChannelPartnerProperties { @Autowired private List channelPartners; public List getChannelPartners() { return channelPartners; } public void setChannelPartners(List channelPartners) { this.channelPartners = channelPartners; } } 

 @Configuration public class ChannelPartner { private String code; private String contracts; private Boolean includeSpecialContracts; private String touroperatorCode = "EUTO"; @Autowired private PublishConfig publishConfig; @Autowired private BackupConfig backupConfig; @Autowired private List jobConfigs; //getter/setter 

 @Configuration public class JobConfig { private String schedule; private boolean hotelEDF; private boolean allotmentEDF; private boolean enabled; private String name; //getter/setter 

向github添加了项目以更好地理解问题

您的列表将包含空值的原因是因为您正在调用的getObject方法应返回CronTrigger,该CronTrigger仅在spring启动spring上下文时由spring调用的afterPropertiesSet方法中启动。 您可以在CronTriggerFactoryBean上手动调用此方法,这将允许您将其作为私有方法。

  // Just to clarify, no annotations here private CronTriggerFactoryBean jobTrigger(ChannelPartner ch, JobConfig jobConfig) throws ParseException { CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); cronTriggerFactoryBean.setJobDetail(jobBean(ch, jobConfig).getObject()); cronTriggerFactoryBean.setCronExpression(jobConfig.getSchedule()); cronTriggerFactoryBean.setGroup("mainGroup"); cronTriggerFactoryBean.setBeanName(jobConfig.getName() + "Trigger"); cronTriggerFactoryBean.afterPropertiesSet(); return cronTriggerFactoryBean; } 

我相信还有很多其他方法可以做到这一点,正如你自己提到的,你做了一个解决方案,如果这不是你想要的或需要我可以检查更多,如果我能找到更好的方法。

你的jobTrigger()jobBean()方法不是实际的bean,而是你使用的工厂方法给定一些输入来构造CronTriggerJobDetail ,通过调用triggers.add(..)quartzScheduler bean中找到的循环中注册。

jobTrigger()jobBean()方法中删除@Bean@Scope注释(理想情况下也会降低它们的可见性(如果不是私有的则包私有),你应该好好去。

在尝试使用此代码后,我找到了一个有效的解决方案。 它只是一个解决方法,但提供了一些提示,以找到正确的 – 而不是解决方法 – 解决方案。

我做了什么:

  1. 我将所有@Configuration类更改为@ComponentChannelPartnerPropertiesQuartzJobConfig除外。
  2. 我把@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)放到我的jobBean()jobTrigger()方法中。
  3. 我删除了两者的方法参数。
  4. 我的代码中的任何其他地方都没有任何其他@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  5. 我创建了三个计数器来计算我的channelPartnersjobConfigs和一个TriggerGroups名称。
  6. 我不再使用我的循环中的本地对象了。 但是使用计数器从我的@Autowired channelPartnerProperties获取正确的对象,该对象包含我的yaml文件的所有条目。

之后,我的QuartzJobConfig类看起来像这样:

 @Configuration public class QuartzJobConfig implements IJobClass { private static int channelPartnerCount = 0; private static int jobCount = 0; private static int groupCounter = 0; @Autowired ChannelPartnerProperties channelPartnerProperties; @Autowired private ApplicationContext applicationContext; @Bean public SchedulerFactoryBean quartzScheduler() { SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean(); quartzScheduler.setOverwriteExistingJobs(true); quartzScheduler.setSchedulerName("-scheduler"); AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); quartzScheduler.setJobFactory(jobFactory); List triggers = new ArrayList<>(); for (ChannelPartner ch : channelPartnerProperties.getChannelPartners()) { for (JobConfig jobConfig : ch.getJobConfigs()) { triggers.add(jobTrigger().getObject()); jobCount++; groupCounter++; } channelPartnerCount++; jobCount = 0; } quartzScheduler.setTriggers(triggers.stream().toArray(Trigger[]::new)); return quartzScheduler; } @Bean @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public JobDetailFactoryBean jobBean() { JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean(); jobDetailFactoryBean.setJobClass(findJobByConfig( channelPartnerProperties.getChannelPartners().get(channelPartnerCount).getJobConfigs().get(jobCount))); jobDetailFactoryBean.setGroup("mainGroup" + groupCounter); jobDetailFactoryBean.setName(channelPartnerProperties.getChannelPartners().get(channelPartnerCount) .getJobConfigs().get(jobCount).getName()); jobDetailFactoryBean.setBeanName(channelPartnerProperties.getChannelPartners().get(channelPartnerCount) .getJobConfigs().get(jobCount).getName()); jobDetailFactoryBean.getJobDataMap().put("channelPartner", channelPartnerProperties.getChannelPartners().get(channelPartnerCount)); return jobDetailFactoryBean; } @Bean @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public CronTriggerFactoryBean jobTrigger() { CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); cronTriggerFactoryBean.setJobDetail(jobBean().getObject()); cronTriggerFactoryBean.setCronExpression(channelPartnerProperties.getChannelPartners().get(channelPartnerCount) .getJobConfigs().get(jobCount).getSchedule()); cronTriggerFactoryBean.setGroup("mainGroup" + groupCounter); cronTriggerFactoryBean.setBeanName(channelPartnerProperties.getChannelPartners().get(channelPartnerCount) .getJobConfigs().get(jobCount).getName() + "Trigger" + groupCounter); return cronTriggerFactoryBean; } @Override public Class findJobByConfig(JobConfig jobConfig) { if (isAllotmentJob(jobConfig) && isHotelJob(jobConfig)) { return HotelAndAllotmentEdfJob.class; } if (isAllotmentJob(jobConfig)) { return AllotmentEdfJob.class; } if (isHotelJob(jobConfig)) { return HotelEdfJob.class; } return HotelAndAllotmentEdfJob.class; } private boolean isAllotmentJob(JobConfig jobConfig) { return jobConfig.isAllotmentEDF(); } private boolean isHotelJob(JobConfig jobConfig) { return jobConfig.isHotelEDF(); } 

我的yaml配置中的所有已定义作业都会按照定义进行初始化和执行。

它是一个有效的解决方案,但是一个变通 也许我们找到一个更好的。