任务计划、@Scheduled和石英
2022-09-02 01:35:51
有没有办法将quartz作为底层调度程序?@Scheduled
我能想到的两件事,但都需要一些工作:
- 创建一个将解析注释并注册石英作业的自定义
BeanPostProcessor
@Scheduled
- 实现委托给石英。
TaskScheduler
Scheduler
问题是:是否已经为上述两个选项编写了一些内容,是否还有其他选择?
有没有办法将quartz作为底层调度程序?@Scheduled
我能想到的两件事,但都需要一些工作:
BeanPostProcessor
@Scheduled
TaskScheduler
Scheduler
问题是:是否已经为上述两个选项编写了一些内容,是否还有其他选择?
我最终制作了自己的弹簧石英“桥”。我打算建议把它作为春天的改进。
首先,我创建了一个新的注释,该注释将放置在实现 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 假设每个作业执行都是一个新实例。我不是石英专家,所以我符合这个期望。如果事实证明它是多余的,您只需删除@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来插入石英,以便通过弹簧上下文创建作业:
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>
似乎没有现成的实现。但是,连接自己的布线应该不是很困难:
@Service
public class QuartzTaskScheduler implements TaskScheduler {
//...
}
并制作春天使用它:
<task:annotation-driven/>
<bean class="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor">
<property name="scheduler" ref="quartzTaskScheduler"/>
</bean>
如果你走这条路,考虑把你的代码贡献给Spring框架(包),或者至少为此打开一个问题。org.springframework.scheduling.quartz