Статьи

Интеграция кварца с пружиной

Когда речь идет о планировании заданий в Java-приложении, Quartz является первым инструментом, который учитывается.
Quartz — это планировщик заданий, поддерживаемый большинством популярных РСУБД. Это действительно удобно и легко интегрируется с пружиной.
Чтобы создать кварцевую схему, вы должны скачать кварцевый дистрибутив и извлечь папку, расположенную в quartz-2.2.3 / docs / dbTables /

Выберите схему кварца в соответствии с базой данных, которую вы используете. В нашем случае мы будем использовать локальную базу данных h2, поэтому я буду использовать схему tables_h2.sql.
Чтобы избежать каких-либо ручных действий sql, я буду использовать функцию инициализации загрузочной базы данных Spring .

Давайте начнем с нашего файла Gradle.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
group 'com.gkatzioura'
version '1.0-SNAPSHOT'
 
apply plugin: 'java'
 
sourceCompatibility = 1.8
 
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.3.RELEASE")
    }
}
 
apply plugin: 'idea'
apply plugin: 'spring-boot'
 
repositories {
    mavenCentral()
}
 
dependencies {
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.3.3.RELEASE'
    compile group: 'org.springframework', name: 'spring-context-support', version: '4.2.4.RELEASE'
    compile group: 'org.springframework', name:'spring-jdbc', version: '4.2.4.RELEASE'
    compile group: 'org.quartz-scheduler', name: 'quartz', version: '2.2.3'
    compile group: 'ch.qos.logback', name: 'logback-core', version:'1.1.3'
    compile group: 'ch.qos.logback', name: 'logback-classic',version:'1.1.3'
    compile group: 'org.slf4j', name: 'slf4j-api',version:'1.7.13'
    compile group: 'com.h2database', name: 'h2', version:'1.4.192'
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

Помимо зависимостей кварца, пружины и h2, мы добавляем зависимости spring-jdbc, поскольку мы хотим, чтобы база данных инициализировалась через spring.

Мы также добавим файл application.yml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
spring:
  datasource:
    continueOnError: true
org:
  quartz:
    scheduler:
      instanceName: spring-boot-quartz-demo
      instanceId: AUTO
    threadPool:
      threadCount: 5
job:
  startDelay: 0
  repeatInterval: 60000
  description: Sample job
  key: StatisticsJob

Из-за операторов создания схемы (отсутствие операторов create, если не существует) я установил в spring.datasource.continueOnError значение false. В зависимости от вашей реализации обходной путь будет отличаться.

Класс приложения

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package com.gkatzioura.springquartz;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
 
/**
 * Created by gkatzioura on 6/6/16.
 */
@SpringBootApplication
public class Application {
 
    public static void main(String[] args) {
 
        SpringApplication springApplication = new SpringApplication();
        ApplicationContext ctx = springApplication.run(Application.class,args);
    }
}

Конфигурация источника данных h2, необходимая для кварца

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.gkatzioura.springquartz.config;
 
import org.h2.jdbcx.JdbcDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import javax.sql.DataSource;
 
/**
 * Created by gkatzioura on 6/6/16.
 */
@Configuration
public class QuartzDataSource {
 
    //Since it a test database it will be located at the temp directory
    private static final String TMP_DIR = System.getProperty("java.io.tmpdir");
 
    @Bean
    public DataSource dataSource() {
 
        JdbcDataSource ds = new JdbcDataSource();
        ds.setURL("jdbc:h2:"+TMP_DIR+"/test");
 
        return ds;
    }
 
}

В нашем случае мы хотим отправлять спам-сообщения каждую минуту, поэтому мы определяем простой почтовый сервис

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package com.gkatzioura.springquartz.service;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
 
/**
 * Created by gkatzioura on 6/7/16.
 */
@Service
public class EmailService {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(EmailService.class);
 
    public void sendSpam() {
 
        LOGGER.info("Should send emails");
    }
 
}

Я также буду реализовывать SpringBeanJobFactory

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.gkatzioura.springquartz.quartz;
 
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
 
/**
 * Created by gkatzioura on 6/7/16.
 */
public class QuartzJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
 
    private transient AutowireCapableBeanFactory beanFactory;
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
 
        beanFactory = applicationContext.getAutowireCapableBeanFactory();
    }
 
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
 
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}

