SpringBoot Quartz使用

Pom中引用quartz

<?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">
    <parent>
        <artifactId>fkandy-multi-backend-parent</artifactId>
        <groupId>vip.fkandy</groupId>
        <version>4.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>fkandy-common-quartz</artifactId>
    <dependencies>
        <!-- 定时任务 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.mchange</groupId>
                    <artifactId>c3p0</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
</project>

配置SchedulerFactoryBean

其中SchedulerFactoryBean是Spring-Context提供的辅助类,由SchedulerFactoryBean创建Scheduler

package vip.fkandy.quartz.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.util.Properties;

/**
 * 定时任务配置,注释掉整个类之后将使用内存数据库
 *
 * @author admin
 */
@Configuration
public class ScheduleConfig {
	@Bean
	public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
		SchedulerFactoryBean factory = new SchedulerFactoryBean();
		factory.setDataSource(dataSource);

		// quartz参数
		Properties prop = new Properties();
		//调度标识名 集群中每一个实例都必须使用相同的名称 (区分特定的调度器实例)
		prop.put("org.quartz.scheduler.instanceName", "FkAndyScheduler");
		//ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的)
		prop.put("org.quartz.scheduler.instanceId", "AUTO");

		// 线程池配置
		prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
		prop.put("org.quartz.threadPool.threadCount", "20");
		prop.put("org.quartz.threadPool.threadPriority","5");
		// JobStore持久化配置
		prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");

		// 集群配置true 为集群 false不是集群
		prop.put("org.quartz.jobStore.isClustered", "true");
		//调度实例失效的检查时间间隔
		prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
		prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
		prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");

		// sqlserver 启用
		// prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");

		//容许的最大作业延长时间
		prop.put("org.quartz.jobStore.misfireThreshold", "12000");

		//表的前缀
		prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
		factory.setQuartzProperties(prop);

		factory.setSchedulerName("FkAndyScheduler");
		// 延时启动
		factory.setStartupDelay(1);
		factory.setApplicationContextSchedulerContextKey("applicationContextKey");
		// 可选,QuartzScheduler
		// 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
		factory.setOverwriteExistingJobs(true);
		// 设置自动启动,默认为true
		factory.setAutoStartup(true);

		return factory;
	}
}

 定义AbstractQuartzJob用来记录日志

package vip.fkandy.quartz.util;

import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import vip.fkandy.common.constant.Constants;
import vip.fkandy.common.constant.ScheduleConstants;
import vip.fkandy.common.utils.ExceptionUtil;
import vip.fkandy.common.utils.StringUtils;
import vip.fkandy.common.utils.bean.BeanUtils;
import vip.fkandy.common.utils.spring.SpringUtils;
import vip.fkandy.quartz.domain.SysJob;
import vip.fkandy.quartz.domain.SysJobLog;
import vip.fkandy.quartz.service.ISysJobLogService;

/**
 * 抽象quartz调用
 *
 * @author admin
 */
public abstract class AbstractQuartzJob implements Job {
	private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);

	/**
	 * 线程本地变量
	 */
	private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		SysJob sysJob = new SysJob();
		BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
		try {
			before(context, sysJob);
			if (sysJob != null) {
				doExecute(context, sysJob);
			}
			after(context, sysJob, null);
		} catch (Exception e) {
			log.error("任务执行异常  - :", e);
			after(context, sysJob, e);
		}
	}

	/**
	 * 执行前
	 *
	 * @param context 工作执行上下文对象
	 * @param sysJob  系统计划任务
	 */
	protected void before(JobExecutionContext context, SysJob sysJob) {
		threadLocal.set(new Date());
	}

	/**
	 * 执行后
	 *
	 * @param context 工作执行上下文对象
	 * @param sysJob  系统计划任务
	 */
	protected void after(JobExecutionContext context, SysJob sysJob, Exception e) {
		Date startTime = threadLocal.get();
		threadLocal.remove();

		final SysJobLog sysJobLog = new SysJobLog();
		sysJobLog.setJobName(sysJob.getJobName());
		sysJobLog.setJobGroup(sysJob.getJobGroup());
		sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
		sysJobLog.setStartTime(startTime);
		sysJobLog.setEndTime(new Date());
		long runMs = sysJobLog.getEndTime().getTime() - sysJobLog.getStartTime().getTime();
		sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
		if (e != null) {
			sysJobLog.setStatus(Constants.FAIL);
			String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
			sysJobLog.setExceptionInfo(errorMsg);
		} else {
			sysJobLog.setStatus(Constants.SUCCESS);
		}

		// 写入数据库当中
		SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
	}

	/**
	 * 执行方法,由子类重载
	 *
	 * @param context 工作执行上下文对象
	 * @param sysJob  系统计划任务
	 * @throws Exception 执行过程中的异常
	 */
	protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
}

 创建Cron表达式工具类

