如果对quartz集群原理和配置不太了解,大家可参考文章:Quartz集群原理及配置应用,这里我就不细说了
SpringBoot实现定时任务两种方式:
1、基于 spring的@Scheduled注解;
可参考文章:SpringBoot整合Quartz定时任务(基于注解方式),这种方式比较便捷,在单台服务部署情况下可使用,若部署多台机器,到了时间点,便会同时均开始这个执行定时任务(问题)。
2、使用Quartz(集群环境)实现:
2.1 Quzrtz集群的实现方式在于11张表,集群节点相互之间不通信,而是通过相同的数据库表来感知到另一节点的,因此Quartz集群依赖于数据库,必须先创建数据库表,数据表是官方提供的 ,有11张表,如下:
项目结构:
2.2 创建maven项目pom.xml添加依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test.quartz</groupId>
<artifactId>quartz</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
<!--spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.1.0.RELEASE</version>
<exclusions>
<exclusion>
<artifactId>log4j-api</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.1.0.RELEASE</version>
<exclusions>
<exclusion>
<artifactId>log4j-api</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>2.1.0.RELEASE</version>
</dependency>
<!--commons-lang -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
<exclusions>
<exclusion>
<artifactId>netty</artifactId>
<groupId>io.netty</groupId>
</exclusion>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
</dependencies>
<build>
<!--<finalName>${artifactId}-${version}</finalName>-->
<!--<plugins>-->
<!--<plugin>-->
<!--<groupId>org.apache.maven.plugins</groupId>-->
<!--<artifactId>maven-compiler-plugin</artifactId>-->
<!--<configuration>-->
<!--<source>1.8</source>-->
<!--<target>1.8</target>-->
<!--</configuration>-->
<!--</plugin>-->
<!--</plugins>-->
<!--打包后的jar包名-->
<finalName>${artifactId}-${version}</finalName>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>target/jacoco.exec</dataFile>
</configuration>
</execution>
</executions>
<configuration>
<destFile>
target/jacoco.exec
</destFile>
</configuration>
</plugin>
<!-- progurad-maven-plugin -->
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<version>2.0.11</version>
<executions>
<execution>
<!--混淆阶段是package-->
<phase>package</phase>
<goals>
<goal>proguard</goal>
</goals>
</execution>
</executions>
<!--配置项-->
<configuration>
<!--指定proguard的版本-->
<proguardVersion>6.0.3</proguardVersion>
<!--多模块项目打包重点一,添加依赖子模块到混淆范围,混淆后都会放在classes中-->
<assembly>
<inclusions>
<inclusion>
<groupId>${project.groupId}</groupId>
<artifactId>quartz</artifactId>
</inclusion>
</inclusions>
</assembly>
<!--输入的jar名-->
<injar>${project.build.finalName}.jar</injar>
<!--输出的jar名,当与injar相同时,会将混淆后的class文件放入jar中,源class文件放入项目名_proguard_base.jar中-->
<outjar>${project.build.finalName}.jar</outjar>
<!--混淆是默认开启的。混淆使类和类成员名称变成短的随机名,被各种“-keep”选项保护的类、类成员除外。对调试有用的内部属性(源文件名称,变量名称,行号)将被删除。-->
<obfuscate>true</obfuscate>
<options>
<!--指定java版本-->
<option>-target 1.8</option>
<!--忽略警告-->
<option>-ignorewarnings</option>
<!--排除依赖-->
<!-- 不做收缩(删除注释、未被引用代码)-->
<option>-dontshrink</option>
<!-- 不做优化(变更代码实现逻辑)-->
<option>-dontoptimize</option>
<!-- 确定统一的混淆类的成员名称来增加混淆-->
<option>-useuniqueclassmembernames</option>
<!--保护代码中的Annotation不被混淆,在使用如fastJSON时的实体映射-->
<option>-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
</option>
<!--混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代-->
<option>-adaptclassstrings</option>
<!--保留启动类不混淆-->
<option>-keep class com.iflyrec.jk.application.TaskCenterApplication { *; }</option>
<!--保留实体类不混淆-->
<option>-keep class com.test.quartz.** { *; }</option>
<!--包名不混淆-->
<option>-keeppackagenames</option>
<!--参数名不混淆-->
<!--<option>-keepparameternames</option>-->
<!--getset不混淆-->
<!--<option>-keepclassmembers public class * {void set*(***);*** get*();}</option>-->
<!--<option>-keep class !com.iflyrec.jk.** {*;}</option>-->
<!--<option>-keepclassmembers class * {-->
<!--@org.springframework.beans.factory.annotation.Autowired *;-->
<!--@org.springframework.beans.factory.annotation.Value *;-->
<!--}-->
<!--</option>-->
</options>
<!--必要的工具包-->
<libs>
<!-- Include main JAVA library required.-->
<lib>${java.home}/lib/rt.jar</lib>
<!--<lib>${java.home}/lib/jce.jar</lib>-->
</libs>
</configuration>
<!--依赖包-->
<dependencies>
<!--proguard-->
<dependency>
<groupId>net.sf.proguard</groupId>
<artifactId>proguard-base</artifactId>
<version>6.0.3</version>
</dependency>
</dependencies>
</plugin>
<!-- Maven assembly must be run after proguard obfuscation so it take already obfuscated files.-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.4.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<!--指定主类-->
<mainClass>com.test.quartz.application.CenterApplication</mainClass>
<!--多模块项目打包重点二,需要在构建可执行jar包时去除加入混淆的子模块-->
<!-- <excludes>
<exclude>
<groupId>${project.groupId}</groupId>
<artifactId>子模块名称</artifactId>
</exclude>
</excludes>-->
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.3 创建springboot启动类(CenterApplication):
package com.test.quartz.application;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
/**
* @author xqli7@iflytek.com
* @date 2019/2/13
* @description: springboot启动类
*/
@SpringBootApplication
@ComponentScan(basePackages = "com.test.quartz.*")
@EnableScheduling
public class CenterApplication {
private static final Logger LOG = LoggerFactory.getLogger(CenterApplication.class);
private static final String START_PACKAGE = "com.test.quartz.";
public static class CustomGenerator implements BeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
String beanName = beanDefinition.getBeanClassName();
if (beanName.startsWith(START_PACKAGE)) {
return beanName;
}
try {
Class<?> aClass = Class.forName(beanName);
Component annotation = aClass.getAnnotation(Component.class);
if (annotation == null) {
return beanName;
}
String value = annotation.value();
if (StringUtils.isNotBlank(value)) {
return value;
}
} catch (Exception e) {
LOG.error("generateBeanName method throw a exception", e);
}
return beanName;
}
}
public static void main(String... args) {
ApplicationContext ctx = new SpringApplicationBuilder(CenterApplication.class)
.beanNameGenerator(new CustomGenerator())
.run(args);
String[] activeProfiles = ctx.getEnvironment().getActiveProfiles();
for (String profile : activeProfiles) {
LOG.info("Spring Boot 使用profile为: application-{}.yml", profile);
}
}
}
2.4 配置文件:
application.yml
spring:
profiles:
active: win
#active: linux
application-win.yml(window上的配置,一般喜欢将window和linux分开来写,后期部署时方便切换,liunx用不上暂时就不放了):
server:
port: 7008
logging:
config: classpath:properties/logback-spring.xml
#config: classpath:properties/logback-spring.xml
charset:
encoding: utf-8
spring:
quartz:
job-store-type: jdbc
日志文件logback-spring.xml ,通过在配置文件中指定生效的环境和日志的级别 :
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<root level="INFO">
<!--<appender-ref ref="ROLLIN"/>-->
</root>
<logger name="org.springframework.web" level="INFO"/>
</configuration>
QuartzConfiguration配置文件:org.quartz.jobStore.isClustered 是否启用集群,设置为true
package com.test.quartz.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.util.Properties;
/**
* @author xqli7@iflytek.com
* @date 2019/2/13
* @description: quartz配置
*/
@Configuration
public class QuartzConfiguration implements SchedulerFactoryBeanCustomizer {
@Autowired
DataSource dataSource;
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
schedulerFactoryBean.setQuartzProperties(quartzProperties());
schedulerFactoryBean.setStartupDelay(2);
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setDataSource(dataSource);
//如果这个覆盖配置为false,quratz启动以后将以数据库的数据为准,配置文件的修改不起作用。
schedulerFactoryBean.setOverwriteExistingJobs(true);
}
/**
* 设置参数
*
* @return quartz参数
*/
private Properties quartzProperties() {
Properties prop = new Properties();
// 调度标识名 集群中每一个实例都必须使用相同的名称
prop.put("org.quartz.scheduler.instanceName", "quartz-01");
// ID设置为自动获取 每一个必须不同
prop.put("org.quartz.scheduler.instanceId", "AUTO");
// 数据保存方式为持久化
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
// 数据库方言
prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
// 表前缀
prop.put("org.quartz.jobStore.tablePrefix", "t_jk_qrtz_");
// 是否启用集群功能
prop.put("org.quartz.jobStore.isClustered", "true");
// 设置一个频度(毫秒),用于实例报告给集群中的其他实例。这会影响到侦测失败实例的敏捷度。它只用于设置了 isClustered 为 true 的时候。
prop.put("org.quartz.jobStore.clusterCheckinInterval", "20000");
// 这是 JobStore 能处理的错过触发的 Trigger 的最大数量。处理太多(超过两打) 很快会导致数据库表被锁定够长的时间,这样就妨碍了触发别的(还未错过触发) trigger 执行的性能。
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
// 当前时间与下一次执行的时间差大于改值时认为missFire(错过触发),根据missFire原则处理任务;若小于该值直接执行任务。默认60000(60秒)
prop.put("org.quartz.jobStore.misfireThreshold", "120000");
// 值为 True 时告诉 Quartz (当使用 JobStoreTX 或 CMT 时),调用 JDBC 连接的 setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) 方法,设置事务隔离级别为串行
// 这能助于防止某些数据库在高负荷和长事物时的锁超时。
prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
// 执行任务的线程池配置
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "10");
prop.put("org.quartz.threadPool.threadPriority", "5");
// 指定Quartz生成的线程是否继承初始化线程的上下文类加载器
prop.put("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", "true");
// 跳过更新检查
prop.put("org.quartz.scheduler.skipUpdateCheck", true);
//quartz 插件配置
prop.put("org.quartz.plugin.triggHistory.class", "org.quartz.plugins.history.LoggingJobHistoryPlugin");
prop.put("org.quartz.plugin.shutdownhook.class", "org.quartz.plugins.management.ShutdownHookPlugin");
prop.put("org.quartz.plugin.shutdownhook.cleanShutdown", "true");
return prop;
}
}
mysql配置文件:MysqlConfig
package com.test.quartz.configuration;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
/**
* @author xqli7@iflytek.com
* @date 2019/2/13
* @description: mysql连接配置
*/
@Component
public class MysqlConfig {
@Bean
public DataSource dataSource() {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/hiseejk?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai");
hikariDataSource.setUsername("root");
hikariDataSource.setPassword("root");
hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariDataSource.setPoolName("hiseejk");
// 连接超时时间
hikariDataSource.setConnectionTimeout(30000);
// 最大池大小
hikariDataSource.setMaximumPoolSize(20);
// 默认连接数
hikariDataSource.setMinimumIdle(10);
hikariDataSource.setIdleTimeout(600000);
hikariDataSource.setMaxLifetime(1800000);
return hikariDataSource;
}
}
任务实体类:
package com.test.quartz.quartz;
import java.util.Map;
/**
* @author xqli7@iflytek.com
* @date 2019/2/13
* @description: 任务实体类
*/
public class JobView {
private String name;
private String group;
private String cronExpression;
private String nextFireTime;
private String status;
Map<String, Object> params;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
public String getNextFireTime() {
return nextFireTime;
}
public void setNextFireTime(String nextFireTime) {
this.nextFireTime = nextFireTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Map<String, Object> getParams() {
return params;
}
public void setParams(Map<String, Object> params) {
this.params = params;
}
}
任务工具类QuartzJobUtils:
package com.test.quartz.quartz;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.curator.shaded.com.google.common.collect.Lists;
import org.apache.curator.shaded.com.google.common.collect.Maps;
import org.apache.http.client.utils.DateUtils;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* @author xqli7@iflytek.com
* @date 2019/2/13
* @description:
*/
public class QuartzJobUtils {
private static final Logger LOG = LoggerFactory.getLogger(QuartzJobUtils.class);
private QuartzJobUtils() {
super();
}
/**
* 创建quartz定时任务
*
* @param scheduler 调度器
* @param jobClass 任务class,必须继承QuartzJobBean
* @param cronExpression cron表达式
* @param jobDataMap 任务信息
*/
public static void createJob(Scheduler scheduler, String name, Class<? extends QuartzJobBean> jobClass, String cronExpression, JobDataMap jobDataMap) {
//任务所属分组
String group = "TEST";
LOG.info("----createJob start ,name:{},group:{}", name, group);
if (exist(scheduler, name, group)) {
LOG.info("----createJob fail ,job already existed,name:{},group:{}", name, group);
return;
}
//cron表达式封装
//missfire处理 withMisfireHandlingInstructionDoNothing 错过触发时间时,不执行执行的,等待下一个执行时间
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
//创建任务
JobDetail jobDetail;
if (jobDataMap != null) {
//requestRecovery(true)指在集群中,一个scheduler执行job失败,将会被另外一个scheduler执行
jobDetail = JobBuilder.newJob(jobClass).withIdentity(name, group).usingJobData(jobDataMap).requestRecovery(true).build();
} else {
jobDetail = JobBuilder.newJob(jobClass).withIdentity(name, group).requestRecovery(true).build();
}
//创建任务触发器
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group).withSchedule(scheduleBuilder).build();
//将触发器与任务绑定到调度器内
try {
Date firstFireTime = scheduler.scheduleJob(jobDetail, trigger);
LOG.info("----createJob success,firstFireTime:{}", DateFormatUtils.format(firstFireTime, "yyyy-MM-dd HH:mm:ss"));
} catch (SchedulerException e) {
LOG.error("-----createJob exception", e);
}
}
/**
* 创建quartz定时任务
*
* @param scheduler 调度器
* @param jobClass 任务class,必须继承QuartzJobBean
* @param cronExpression cron表达式
*/
public static void createJob(Scheduler scheduler, String name, Class<? extends QuartzJobBean> jobClass, String cronExpression) {
createJob(scheduler, name, jobClass, cronExpression, null);
}
public static void close(Scheduler scheduler, JobExecutionContext context) {
TriggerKey triggerKey = context.getTrigger().getKey();
JobKey jobKey = context.getJobDetail().getKey();
LOG.info("----quartzJob close,jobName:{}", jobKey.getName());
try {
// 停止触发器
scheduler.pauseTrigger(triggerKey);
// 移除触发器
scheduler.unscheduleJob(triggerKey);
// 删除任务
scheduler.deleteJob(jobKey);
LOG.info("----quartzJob success");
} catch (SchedulerException e) {
LOG.error("----quartzJob close exception", e);
}
}
/**
* 判断任务是否存在
*
* @param scheduler 调度器
* @param name 任务名称
* @param group 任务分组
* @return true==存在 false==不存在
*/
public static boolean exist(Scheduler scheduler, String name, String group) {
JobDetail jobDetail;
JobKey jobKey = new JobKey(name, group);
TriggerKey triggerKey = new TriggerKey(name, group);
try {
jobDetail = scheduler.getJobDetail(jobKey);
if (jobDetail != null) {
if (Trigger.TriggerState.ERROR.equals(scheduler.getTriggerState(triggerKey))) {
LOG.info("-----定时任务状态异常,恢复状态,job name :{}", name);
scheduler.resumeJob(jobKey);
}
return true;
}
} catch (SchedulerException e) {
LOG.error("-----exist exception", e);
}
return false;
}
/**
* 获取定时任务列表
*
* @param scheduler 任务调度器
* @return
*/
public static List<JobView> jobs(Scheduler scheduler) {
List<JobView> jobViews = Lists.newArrayList();
try {
for (String groupName : scheduler.getJobGroupNames()) {
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
String jobName = jobKey.getName();
String jobGroup = jobKey.getGroup();
// name and group
JobView jobView = new JobView();
jobView.setName(jobName);
jobView.setGroup(jobGroup);
// params
JobDetail qJobDetail = scheduler.getJobDetail(jobKey);
if (null != qJobDetail.getJobDataMap()) {
Map<String, Object> params = Maps.newHashMap();
params.putAll(qJobDetail.getJobDataMap());
jobView.setParams(params);
}
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
// 应该只有一个
for (Trigger trigger : triggers) {
trigger.getNextFireTime();
if (trigger instanceof CronTrigger) {
CronTrigger cronTrigger = (CronTrigger) trigger;
String cronExpr = cronTrigger.getCronExpression();
jobView.setCronExpression(cronExpr);
}
jobView.setNextFireTime(DateUtils.formatDate(trigger.getNextFireTime(), "yyyy-MM-dd HH:mm:ss"));
Trigger.TriggerState state = scheduler.getTriggerState(trigger.getKey());
jobView.setStatus(state.name());
}
jobViews.add(jobView);
}
}
} catch (Exception e) {
return Collections.emptyList();
}
return jobViews;
}
}
创建具体任务类(写需要实现的一些逻辑):
package com.test.quartz.quartz;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;
/**
* @author xqli7@iflytek.com
* @date 2019/2/13
* @description: 具体任务类
*/
@DisallowConcurrentExecution
public class TestQuartzJob extends QuartzJobBean {
private static final Logger LOGGER = LoggerFactory.getLogger(TestQuartzJob.class);
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) {
LOGGER.info("-----TestQuartzJob start");
//这里写具体的实现业务逻辑
try {
//线程睡眠是因为需要测试,防止程序过快执行
Thread.sleep(5000);
System.out.println("任务执行了" + "================================");
} catch (Exception e) {
LOGGER.error("休眠时间异常", e);
}
}
}
最后再将所有任务初始化:QuartzJobManager
package com.test.quartz.quartz;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author xqli7@iflytek.com
* @date 2019/2/13
* @description: 初始化任务
*/
@Component
public class QuartzJobManager {
@Autowired
private Scheduler scheduler;
/**
* 初始化任务的创建
* 判断任务是否存在,不存在则创建
*/
@PostConstruct
public void initJob() {
//每分钟执行一次
QuartzJobUtils.createJob(scheduler, TestQuartzJob.class.getSimpleName(), TestQuartzJob.class,
"0 0/1 * * * ? ");
}
}
执行结果:
若是部署多台机器,到了时间点,只有一台会执行,其他不会执行。(测试方案:修改tamcat端口号,启动多台服务)
若多个节点其中一个scheduler执行job失败,将会被另外一个scheduler执行。(测试方案:修改tamcat端口号,启动多台服务,将正在执行这个任务的服务关掉)
问题:
任务节点部署在不同服务器时间同步问题(暂未解决)
代码地址:https://download.csdn.net/download/qq_33914912/10957062 (积分默认的,我想改成免费的,如果有的会的教下我去改)