【问题标题】:How to create Spring Beans in a dynamical way. Using Quartz SchedulerFactoryBean如何以动态方式创建 Spring Bean。使用 Quartz SchedulerFactoryBean
【发布时间】:2016-12-07 16:15:26
【问题描述】:

我有一个QuartzJobConfig 课程,我在其中注册了我的Spring-Quartz-Beans。

我遵循了SchedulerFactoryBean、JobDetailFactoryBean和CronTriggerFactoryBean的指示。

我的作业在应用程序外部的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以便更好地理解问题

【问题讨论】:

  • 您的方法标记为@Bean,这意味着它们都是单例...如果您只想将其用作工厂方法,请将其标记为@Bean(scope="prototype")。
  • @M.Deinum 我尝试使用原型范围。但它只是初始化相同的工作。我的List&lt;JobConfig&gt; 应该是原型吗?
  • 您希望拥有多个实例(您的工作、触发器)的每个 bean 都需要在此场景中成为原型范围,否则它将无法工作。所以你的jobTrigger 和jobBean 都必须是原型范围的......否则它会失败。
  • @M.Deinum 完全正确。你描述的工作流程就是这样发生的。但只有我在列表中的第一个 jobconfig 被多次注册。我在这里错过了什么?
  • 如果你没有原型作用域的bean,它们是单例的,只会被创建一次......

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


【解决方案1】:

您的列表将包含空值的原因是因为您正在调用的 getObject 方法应该返回 CronTrigger,该 CronTrigger 仅在完成启动 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;
    }

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

【讨论】:

  • 为什么CronTriggerFactoryBean不应该有任何注释?
  • 您的 jobBean 和 jobTrigger 方法都是从quartzScheduler 方法调用的(通过链)。由于该方法创建了一个包含这些的 SchedulerFactoryBean 并且它本身就是一个 bean(这意味着它是 spring 上下文的一部分)。我认为唯一重要的是您的 SchedulerFactoryBean 是一个spring bean,我在这里可能是错的,但我认为只要您自己照顾它们,它们实际上就不需要在spring上下文中。那么如果你能回答,为什么 JobDetailFactoryBean 和 CronTriggerFactoryBean 应该在 spring 上下文中呢?
  • 我遵循了这个指示。 quartz and spring
  • 好吧,我的理解是,将它们添加为 spring bean 的原因是为了确保在创建上下文后调用某些方法。 afterPropertiresSet 是我能找到的唯一一个,你可以手动调用。然而,如果在未来的版本中添加了其他需要在 spring 负责创建后调用的方法,则不将它们作为 bean 的缺点是。所以基本上你在这里要做的就是自己照顾创作,承担责任,而不是把它留给春天。
【解决方案2】:

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

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

【讨论】:

  • 感谢您的回答。我试图删除 @Bean 和 @Scope 注释,但列表中填充了空值。有什么建议吗?
【解决方案3】:

在多次尝试让这段代码正常工作后,我找到了一个可行的解决方案。它只是一种解决方法,但可能会提供一些提示来找到正确的解决方案 - 而不是解决方法 - 解决方案。

我做了什么:

  1. 我改变了我所有的@Configuration 类到 @Component 除了 ChannelPartnerProperties 和 QuartzJobConfig。
  2. 我将@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) 放入我的jobBean() 和jobTrigger() 方法中。
  3. 我删除了两者的方法参数。
  4. 我的代码中没有任何其他 @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)。
  5. 我创建了三个计数器用于计数我的channelPartners、jobConfigs 和一个用于 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<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 配置中的所有已定义作业都会按照它们定义的方式进行初始化和执行。

这是一个可行的解决方案,但也是一种解决方法。也许我们会找到更好的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-19
    • 2017-01-29
    • 2012-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多