package vip.fkandy.quartz.util;

import java.text.ParseException;
import java.util.Date;

import org.quartz.CronExpression;

/**
 * cron表达式工具类
 *
 * @author admin
 */
public class CronUtils {
	/**
	 * 返回一个布尔值代表一个给定的Cron表达式的有效性
	 *
	 * @param cronExpression Cron表达式
	 * @return boolean 表达式是否有效
	 */
	public static boolean isValid(String cronExpression) {
		return CronExpression.isValidExpression(cronExpression);
	}

	/**
	 * 返回一个字符串值,表示该消息无效Cron表达式给出有效性
	 *
	 * @param cronExpression Cron表达式
	 * @return String 无效时返回表达式错误描述,如果有效返回null
	 */
	public static String getInvalidMessage(String cronExpression) {
		try {
			new CronExpression(cronExpression);
			return null;
		} catch (ParseException pe) {
			return pe.getMessage();
		}
	}

	/**
	 * 返回下一个执行时间根据给定的Cron表达式
	 *
	 * @param cronExpression Cron表达式
	 * @return Date 下次Cron表达式执行时间
	 */
	public static Date getNextExecution(String cronExpression) {
		try {
			CronExpression cron = new CronExpression(cronExpression);
			return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
		} catch (ParseException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}
}
package vip.fkandy.quartz.util;

import org.quartz.JobExecutionContext;
import vip.fkandy.quartz.domain.SysJob;

/**
 * 定时任务处理(允许并发执行)
 *
 * @author admin
 */
public class QuartzJobExecution extends AbstractQuartzJob {
	@Override
	protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {
		JobInvokeUtil.invokeMethod(sysJob);
	}
}
package vip.fkandy.quartz.util;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import vip.fkandy.quartz.domain.SysJob;

/**
 * 定时任务处理(禁止并发执行)
 *
 * @author admin
 */
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob {
	@Override
	protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {
		JobInvokeUtil.invokeMethod(sysJob);
	}
}

 Scheduler工具类,直接被业务调度

package vip.fkandy.quartz.util;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import vip.fkandy.common.constant.ScheduleConstants;
import vip.fkandy.common.exception.job.TaskException;
import vip.fkandy.common.exception.job.TaskException.Code;
import vip.fkandy.quartz.domain.SysJob;

/**
 * 定时任务工具类
 *
 * @author admin
 */
public class ScheduleUtils {
	/**
	 * 得到quartz任务类
	 *
	 * @param sysJob 执行计划
	 * @return 具体执行任务类
	 */
	private static Class<? extends Job> getQuartzJobClass(SysJob sysJob) {
		boolean isConcurrent = "0".equals(sysJob.getConcurrent());
		return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
	}

	/**
	 * 构建任务触发对象
	 */
	public static TriggerKey getTriggerKey(Long jobId, String jobGroup) {
		return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
	}

	/**
	 * 构建任务键对象
	 */
	public static JobKey getJobKey(Long jobId, String jobGroup) {
		return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
	}

	/**
	 * 创建定时任务
	 */
	public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException {
		Class<? extends Job> jobClass = getQuartzJobClass(job);
		// 构建job信息
		Long jobId = job.getJobId();
		String jobGroup = job.getJobGroup();
		JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();

		// 表达式调度构建器并设置定时任务策略
		CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
		cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);

		// 按新的cronExpression表达式构建一个新的trigger
		CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
				.withSchedule(cronScheduleBuilder).build();

		// 放入参数,运行时的方法可以获取
		jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);

		// 判断是否存在
		if (scheduler.checkExists(getJobKey(jobId, jobGroup))) {
			// 防止创建时存在数据问题 先移除,然后在执行创建操作
			scheduler.deleteJob(getJobKey(jobId, jobGroup));
		}

		scheduler.scheduleJob(jobDetail, trigger);

		// 暂停任务
		if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) {
			scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
		}
	}

	/**
	 * 设置定时任务策略
	 */
	public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
			throws TaskException {
		switch (job.getMisfirePolicy()) {
			case ScheduleConstants.MISFIRE_DEFAULT:
				return cb;
			case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
				return cb.withMisfireHandlingInstructionIgnoreMisfires();
			case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
				return cb.withMisfireHandlingInstructionFireAndProceed();
			case ScheduleConstants.MISFIRE_DO_NOTHING:
				return cb.withMisfireHandlingInstructionDoNothing();
			default:
				throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
						+ "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
		}
	}
}

Job执行工具类 

