TaskScheduler,@ Scheduled和quartz

有没有办法让@Scheduled与quartz作为底层调度程序?

我能想到的两件事,但都需要一些工作:

  • 创建一个自定义BeanPostProcessor ,它将解析@Scheduled注释并注册quartz作业
  • 实现TaskScheduler委托给quartz Scheduler

问题是:是否已经针对上述两个选项编写了一些内容,还有其他选择吗?

我最终制作了自己的弹簧石英“桥”。 我打算建议它作为spring的改进。

首先,我创建了一个新的注释,它将被放置在实现石英Job接口的类上:

 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Component @Scope("prototype") public @interface ScheduledJob { String cronExpression() default ""; long fixedRate() default -1; boolean durable() default false; boolean shouldRecover() default true; String name() default ""; String group() default ""; } 

(注意原型范围 – quartz假设每个作业执行都是一个新实例。我不是一个石英专家,所以我符合这个期望。如果结果是多余的,你可以简单地删除@Scope注释)

然后我定义了一个ApplicationListener,每当刷新(或启动)上下文时,都会查找所有使用@ScheduledJob注释的类,并在quartz调度程序中注册它们:

 /** * This class listeners to ContextStartedEvent, and when the context is started * gets all bean definitions, looks for the @ScheduledJob annotation, * and registers quartz jobs based on that. * * Note that a new instance of the quartz job class is created on each execution, * so the bean has to be of "prototype" scope. Therefore an applicationListener is used * rather than a bean postprocessor (unlike singleton beans, prototype beans don't get * created on application startup) * * @author bozho * */ public class QuartzScheduledJobRegistrar implements EmbeddedValueResolverAware, ApplicationContextAware, ApplicationListener { private Scheduler scheduler; private StringValueResolver embeddedValueResolver; private Map jobListeners; private ApplicationContext applicationContext; public void setEmbeddedValueResolver(StringValueResolver resolver) { this.embeddedValueResolver = resolver; } public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @SuppressWarnings("unchecked") @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (event.getApplicationContext() == this.applicationContext) { try { scheduler.clear(); for (Map.Entry entry : jobListeners.entrySet()) { scheduler.getListenerManager().addJobListener(entry.getKey(), NameMatcher.nameStartsWith(entry.getValue())); } } catch (SchedulerException ex) { throw new IllegalStateException(ex); } DefaultListableBeanFactory factory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory(); String[] definitionNames = factory.getBeanDefinitionNames(); for (String definitionName : definitionNames) { BeanDefinition definition = factory.getBeanDefinition(definitionName); try { if (definition.getBeanClassName() != null) { Class beanClass = Class.forName(definition.getBeanClassName()); registerJob(beanClass); } } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e); } } } } public void registerJob(Class targetClass) { ScheduledJob annotation = targetClass.getAnnotation(ScheduledJob.class); if (annotation != null) { Assert.isTrue(Job.class.isAssignableFrom(targetClass), "Only classes implementing the quartz Job interface can be annotated with @ScheduledJob"); @SuppressWarnings("unchecked") // checked on the previous line Class jobClass = (Class) targetClass; JobDetail jobDetail = JobBuilder.newJob() .ofType(jobClass) .withIdentity( annotation.name().isEmpty() ? targetClass.getSimpleName() : annotation.name(), annotation.group().isEmpty() ? targetClass.getPackage().getName() : annotation.group()) .storeDurably(annotation.durable()) .requestRecovery(annotation.shouldRecover()) .build(); TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger() .withIdentity(jobDetail.getKey().getName() + "_trigger", jobDetail.getKey().getGroup() + "_triggers") .startNow(); String cronExpression = annotation.cronExpression(); long fixedRate = annotation.fixedRate(); if (!BooleanUtils.xor(new boolean[] {!cronExpression.isEmpty(), fixedRate >=0})) { throw new IllegalStateException("Exactly one of 'cronExpression', 'fixedRate' is required. Offending class " + targetClass.getName()); } if (!cronExpression.isEmpty()) { if (embeddedValueResolver != null) { cronExpression = embeddedValueResolver.resolveStringValue(cronExpression); } try { triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)); } catch (ParseException e) { throw new IllegalArgumentException(e); } } if (fixedRate >= 0) { triggerBuilder.withSchedule( SimpleScheduleBuilder.simpleSchedule() .withIntervalInMilliseconds(fixedRate) .repeatForever()) .withIdentity(jobDetail.getKey().getName() + "_trigger", jobDetail.getKey().getGroup() + "_triggers"); } try { scheduler.scheduleJob(jobDetail, triggerBuilder.build()); } catch (SchedulerException e) { throw new IllegalStateException(e); } } } public void setScheduler(Scheduler scheduler) { this.scheduler = scheduler; } public void setJobListeners(Map jobListeners) { this.jobListeners = jobListeners; } } 

然后我需要一个自定义的JobFactory来插入石英,以便弹出上下文创建作业:

 public class QuartzSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private SchedulerContext schedulerContext; private ApplicationContext ctx; @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { Job job = ctx.getBean(bundle.getJobDetail().getJobClass()); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job); MutablePropertyValues pvs = new MutablePropertyValues(); pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap()); pvs.addPropertyValues(bundle.getTrigger().getJobDataMap()); if (this.schedulerContext != null) { pvs.addPropertyValues(this.schedulerContext); } bw.setPropertyValues(pvs, true); return job; } public void setSchedulerContext(SchedulerContext schedulerContext) { this.schedulerContext = schedulerContext; super.setSchedulerContext(schedulerContext); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.ctx = applicationContext; } } 

最后,xml配置:

                  

似乎没有现成的实施。 但是,连接自己应该不是很困难:

 @Service public class QuartzTaskScheduler implements TaskScheduler { //... } 

并让Spring使用它:

     

如果你走这条路,考虑将你的代码贡献给Spring框架( org.springframework.scheduling.quartz包)或者至少为此开启一个问题。