Hinzufügen von Quarz zu Spring Boot

Hallo nochmal. Speziell für Studierende des Kurses "Developer on the Spring Framework" wurde eine Übersetzung eines interessanten Artikels erstellt.




In meinem Artikel „Spezifikationen für die Rettung“ habe ich gezeigt, wie Sie die JPA-Spezifikation in Spring Boot verwenden können, um die Filterung in der RESTful-API zu implementieren. Dann wurde im Artikel "Testen dieser Spezifikationen" gezeigt, wie dieselben Spezifikationen getestet werden.

Im nächsten Schritt wollte ich zeigen, wie ein Taskplaner derselben Spring Boot-Anwendung hinzugefügt wird.

Quartz Task Scheduler


Das Spring-Team vereinfacht weiterhin die Java-Entwicklung, indem es verschiedene Spring Boot Starter-Plug-ins über eine einfache Maven-Abhängigkeit bereitstellt.

In diesem Artikel werde ich mich auf den Starter von Quartz Scheduler konzentrieren , der dem Spring Boot-Projekt mit der folgenden Abhängigkeit hinzugefügt werden kann:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> 

Die Implementierung ist recht einfach und wird hier beschrieben. Die vollständige Liste der aktuellen Spring Boot Starter finden Sie hier .

Anpassung


Unter Verwendung eines von David Kiss veröffentlichten Papiers besteht der erste Schritt darin, die automatische Bindung für Quarzaufträge hinzuzufügen:

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

Fügen Sie als Nächstes die grundlegende Quarzkonfiguration hinzu:

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

Sie können die in der scheduler() -Methode verwendeten Eigenschaften ausgeben, aber ich habe speziell beschlossen, dieses Beispiel zu vereinfachen.

Anschließend werden statische Methoden hinzugefügt, die eine programmgesteuerte Möglichkeit zum Erstellen von Aufgaben und Auslösern bieten:

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

Die Methode createJobDetail() ist eine einfache und nützliche Methode zum Erstellen von Aufgaben.
Es gibt zwei Optionen für Trigger: basierend auf CRON und einfache Trigger.

Dienstleistungen


Der Quartz Base Scheduler kann jetzt Aufgaben in unserer Spring Boot-Anwendung ausführen. Als Nächstes erstellen wir einige Beispiele für Dienste, die vom Scheduler gestartet werden.

Der erste Dienst zeigt einfache Mitgliedschaftsstatistiken an. Wenn Sie sich erinnern, bezog sich das Beispiel im ursprünglichen Projekt auf einen Fitnessclub. MemberService in der MemberService Klasse die MemberService memberStats() -Methode:

 public void memberStats() { List<Member> members = 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("=========================="); } 

Erstellen Sie eine classStats() Methode in classStats() , um die Interessen an Fitnessclubklassen zu verfolgen:

 public void classStats() { List<MemberClass> memberClasses = classRepository.findAll(); Map<String, Integer> memberClassesMap = memberClasses .stream() .collect(Collectors.toMap(MemberClass::getName, c -> 0)); List<Member> 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("============="); memberClassesMap.forEach((k,v) -> log.info("{}: {}", k, v)); log.info("=========================="); } 

Missionen


Um den Dienstcode auszuführen, müssen Sie den entsprechenden Job erstellen. Für MemberService ich eine MemberStatsJob MemberService erstellt:

 @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()); } } 

Für den MemberClassService Dienst wurde die MemberClassService Klasse erstellt:

 @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()); } } 

Task Schedule


In diesem Projekt sollen alle Aufgaben geplant werden, wenn der Spring Boot-Server gestartet wird. Zu diesem QuartzSubmitJobs ich die QuartzSubmitJobs Klasse erstellt, die vier einfache Methoden enthält. Zwei Methoden erstellen neue Aufgaben und zwei Methoden erstellen entsprechende Trigger.

 @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 starten


Wenn alles fertig ist, können Sie den Spring Boot-Server starten und die Quarz-Initialisierung anzeigen:

 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 ossconcurrent.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 ossquartz.SchedulerFactoryBean : Starting Quartz Scheduler now 2019-07-14 14:36:52.054 ossquartz.LocalDataSourceJobStore : Freed 0 triggers from 'acquired' / 'blocked' state. 2019-07-14 14:36:52.056 ossquartz.LocalDataSourceJobStore : Recovering 0 jobs that were in-progress at the time of the last shut-down. 2019-07-14 14:36:52.056 ossquartz.LocalDataSourceJobStore : Recovery complete. 2019-07-14 14:36:52.056 ossquartz.LocalDataSourceJobStore : Removed 0 'complete' triggers. 2019-07-14 14:36:52.058 ossquartz.LocalDataSourceJobStore : Removed 0 stale fired job entries. 2019-07-14 14:36:52.058 org.quartz.core.QuartzScheduler : Scheduler MyInstanceName_$_Instance1 started. 


Führen Sie den memberStats() -Job aus:

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

Und dann mache ich den classStats() Job:

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

Fazit


Im obigen Beispiel habe ich ein vorhandenes Projekt für Spring Boot verwendet und ohne großen Aufwand den Quartz-Scheduler hinzugefügt. Ich habe Servicemethoden entwickelt, die eine einfache Datenanalyse durchführen. Diese Dienstmethoden wurden von Jobklassen gestartet. Schließlich sollten Jobs und Trigger ausgeführt werden.

Den vollständigen Quellcode finden Sie hier .

Im nächsten Artikel werde ich zeigen, wie eine RESTful-API hinzugefügt wird, um Informationen zu Quarzeinstellungen anzuzeigen.

Source: https://habr.com/ru/post/de475996/


All Articles