2016-12-07 4 views
2

J'ai une classe QuartzJobConfig où j'inscris mon Spring-Quartz-Beans. J'ai suivi les instructions du SchedulerFactoryBean, JobDetailFactoryBean et CronTriggerFactoryBean.Comment créer des haricots de printemps de manière dynamique. Utilisation de Quartz SchedulerFactoryBean

Mes tâches sont configurées dans un fichier yaml en dehors de l'application. Moyens Je dois créer les Beans dynamiquement lorsque l'application démarre.

Ma config:

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

Ma config Classe:

@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(); 
    } 

} 

Mon problème est que la création des haricots à l'intérieur de l'itération (point 1) est juste fait une seule fois. Après la première itération, il ne va plus dans la méthode jobTrigger(ch, jobConfig). (Plus ou moins claire en raison du nom de haricot si je suis à droite)

Ce que je pensais, parce que j'utilise la Quartz factories du printemps la méthode jobDetailFactoryBean.setBeanName() est utilisée pour créer plus de haricots avec des noms différents.

Vous ne savez pas comment résoudre ce problème. Le code fonctionne et le premier travail créé s'exécute correctement. Mais j'ai besoin de plus d'emplois.

Comment puis-je créer les différents travaux de manière dynamique?


Edit:

Mes cours complet de configuration:

@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 

Ajouté project to github pour une meilleure compréhension du problème

+0

Vous avez vos méthodes marquées '@ Bean' qui signifie que toutes sont des singletons ... Si vous voulez juste l'utiliser comme une méthode d'usine marquez-le' @Bean (scope = "prototype") '. –

+0

@ M.Deinum J'ai essayé d'utiliser la portée du prototype. Mais c'est juste initialiser le même travail. Est-ce que mon 'List ' devrait être prototype? – Patrick

+0

Chaque bean que vous voulez avoir plusieurs instances de (votre travail, triggers) doit être prototype dans ce scénario sinon cela ne fonctionnera pas. Donc à la fois votre 'jobTrigger' et' jobBean' doivent être protégés par un prototype ... Sinon, cela échouera. –

Répondre

1

Votre jobTrigger() et jobBean() méthodes ne sont pas les haricots réels, mais les méthodes d'usine vous en utilisant donné des entrées pour construire CronTrigger s et JobDetail s pour enregistrer dans votre boucle trouvée dans votre Bean en appelant triggers.add(..).

Retirez les @Bean et @Scope annotations des méthodes jobTrigger() et jobBean() (idéalement de réduire leur visibilité trop (privée sinon privée package) et vous devriez être bon d'aller.

+0

Merci pour votre réponse. J'ai essayé de supprimer les annotations '@ Bean' et' @ Scope' mais la liste est remplie de valeurs nulles. Toute suggestion? – Patrick

1

Après de nombreux essais pour obtenir le code de travail , J'ai trouvé une solution de travail.C'est juste une solution de contournement, mais donne peut-être quelques conseils pour trouver la bonne solution - pas solution de contournement -.

Ce que je l'ai fait:

  1. J'ai changé tous mes @Configuration classes @Component sauf ChannelPartnerProperties et QuartzJobConfig.
  2. Je mets @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) à ma méthode jobBean() et jobTrigger().
  3. J'ai supprimé le paramètre de méthode des deux. Je n'ai pas d'autre @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) nulle part ailleurs dans mon code.
  4. J'ai créé trois compteur pour compter à travers mes channelPartners, jobConfigs et un pour le nom TriggerGroups.
  5. Je n'utilise plus les objets locaux dans mes boucles. Mais utilisez les compteurs pour obtenir les bons objets de mon @Autowired channelPartnerProperties qui contient toutes les entrées de mon fichier yaml.

Après que ma classe QuartzJobConfig ressemble à ça:

@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(); 
    } 

Tous les travaux définis dans ma configuration yaml et obtient initialisés exécutée comme ils définis.

C'est une solution de travail mais une solution de contournement. Peut-être que nous en trouverons un meilleur.

1

La raison pour laquelle votre liste contiendra des valeurs nulles est que la méthode getObject que vous appelez doit renvoyer le CronTrigger qui est uniquement initié dans la méthode afterPropertiesSet appelée par spring lorsque le contexte printanier est terminé. Vous pouvez appeler vous-même cette méthode manuellement sur votre CronTriggerFactoryBean, cela vous permettra de l'avoir comme une méthode privée.

// 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; 
    } 

Je suis sûr qu'il ya beaucoup d'autres façons de le faire aussi bien, comme vous avez dit que vous avez fait un travail autour pour elle, si cela est toutefois pas ce que vous voulez ou avez besoin, je peux vérifier un peu plus si je peux trouver un meilleur moyen.

+0

pourquoi 'CronTriggerFactoryBean' ne devrait avoir aucune annotation? – Patrick

+0

Vos méthodes jobBean et jobTrigger sont appelées (par chaîne) à partir de la méthode quartzScheduler. Et comme cette méthode crée un SchedulerFactoryBean qui contient ceux-ci et est lui-même un bean (ce qui signifie que c'est une partie du contexte de printemps). Je pense que la seule chose importante est que votre SchedulerFactoryBean est un haricot de printemps, je peux me tromper ici mais je pense que tant que vous en prenez soin vous-même, il n'est pas nécessaire qu'ils soient dans le contexte du printemps. Donc, si vous pouvez répondre, pourquoi JobDetailFactoryBean et CronTriggerFactoryBean devraient être dans le contexte de printemps? – nesohc

+0

J'ai suivi cette instruction. [quartz et printemps] (https://gist.github.com/jelies/5085593) – Patrick