QuartzJobFactory создаст экземпляр задания и будет использовать контекст приложения для внедрения любых определенных зависимостей.

Следующий шаг — определение нашей работы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.gkatzioura.springquartz.job;
 
import com.gkatzioura.springquartz.service.EmailService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
 
/**
 * Created by gkatzioura on 6/6/16.
 */
public class EmailJob implements Job {
 
    @Autowired
    private EmailService cronService;
 
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
 
        cronService.sendSpam();
    }
}

Последний шаг — добавление конфигурации кварца.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
package com.gkatzioura.springquartz.config;
 
 
import com.gkatzioura.springquartz.job.EmailJob;
import com.gkatzioura.springquartz.quartz.QuartzJobFactory;
import org.quartz.SimpleTrigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;
 
import javax.sql.DataSource;
import java.util.Properties;
 
/**
 * Created by gkatzioura on 6/7/16.
 */
@Configuration
public class QuartzConfig {
 
    @Value("${org.quartz.scheduler.instanceName}")
    private String instanceName;
 
    @Value("${org.quartz.scheduler.instanceId}")
    private String instanceId;
 
    @Value("${org.quartz.threadPool.threadCount}")
    private String threadCount;
 
    @Value("${job.startDelay}")
    private Long startDelay;
 
    @Value("${job.repeatInterval}")
    private Long repeatInterval;
 
    @Value("${job.description}")
    private String description;
 
    @Value("${job.key}")
    private String key;
 
    @Autowired
    private DataSource dataSource;
 
    @Bean
    public org.quartz.spi.JobFactory jobFactory(ApplicationContext applicationContext) {
 
        QuartzJobFactory sampleJobFactory = new QuartzJobFactory();
        sampleJobFactory.setApplicationContext(applicationContext);
        return sampleJobFactory;
    }
 
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(ApplicationContext applicationContext) {
 
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
 
        factory.setOverwriteExistingJobs(true);
        factory.setJobFactory(jobFactory(applicationContext));
 
        Properties quartzProperties = new Properties();
        quartzProperties.setProperty("org.quartz.scheduler.instanceName",instanceName);
        quartzProperties.setProperty("org.quartz.scheduler.instanceId",instanceId);
        quartzProperties.setProperty("org.quartz.threadPool.threadCount",threadCount);
 
        factory.setDataSource(dataSource);
 
        factory.setQuartzProperties(quartzProperties);
        factory.setTriggers(emailJobTrigger().getObject());
 
        return factory;
    }
 
    @Bean(name = "emailJobTrigger")
    public SimpleTriggerFactoryBean emailJobTrigger() {
 
 
        SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
        factoryBean.setJobDetail(emailJobDetails().getObject());
        factoryBean.setStartDelay(startDelay);
        factoryBean.setRepeatInterval(repeatInterval);
        factoryBean.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
        factoryBean.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);
        return factoryBean;
    }
 
    @Bean(name = "emailJobDetails")
    public JobDetailFactoryBean emailJobDetails() {
 
        JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
        jobDetailFactoryBean.setJobClass(EmailJob.class);
        jobDetailFactoryBean.setDescription(description);
        jobDetailFactoryBean.setDurability(true);
        jobDetailFactoryBean.setName(key);
 
        return jobDetailFactoryBean;
    }
}

Мы создали фабрику bean-компонента планировщика с использованием определенной нами QuartzJobFactory, и мы зарегистрировали триггеры, необходимые для запуска наших заданий. В нашем случае мы реализовали простой триггер, работающий каждую минуту.

Вы можете найти исходный код на GitHub

Ссылка: Интеграция Quartz с Spring от нашего партнера JCG Эммануила Гкатзиураса в блоге gkatzioura .