Java quartz and spring

Добавление Quartz в Spring Boot

В моей статье «Specifications to the Rescue» я показал как можно использовать JPA Specification в Spring Boot для реализации фильтрации в RESTful API. Затем в статье «Testing those Specifications» было показано как протестировать эти самые спецификации.

Следующим шагом я решил продемонстрировать, как добавить планировщик заданий в это же приложение Spring Boot.

Планировщик заданий Quartz

Команда Spring продолжает облегчать разработку на Java, предоставляя различные Spring Boot Starter, подключаемые через простую maven-зависимость.

В этой статье я сконцентрируюсь на стартере Quartz Scheduler, который можно добавить в проект Spring Boot с помощью следующей зависимости:

 org.springframework.boot spring-boot-starter-quartz 

Реализация довольно проста и описана здесь. Полный список текущих Spring Boot Starter вы можете посмотреть здесь.

Настройка

Используя работу, опубликованную Дэвидом Киссом, первым этапом будет добавление автосвязывания для заданий Quartz:

public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware < private transient AutowireCapableBeanFactory beanFactory; @Override public void setApplicationContext(final ApplicationContext context) < beanFactory = context.getAutowireCapableBeanFactory(); >@Override protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception < final Object job = super.createJobInstance(bundle); beanFactory.autowireBean(job); return job; >>

Далее добавляем базовую конфигурацию Quartz:

@Configuration public class QuartzConfig < private ApplicationContext applicationContext; private DataSource dataSource; public QuartzConfig(ApplicationContext applicationContext, DataSource dataSource) < this.applicationContext = applicationContext; this.dataSource = dataSource; >@Bean public SpringBeanJobFactory springBeanJobFactory() < AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory(); jobFactory.setApplicationContext(applicationContext); return jobFactory; >@Bean public SchedulerFactoryBean scheduler(Trigger. triggers) < SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean(); Properties properties = new Properties(); properties.setProperty("org.quartz.scheduler.instanceName", "MyInstanceName"); properties.setProperty("org.quartz.scheduler.instanceId", "Instance1"); schedulerFactory.setOverwriteExistingJobs(true); schedulerFactory.setAutoStartup(true); schedulerFactory.setQuartzProperties(properties); schedulerFactory.setDataSource(dataSource); schedulerFactory.setJobFactory(springBeanJobFactory()); schedulerFactory.setWaitForJobsToCompleteOnShutdown(true); if (ArrayUtils.isNotEmpty(triggers)) < schedulerFactory.setTriggers(triggers); >return schedulerFactory; > >

Можно вынести свойства, используемые в методе scheduler() , наружу, но я специально решил упростить этот пример.

Затем добавляются статические методы, обеспечивающие программный способ создания заданий и триггеров:

@Slf4j @Configuration public class QuartzConfig < . static SimpleTriggerFactoryBean createTrigger(JobDetail jobDetail, long pollFrequencyMs, String triggerName) < log.debug("createTrigger(jobDetail=<>, pollFrequencyMs=<>, triggerName=<>)", jobDetail.toString(), pollFrequencyMs, triggerName); SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean(); factoryBean.setJobDetail(jobDetail); factoryBean.setStartDelay(0L); factoryBean.setRepeatInterval(pollFrequencyMs); factoryBean.setName(triggerName); factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); factoryBean.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT); return factoryBean; > static CronTriggerFactoryBean createCronTrigger(JobDetail jobDetail, String cronExpression, String triggerName) < log.debug("createCronTrigger(jobDetail=<>, cronExpression=<>, triggerName=<>)", jobDetail.toString(), cronExpression, triggerName); // To fix an issue with time-based cron jobs Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean(); factoryBean.setJobDetail(jobDetail); factoryBean.setCronExpression(cronExpression); factoryBean.setStartTime(calendar.getTime()); factoryBean.setStartDelay(0L); factoryBean.setName(triggerName); factoryBean.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING); return factoryBean; > static JobDetailFactoryBean createJobDetail(Class jobClass, String jobName) < log.debug("createJobDetail(jobClass=<>, jobName=<>)", jobClass.getName(), jobName); JobDetailFactoryBean factoryBean = new JobDetailFactoryBean(); factoryBean.setName(jobName); factoryBean.setJobClass(jobClass); factoryBean.setDurability(true); return factoryBean; > >

