TaskScheduler, @Scheduled и кварц - PullRequest
21 голосов
/ 22 июля 2011

Есть ли способ иметь @Scheduled с кварцем в качестве основного планировщика?

Две вещи, о которых я могу подумать, но обе требуют некоторой работы:

  • создать пользовательский BeanPostProcessor, который будет анализировать аннотацию @Scheduled и регистрировать кварцевые задания
  • Реализация TaskScheduler для делегирования кварцу Scheduler.

Вопрос: уже написано что-то для двух вышеупомянутых опций и есть ли другая опция?

Ответы [ 2 ]

23 голосов
/ 27 июля 2011

Я закончил тем, что сделал свой собственный весенне-кварцевый "мост". Я планирую предложить это как улучшение к весне.

Сначала я создал новую аннотацию, которая должна быть размещена в классах, реализующих кварцевый интерфейс 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 "";
}

(обратите внимание на область действия прототипа - кварц предполагает, что каждое выполнение задания является новым экземпляром. Я не являюсь экспертом по кварцу, поэтому я соответствовал этому ожиданию. Если оно окажется избыточным, вы можете просто удалить аннотацию @Scope)

Затем я определил ApplicationListener, который всякий раз, когда контекст обновляется (или запускается), ищет все классы, аннотированные @ScheduledJob, и регистрирует их в кварцевом планировщике:

/**
 * 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 для подключения кварца, чтобы задания создавались в контексте пружины:

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>
1 голос
/ 24 июля 2011

Кажется, что нет готовой реализации.Однако, собственное подключение не должно быть очень сложным:

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

И заставить Spring использовать его:

<task:annotation-driven/>

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

Если вы идете по этому пути, рассмотрите возможность добавления своего кода вSpring Framework (org.springframework.scheduling.quartz пакет) или, по крайней мере, открытие проблемы для этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...