Adicionando Quartz à Inicialização Spring

Olá novamente. Especialmente para os alunos do curso "Developer on the Spring Framework" preparou uma tradução de um artigo interessante.




No meu artigo “Especificações para o Rescue”, mostrei como você pode usar a Especificação JPA no Spring Boot para implementar a filtragem na API RESTful. Em seguida, no artigo "Testando essas especificações" , foi mostrado como testar essas mesmas especificações.

Na próxima etapa, decidi demonstrar como adicionar um agendador de tarefas ao mesmo aplicativo Spring Boot.

Agendador de tarefas de quartzo


A equipe do Spring continua a facilitar o desenvolvimento do Java, fornecendo vários plugins do Spring Boot Starter por meio de uma dependência simples do maven.

Neste artigo, vou focar no iniciador do Quartz Scheduler , que pode ser adicionado ao projeto Spring Boot com a seguinte dependência:

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

A implementação é bastante simples e é descrita aqui . Você pode ver a lista completa do Spring Boot Starter atual aqui .

Personalização


Usando um artigo publicado por David Kiss , o primeiro passo é adicionar a ligação automática para trabalhos de quartzo:

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

Em seguida, adicione a configuração básica do 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; } } 

Você pode colocar as propriedades usadas no método scheduler() , mas eu decidi especificamente simplificar este exemplo.

Em seguida, são adicionados métodos estáticos que fornecem uma maneira programática de criar tarefas e gatilhos:

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

O método createJobDetail() é um método simples e útil para criar tarefas.
Existem duas opções para gatilhos: baseados no CRON e gatilhos simples.

Serviços


O planejador básico do Quartz está pronto para executar tarefas em nosso aplicativo Spring Boot. Em seguida, criaremos alguns exemplos de serviços que serão lançados pelo planejador.

O primeiro serviço exibe estatísticas simples de associação. Se você se lembra, o exemplo no projeto original estava relacionado a uma academia de ginástica. Na classe MemberService crie o método memberStats() :

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

Para rastrear interesses em aulas de academias, crie um método classStats() em classStats() :

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

Missões


Para executar o código de serviço, você deve criar o trabalho apropriado. Para MemberService criei uma MemberStatsJob trabalho MemberStatsJob :

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

Para o serviço MemberClassService , a classe MemberClassService foi criada:

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

Agenda de tarefas


Neste projeto, queremos que todas as tarefas sejam agendadas quando o servidor Spring Boot for iniciado. Para fazer isso, criei a classe QuartzSubmitJobs , que inclui quatro métodos simples. Dois métodos criam novas tarefas e dois métodos criam gatilhos correspondentes.

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

Iniciar o Spring Boot


Quando tudo estiver pronto, você pode iniciar o servidor Spring Boot e ver a inicialização do 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 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. 


E executando o trabalho memberStats() :

 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 

E, em seguida, executando o trabalho classStats() :

 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 

Conclusão


No exemplo acima, usei um projeto existente no Spring Boot e sem muito esforço adicionei o planejador Quartz a ele. Criei métodos de serviço que executavam análises simples de dados. Esses métodos de serviço foram lançados por classes de trabalho. Por fim, tarefas e gatilhos foram agendados para execução.

O código fonte completo pode ser encontrado aqui .

No próximo artigo, mostrarei como adicionar uma API RESTful para visualizar informações de configurações do Quartz.

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


All Articles