Метод createJobDetail() — это простой и полезный метод для создания заданий.
Для триггеров существуют два варианта: на основе CRON и простые триггеры.

Читайте также:  Source file code html

Сервисы

Теперь базовый планировщик Quartz готов к запуску заданий в нашем Spring Boot — приложении. Далее создадим несколько примеров сервисов, которые будут запускаться планировщиком.

Первый сервис отображает простую статистику членства. Если вы помните, пример в первоначальном проекте был связан с фитнес-клубом. В классе MemberService создаем метод memberStats() :

public void memberStats() < Listmembers = memberRepository.findAll(); int activeCount = 0; int inactiveCount = 0; int registeredForClassesCount = 0; int notRegisteredForClassesCount = 0; for (Member member : members) < if (member.isActive()) < activeCount++; if (CollectionUtils.isNotEmpty(member.getMemberClasses())) < registeredForClassesCount++; >else < notRegisteredForClassesCount++; >> else < inactiveCount++; >> log.info("Member Statics:"); log.info("=============="); log.info("Active member count: <>", activeCount); log.info(" - Registered for Classes count: <>", registeredForClassesCount); log.info(" - Not registered for Classes count: <>", notRegisteredForClassesCount); log.info("Inactive member count: <>", inactiveCount); log.info("========================= java">public void classStats() < ListmemberClasses = classRepository.findAll(); Map memberClassesMap = memberClasses .stream() .collect(Collectors.toMap(MemberClass::getName, c -> 0)); List members = memberRepository.findAll(); for (Member member : members) < if (CollectionUtils.isNotEmpty(member.getMemberClasses())) < for (MemberClass memberClass : member.getMemberClasses()) < memberClassesMap.merge(memberClass.getName(), 1, Integer::sum); >> > log.info("Class Statics:"); log.info("============ <>: <>", k, v)); log.info("========================= java">@Slf4j @Component @DisallowConcurrentExecution public class MemberStatsJob implements Job < @Autowired private MemberService memberService; @Override public void execute(JobExecutionContext context) < log.info("Job ** <>** starting @ <>", context.getJobDetail().getKey().getName(), context.getFireTime()); memberService.memberStats(); log.info("Job ** <> ** completed. Next job scheduled @ <>", context.getJobDetail().getKey().getName(), context.getNextFireTime()); > >

Для сервиса MemberClassService был создан класс MemberClassStatsJob :

@Slf4j @Component @DisallowConcurrentExecution public class MemberClassStatsJob implements Job < @Autowired MemberClassService memberClassService; @Override public void execute(JobExecutionContext context) < log.info("Job ** <>** starting @ <>", context.getJobDetail().getKey().getName(), context.getFireTime()); memberClassService.classStats(); log.info("Job ** <> ** completed. Next job scheduled @ <>", context.getJobDetail().getKey().getName(), context.getNextFireTime()); > >

Расписание заданий

В этом проекте мы хотим, чтобы все задания были запланированы при запуске Spring Boot сервера. Для этого я создал класс QuartzSubmitJobs , который включает в себя четыре простых метода. Два метода создают новые задания, а два метода — соответствующие триггеры.

@Configuration public class QuartzSubmitJobs < private static final String CRON_EVERY_FIVE_MINUTES = "0 0/5 * ? * * *"; @Bean(name = "memberStats") public JobDetailFactoryBean jobMemberStats() < return QuartzConfig.createJobDetail(MemberStatsJob.class, "Member Statistics Job"); >@Bean(name = "memberStatsTrigger") public SimpleTriggerFactoryBean triggerMemberStats(@Qualifier("memberStats") JobDetail jobDetail) < return QuartzConfig.createTrigger(jobDetail, 60000, "Member Statistics Trigger"); >@Bean(name = "memberClassStats") public JobDetailFactoryBean jobMemberClassStats() < return QuartzConfig.createJobDetail(MemberClassStatsJob.class, "Class Statistics Job"); >@Bean(name = "memberClassStatsTrigger") public CronTriggerFactoryBean triggerMemberClassStats(@Qualifier("memberClassStats") JobDetail jobDetail) < return QuartzConfig.createCronTrigger(jobDetail, CRON_EVERY_FIVE_MINUTES, "Class Statistics Trigger"); >>

Запуск Spring Boot

Когда все готово, можно запустить Spring Boot сервер и увидеть инициализацию Quartz:

2019-07-14 14:36:51.651 org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'MyInstanceName' initialized from an externally provided properties instance. 2019-07-14 14:36:51.651 org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.0 2019-07-14 14:36:51.651 org.quartz.core.QuartzScheduler : JobFactory set to: com.gitlab.johnjvester.jpaspec.config.AutowiringSpringBeanJobFactory@79ecc507 2019-07-14 14:36:51.851 o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2019-07-14 14:36:51.901 aWebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning 2019-07-14 14:36:52.051 o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now 2019-07-14 14:36:52.054 o.s.s.quartz.LocalDataSourceJobStore : Freed 0 triggers from 'acquired' / 'blocked' state. 2019-07-14 14:36:52.056 o.s.s.quartz.LocalDataSourceJobStore : Recovering 0 jobs that were in-progress at the time of the last shut-down. 2019-07-14 14:36:52.056 o.s.s.quartz.LocalDataSourceJobStore : Recovery complete. 2019-07-14 14:36:52.056 o.s.s.quartz.LocalDataSourceJobStore : Removed 0 'complete' triggers. 2019-07-14 14:36:52.058 o.s.s.quartz.LocalDataSourceJobStore : Removed 0 stale fired job entries. 2019-07-14 14:36:52.058 org.quartz.core.QuartzScheduler : Scheduler MyInstanceName_$_Instance1 started.

И запуск задания memberStats() :

2019-07-14 14:36:52.096 c.g.j.jpaspec.jobs.MemberStatsJob : Job ** Member Statistics Job ** starting @ Sun Jul 14 14:36:52 EDT 2019 2019-07-14 14:36:52.217 c.g.j.jpaspec.service.MemberService : Member Statics: 2019-07-14 14:36:52.217 c.g.j.jpaspec.service.MemberService : ============== 2019-07-14 14:36:52.217 c.g.j.jpaspec.service.MemberService : Active member count: 7 2019-07-14 14:36:52.217 c.g.j.jpaspec.service.MemberService : - Registered for Classes count: 6 2019-07-14 14:36:52.217 c.g.j.jpaspec.service.MemberService : - Not registered for Classes count: 1 2019-07-14 14:36:52.217 c.g.j.jpaspec.service.MemberService : Inactive member count: 3 2019-07-14 14:36:52.217 c.g.j.jpaspec.service.MemberService : ========================== 2019-07-14 14:36:52.219 c.g.j.jpaspec.jobs.MemberStatsJob : Job ** Member Statistics Job ** completed. Next job scheduled @ Sun Jul 14 14:37:51 EDT 2019

А затем выполнение задания classStats() :

2019-07-14 14:40:00.006 c.g.j.jpaspec.jobs.MemberClassStatsJob : Job ** Class Statistics Job ** starting @ Sun Jul 14 14:40:00 EDT 2019 2019-07-14 14:40:00.021 c.g.j.j.service.MemberClassService : Class Statics: 2019-07-14 14:40:00.022 c.g.j.j.service.MemberClassService : ============= 2019-07-14 14:40:00.022 c.g.j.j.service.MemberClassService : Tennis: 4 2019-07-14 14:40:00.022 c.g.j.j.service.MemberClassService : FitCore 2000: 3 2019-07-14 14:40:00.022 c.g.j.j.service.MemberClassService : Spin: 2 2019-07-14 14:40:00.022 c.g.j.j.service.MemberClassService : Swimming: 4 2019-07-14 14:40:00.022 c.g.j.j.service.MemberClassService : New Class: 0 2019-07-14 14:40:00.022 c.g.j.j.service.MemberClassService : Basketball: 2 2019-07-14 14:40:00.022 c.g.j.j.service.MemberClassService : ========================== 2019-07-14 14:40:00.022 c.g.j.jpaspec.jobs.MemberClassStatsJob : Job ** Class Statistics Job ** completed. Next job scheduled @ Sun Jul 14 14:45:00 EDT 2019

Заключение

В приведенном выше примере я использовал существующий проект на Spring Boot и без особых усилий добавил в него планировщик Quartz. Я создал сервисные методы, которые выполняли простой анализ данных. Эти сервисные методы были запущены классами заданий. Наконец, задания и триггеры были запланированы для запуска.

Читайте также:  Django messages with html

Полный исходный код можно найти здесь.

В следующей статье я покажу как добавить RESTful API для просмотра информации о настройках Quartz.

Источник

Оцените статью