任务调度器,@Scheduled和quartz

22

有没有一种方法可以使用quartz作为底层调度程序,并使用@Scheduled注释?

我能想到两种方法,但都需要一些工作:

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

问题是:是否已经有人为上述两个选项编写了代码,还有其他选项吗?


我没有遇到过这种情况。我猜大多数人不认为需要将它们堆叠在一起。毕竟,Spring任务调度程序在所有实质上都与Quartz执行相同的操作,那么委派的好处是什么呢? - pap
1
据我所知,Quartz拥有更多的功能。 - Bozho
2个回答

24

我最终自己制作了一个spring-quartz“桥梁”。我计划将其建议为Spring的改进。

首先,我创建了一个新的注释,该注释应放置在实现quartz 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假定每个作业执行都是一个新实例。我不是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<ContextRefreshedEvent> {

private Scheduler scheduler;

private StringValueResolver embeddedValueResolver;

private Map<JobListener, String> 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<JobListener, String> 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<Trigger> 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<JobListener, String> jobListeners) {
    this.jobListeners = jobListeners;
}
}

然后我需要一个自定义的JobFactory来插入Quartz,以便工作由Spring上下文创建:

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配置如下:
    <bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="jobFactory">
        <bean class="com.foo.bar.scheduling.QuartzSpringBeanJobFactory" />
    </property>
</bean>

<bean id="scheduledJobRegistrar" class="com.foo.bar.scheduling.QuartzScheduledJobRegistrar">
    <property name="scheduler" ref="quartzScheduler" />
    <property name="jobListeners">
        <map>
            <entry value=""> <!-- empty string = match all jobs -->
                <key><bean class="com.foo.bar.scheduling.FailuresJobListener"/></key>
            </entry>
        </map>
    </property>
</bean>

非常感谢您发布您的结果。看起来有点复杂,因此将来在Spring的版本中包含它肯定是一个很好的选择。 - Guido
顺便问一下,你知道你的代码是否可以使用数据库来持久化作业并与quartz配合工作吗?我从这个我正在尝试解决的问题的答案中找到了你的回答:http://stackoverflow.com/questions/8991244 - Guido
1
应该进行一些修改,但我不知道应该是什么修改。 - Bozho

1

似乎没有现成的实现。但是,自己编写应该不会很困难:

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

并让Spring使用它:

<task:annotation-driven/>

<bean class="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor">
    <property name="scheduler" ref="quartzTaskScheduler"/>
</bean>

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

谢谢。我已经选择了不同的路径,准备好后会分享(我制作了自定义注释和后处理器)。 - Bozho
添加了我的实现(有点长) - Bozho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接