TaskScheduler, @Scheduled and quartz

匿名 (未验证) 提交于 2019-12-03 01:33:01

问题:

Is there a way to have @Scheduled with quartz as the underlying scheduler?

Two things that I can think of, but both require some work:

  • create a custom BeanPostProcessor that will parse the @Scheduled annotation and register quartz jobs
  • implement TaskScheduler to delegate to the quartz Scheduler.

The question is: is there something already written for the above two options and is there another option?

回答1:

I ended up making my own spring-quartz "bridge". I plan on suggesting it as improvement to spring.

First, I created a new annotation, that is to be placed on classes implementing the quartz Job interface:

@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 ""; } 

(Note the prototype scope - quartz assumes each job execution is a new instance. I am not a quartz expert, so I conformed to that expectation. If it turns out redundant, you can simply remove the @Scope annotation)

Then I defined an ApplicationListener that, whenever the context is refreshed (or started) looks up all classes annotated with @ScheduledJob and registers them in the quartz scheduler:

/**  * 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 extends Job> jobClass = (Class extends Job>) 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; } } 

Then I needed a custom JobFactory to plug in quartz so that jobs are created by the spring context:

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

Finally, the xml configuration:

    


回答2:

Seems like there is no ready implementation. However, wiring-up your own shouldn't be very difficult:

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

And making Spring to use it:

If you go this path, consider contributing your code to Spring framework (org.springframework.scheduling.quartz package) or at least opening an issue for that.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!