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

时间:2016-12-07 16:15:26

标签: java spring spring-boot quartz-scheduler spring-bean

我有一个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<Trigger> 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<? extends Job> 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<ChannelPartner> channelPartners;

    public List<ChannelPartner> getChannelPartners() {
        return channelPartners;
    }

    public void setChannelPartners(List<ChannelPartner> 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<JobConfig> jobConfigs;
    //getter/setter
@Configuration
public class JobConfig {

    private String schedule;
    private boolean hotelEDF;
    private boolean allotmentEDF;
    private boolean enabled;
    private String name;
    //getter/setter

添加project to github以更好地理解问题

3 个答案:

答案 0 :(得分:2)

您的列表将包含空值的原因是因为您正在调用的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;
    }

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

答案 1 :(得分:1)

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

@Bean@Scope方法中删除jobTrigger()jobBean()注释(理想情况下也会降低其可见性(如果不是私有,请将包私有),您应该对去。

答案 2 :(得分:1)

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

我做了什么:

  1. 我改变了所有@Configuration@ComponentChannelPartnerProperties以外的QuartzJobConfig课程。
  2. 我将@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)添加到我的jobBean()jobTrigger()方法。
  3. 我删除了两者的方法参数。
  4. 我的代码中其他地方没有任何其他@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
  5. 我创建了三个计数器,用于计算channelPartnersjobConfigs和一个TriggerGroups名称。
  6. 我不再使用我的循环中的本地对象了。但是使用计数器从@Autowired channelPartnerProperties获取正确的对象,其中包含yaml文件的所有条目。
  7. 之后我的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<CronTrigger> 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<? extends Job> 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配置中的所有已定义作业都会按照定义进行初始化和执行。

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