2016-12-07 10 views
2

У меня есть класс QuartzJobConfig, где я зарегистрирую свой Spring-Quartz-Beans.Как создать весеннюю фасоль динамическим способом. Использование Quartz SchedulerFactoryBean

Я следовал инструкциям SchedulerFactoryBean, JobDetailFactoryBean и CronTriggerFactoryBean.

Мои вакансии настроены в файле yaml за пределами приложения. Значит, я должен динамически создавать Beans при запуске приложения.

Моя конфигурация:

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 
    ... 

My Config Класс:

@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) просто сделать один раз. После первой итерации он больше не входит в метод jobTrigger(ch, jobConfig). (Более или менее ясно из-за названия компонента, если я прав)

Что я думал, потому что я использую Quartz factories Весна. Метод jobDetailFactoryBean.setBeanName() используется для создания большего количества бобов с разными именами.

Не знаю, как я могу решить эту проблему. Код работает, и первое созданное задание выполняется правильно. Но мне нужно больше рабочих мест.

Как я могу создать разные задания динамически?


Edit:

Мои полные классы конфигурации:

@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 для лучшего понимания проблемы

+0

У вас есть методы, отмеченные '@ Bean', что означает, что все они являются одноточиями ... Если вы просто хотите использовать его в качестве заводского метода, отметьте его' @Bean (scope = "prototype") '. –

+0

@ M.Deinum Я попытался использовать область прототипа. Но его просто инициализация той же работы. Должен ли мой «Список » быть прототипом? – Patrick

+0

Каждый бит, который вы хотите иметь несколько экземпляров (ваша работа, триггеры), должен быть прототипом в этом сценарии, иначе он не будет работать. Таким образом, ваши «jobTrigger», а также «jobBean» должны быть прототипами в области ... Иначе это провалится. –

ответ

1

Ваши jobTrigger() и jobBean() методы не являются реальными бобы, но фабричные методы вы используете дали некоторые материалы по строительству CronTrigger с и JobDetail с, чтобы зарегистрировать в цикле найдены в вашем quartzScheduler bean, используя triggers.add(..).

Удалите @Bean и @Scope аннотаций из jobTrigger() и jobBean() методов (в идеале уменьшить их видимость тоже (пакет частных, если не частные), и вы должны быть хорошо идти.

+0

Спасибо за ваш ответ. Я попытался удалить аннотации '@ Bean' и' @ Scope', но список заполняется нулевыми значениями. Любое предложение? – Patrick

1

После многих различных попыток, чтобы получить этот код работает , Я нашел рабочее решение. Это просто обходное решение, но дает некоторые подсказки, чтобы найти правильное решение, а не решение проблемы.

Что я сделал:

  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 инициализируется и выполняется, как они определены.

Его рабочее решение, но обходное решение. Возможно, мы находим лучшую.

1

Причина, по которой ваш список будет содержать нулевые значения, заключается в том, что метод getObject, который вы вызываете, должен возвращать CronTrigger, который инициируется только в методе 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; 
    } 

Я уверен, что есть много других способов сделать это, а также, как вы упомянули себя вы сделали работу вокруг него, если это, однако, не то, что вы хотите или должны я могу проверить некоторые более если я смогу найти лучший способ.

+0

Почему 'CronTriggerFactoryBean' не должен содержать аннотации? – Patrick

+0

Оба метода jobBean и jobTrigger вызываются (по цепочке) из метода quartzScheduler. И поскольку этот метод создает SchedulerFactoryBean, который содержит эти и сам по себе, является компонентом (это означает, что это часть весеннего контекста). Я думаю, что важно только, чтобы ваш SchedulerFactoryBean был весенним бобом, я мог ошибаться здесь, но я думаю, что до тех пор, пока вы сами позаботитесь о них, нет необходимости, чтобы они действительно находились в весеннем контексте. Итак, если вы можете ответить, зачем JobDetailFactoryBean и CronTriggerFactoryBean быть в контексте весны? – nesohc

+0

Я следовал этой инструкции. [quartz and spring] (https://gist.github.com/jelies/5085593) – Patrick