问题
I have a QuartzJobConfig
class where I register my Spring-Quartz-Beans
.
I followed the instruction of the SchedulerFactoryBean
, JobDetailFactoryBean
and CronTriggerFactoryBean
.
My Jobs are configured in a yaml
file outside the application. Means I have to create the Beans dynamically when the application starts.
My 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
...
My Config Class:
@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();
}
}
My problem is that the creation of the Beans inside the iteration (Point 1) is just done one time. After the first iteration its not going inside the jobTrigger(ch, jobConfig)
method anymore. (More or less clear because of the bean name if I am right)
What I was thinking, because I use the Quartz factories
of Spring the jobDetailFactoryBean.setBeanName()
method is used to create more beans with different names.
Not sure how I can solve this problem. The Code is working and the first created job is executing right. But I need more jobs.
How can I create the different jobs in a dynamically way?
Edit:
My full configuration classes:
@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
Added project to github for better understanding of the problem
回答1:
The reason why your list will contain null values is because the getObject method you are calling, should return the CronTrigger which is only initiated in afterPropertiesSet method called by spring when done initiating the spring context. You can call this method yourself manually on your CronTriggerFactoryBean, this will allow you to have it as a private method.
// 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;
}
I'm sure there are many other ways of doing this as well, as you mentioned yourself you did a work-around for it, if this however is not what you want or need I can check some more if I can find a better way.
回答2:
Your jobTrigger()
and jobBean()
methods are not actual beans but factory methods you are using given some inputs to construct CronTrigger
s and JobDetail
s to register in your loop found in your quartzScheduler
bean by invoking triggers.add(..)
.
Remove the @Bean
and @Scope
annotations from the jobTrigger()
and jobBean()
methods (ideally reduce their visibility too (package private if not private) and you should be good to go.
回答3:
After many different tries to get this code working, I found a working solution. Its just a workaround but gives maybe some hints to find the right - not workaround - solution.
What I did:
- I changed all my
@Configuration
classes to@Component
exceptChannelPartnerProperties
andQuartzJobConfig
. - I put
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
to myjobBean()
andjobTrigger()
method. - I deleted the method parameter of both.
- I dont have any other
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
anywhere else in my code. - I created three counter for counting through my
channelPartners
,jobConfigs
and one for the TriggerGroups name. - I dont use the local Objects in my loops anymore. But use the counters to get the right Objects from my
@Autowired channelPartnerProperties
which holds all the entries of myyaml
file.
After that my QuartzJobConfig
class looks like that:
@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();
}
All the defined jobs in my yaml
configuration gets initialized and executed like they defined.
Its a working solution but a workaround. Maybe we find a better one.
来源:https://stackoverflow.com/questions/41022276/how-to-create-spring-beans-in-a-dynamical-way-using-quartz-schedulerfactorybean