package vip.fkandy.quartz.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import vip.fkandy.common.utils.StringUtils;
import vip.fkandy.common.utils.spring.SpringUtils;
import vip.fkandy.quartz.domain.SysJob;

/**
 * 任务执行工具
 *
 * @author admin
 */
public class JobInvokeUtil {
	/**
	 * 执行方法
	 *
	 * @param sysJob 系统任务
	 */
	public static void invokeMethod(SysJob sysJob) throws Exception {
		String invokeTarget = sysJob.getInvokeTarget();
		String beanName = getBeanName(invokeTarget);
		String methodName = getMethodName(invokeTarget);
		List<Object[]> methodParams = getMethodParams(invokeTarget);

		if (!isValidClassName(beanName)) {
			Object bean = SpringUtils.getBean(beanName);
			invokeMethod(bean, methodName, methodParams);
		} else {
			Object bean = Class.forName(beanName).newInstance();
			invokeMethod(bean, methodName, methodParams);
		}
	}

	/**
	 * 调用任务方法
	 *
	 * @param bean         目标对象
	 * @param methodName   方法名称
	 * @param methodParams 方法参数
	 */
	private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
			throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
			InvocationTargetException {
		if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) {
			Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
			method.invoke(bean, getMethodParamsValue(methodParams));
		} else {
			Method method = bean.getClass().getDeclaredMethod(methodName);
			method.invoke(bean);
		}
	}

	/**
	 * 校验是否为class包名
	 *
	 * @param invokeTarget 名称
	 * @return true是 false否
	 */
	public static boolean isValidClassName(String invokeTarget) {
		return StringUtils.countMatches(invokeTarget, ".") > 1;
	}

	/**
	 * 获取bean名称
	 *
	 * @param invokeTarget 目标字符串
	 * @return bean名称
	 */
	public static String getBeanName(String invokeTarget) {
		String beanName = StringUtils.substringBefore(invokeTarget, "(");
		return StringUtils.substringBeforeLast(beanName, ".");
	}

	/**
	 * 获取bean方法
	 *
	 * @param invokeTarget 目标字符串
	 * @return method方法
	 */
	public static String getMethodName(String invokeTarget) {
		String methodName = StringUtils.substringBefore(invokeTarget, "(");
		return StringUtils.substringAfterLast(methodName, ".");
	}

	/**
	 * 获取method方法参数相关列表
	 *
	 * @param invokeTarget 目标字符串
	 * @return method方法相关参数列表
	 */
	public static List<Object[]> getMethodParams(String invokeTarget) {
		String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
		if (StringUtils.isEmpty(methodStr)) {
			return null;
		}
		String[] methodParams = methodStr.split(",");
		List<Object[]> classs = new LinkedList<>();
		for (int i = 0; i < methodParams.length; i++) {
			String str = StringUtils.trimToEmpty(methodParams[i]);
			// String字符串类型,包含'
			if (StringUtils.contains(str, "'")) {
				classs.add(new Object[]{StringUtils.replace(str, "'", ""), String.class});
			}
			// boolean布尔类型,等于true或者false
			else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false")) {
				classs.add(new Object[]{Boolean.valueOf(str), Boolean.class});
			}
			// long长整形,包含L
			else if (StringUtils.containsIgnoreCase(str, "L")) {
				classs.add(new Object[]{Long.valueOf(StringUtils.replaceIgnoreCase(str, "L", "")), Long.class});
			}
			// double浮点类型,包含D
			else if (StringUtils.containsIgnoreCase(str, "D")) {
				classs.add(new Object[]{Double.valueOf(StringUtils.replaceIgnoreCase(str, "D", "")), Double.class});
			}
			// 其他类型归类为整形
			else {
				classs.add(new Object[]{Integer.valueOf(str), Integer.class});
			}
		}
		return classs;
	}

	/**
	 * 获取参数类型
	 *
	 * @param methodParams 参数相关列表
	 * @return 参数类型列表
	 */
	public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
		Class<?>[] classs = new Class<?>[methodParams.size()];
		int index = 0;
		for (Object[] os : methodParams) {
			classs[index] = (Class<?>) os[1];
			index++;
		}
		return classs;
	}

	/**
	 * 获取参数值
	 *
	 * @param methodParams 参数相关列表
	 * @return 参数值列表
	 */
	public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
		Object[] classs = new Object[methodParams.size()];
		int index = 0;
		for (Object[] os : methodParams) {
			classs[index] = (Object) os[0];
			index++;
		}
		return classs;
	}
}

更多:

https://blog.csdn.net/u010648555/article/category/9267873

https://blog.csdn.net/beliefer/article/category/6329644

http://www.blogjava.net/gbk/articles/216122.html

https://www.w3cschool.cn/quartz_doc/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值