Springboot整合quartz實現數據庫配置定時任務

Spring Boot結合quartz實現數據庫動態啓動Bean下的方法

項目代碼已上傳git:

https://gitee.com/gangye/springboot_quartz_schedule

或者使用csdn下載鏈接:

https://download.csdn.net/download/xibei19921101/12260991

相關任務以及執行結果:

Bean實例addNumWorker下定時任務方法work插入結果(上述cron表達式爲每天17點4分每個20秒插入一批數據)

Bean實例下proStatisticsWorker定時任務方法work統計插入結果(上述cron表達式爲每天17點5分30秒查詢插入統計結果)

執行日誌查看:

項目搭建過程:

項目目錄簡介:主要多了這些,其餘的和正常的springboot的web項目一致,在service中進行了定時任務的啓動

1.創建一個maven項目引入Spring Boot、Mybatis、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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.crcb</groupId>
    <artifactId>springbootCornTask</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <log4j.version>1.7.10</log4j.version>
        <org.springframework.version>4.0.6.RELEASE</org.springframework.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>

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

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- quartz spring 3.1以上才支持quartz 2.2.1 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>
    </dependencies>

</project>

2.編寫項目的配置文件,應用配置、日誌配置、mybatis的一些配置

1.application.properties(注:此處在url中設置時區,解決時間插入少8小時的問題(代碼中產生的時間沒問題,插入時有問題)

時區的配置問題可參考:https://blog.csdn.net/u010921682/article/details/100585832

server.port=8089

#spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
#spring.jackson.time-zone=Asia/Shanghai

#數據庫連接池設置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_vue_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=ok

#mybatis的相關配置
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.config-location=classpath:mybatis-config.xml

2.mybatis-config.xml以及logback-config.xml參考我的另外一篇博客:

https://blog.csdn.net/xibei19921101/article/details/104717453

3.工具類Springutils、反射調用scheduleJob中定義的方法、計劃任務執行處等類

1.SpringUtils.java文件(注:此處的SpringUtils工具類必須引入@Component註解,後續要使用反射,要依賴注入)

package com.crcb.utils;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

/**
 * @Classname SpringUtils
 * @Description
 * @Date 2020/3/19 9:10
 * @Created by gangye
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor {

    private static ConfigurableListableBeanFactory beanFactory; // Spring應用上下文環境

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory = beanFactory;
    }

    /**
     * 獲取對象
     *
     * @param name
     * @return Object 一個以所給名字註冊的bean的實例
     * @throws BeansException
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        return (T) beanFactory.getBean(name);
    }

    /**
     * 獲取對象
     * @return Object 一個以所給名字註冊的bean的實例
     * @throws BeansException
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBeanByType(Class<T> clzee) throws BeansException {
        try {
            return beanFactory.createBean(clzee);
        } catch (NoSuchBeanDefinitionException e) {
            return null;
        }

    }

    /**
     * 注入一個對象
     * @return Object 一個以所給名字註冊的bean的實例
     * @throws BeansException
     */
    @SuppressWarnings("unchecked")
    public static void setBean(String springId, Object obj) throws BeansException {
        beanFactory.registerSingleton(springId, obj);
    }

    /**
     * 獲取類型爲requiredType的對象
     * @return
     * @throws BeansException
     */
    public static <T> T getBean(Class<T> clz) throws BeansException {
        try {
            @SuppressWarnings("unchecked")
            T result = (T) beanFactory.getBean(clz);
            return result;
        } catch (NoSuchBeanDefinitionException e) {
            return null;
        }
    }

    /**
     * 如果BeanFactory包含一個與所給名稱匹配的bean定義,則返回true
     *
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name) {
        return beanFactory.containsBean(name);
    }

    /**
     * 判斷以給定名字註冊的bean定義是一個singleton還是一個prototype。
     * 如果與給定名字相應的bean定義沒有被找到,將會拋出一個異常(NoSuchBeanDefinitionException)
     *
     * @param name
     * @return boolean
     * @throws NoSuchBeanDefinitionException
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.isSingleton(name);
    }

    /**
     * @param name
     * @return Class 註冊對象的類型
     * @throws NoSuchBeanDefinitionException
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getType(name);
    }

    /**
     * 如果給定的bean名字在bean定義中有別名,則返回這些別名
     *
     * @param name
     * @return
     * @throws NoSuchBeanDefinitionException
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getAliases(name);
    }

}

2.TaskUtils.java(反射調用scheduleJob中定義的方法)

package com.crcb.task;

import com.crcb.entity.ScheduleJob;
import com.crcb.utils.LogUtils;
import com.crcb.utils.Response;
import com.crcb.utils.SpringUtils;
import com.crcb.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import java.lang.reflect.Method;

public class TaskUtils {

    @Autowired
    private ApplicationContext applicationContext;
    /**
     * 通過反射調用scheduleJob中定義的方法
     *
     * @param scheduleJob
     */
    public static void invokMethod(ScheduleJob scheduleJob) {

        try {//添加最大的異常捕獲
            String springId = scheduleJob.getSpringId();
            Object object = null;
            Class clazz = null;

            //根據反射來進行
            if (StringUtils.isNotBlank(springId)) {
                object = SpringUtils.getBean(springId);
            }

            if (object == null && StringUtils.isNotBlank(scheduleJob.getBeanClass())) {
                String jobStr = "定時任務名稱 = [" + scheduleJob.getJobName() + "]-在spring 中沒有這個 springId, 通過 class type 獲取中...";
                LogUtils.info(jobStr, scheduleJob.getBeanClass());
                try {
                    clazz = Class.forName(scheduleJob.getBeanClass());
                    object = SpringUtils.getBean(clazz);
                    if(object == null){
                        jobStr = "定時任務名稱 = [" + scheduleJob.getJobName() + "]-在spring 中沒有獲得 bean, 調用 spring 方法再次構建中...";
                        LogUtils.info(jobStr, scheduleJob.getBeanClass());
                        object = SpringUtils.getBeanByType(clazz);
                    }
                    if (StringUtils.isNotBlank(springId)) {
                        SpringUtils.setBean(springId, object);
                        LogUtils.info("spring bean 構建完成並加入到容器中 ", scheduleJob.getBeanClass());
                    }
                    LogUtils.info("定時任務 spring bean 構建成功! ", scheduleJob.getBeanClass());
                } catch (Exception e) {
                    LogUtils.error("定時任務 spring bean 構建失敗了!!! ", scheduleJob.getBeanClass(), e);
                    Response.newResponse().error(e);
                    return;
                }
            }

            clazz = object.getClass();
            Method method = null;
            try {
                method = clazz.getDeclaredMethod(scheduleJob.getMethodName());
            } catch (NoSuchMethodException e) {
                String jobStr = "定時任務名稱 = [" + scheduleJob.getJobName() + "] = 未啓動成功,方法名設置錯誤!!!";
                LogUtils.error(jobStr, e);
            } catch (SecurityException e) {
                LogUtils.error("TaskUtils發生異常", e);
                Response.newResponse().error(e);
            }
            if (method != null) {
                try {
                    method.invoke(object);
                    LogUtils.info("定時任務名稱 = [" + scheduleJob.getJobName() + "] = 啓動成功");
                } catch (Exception e) {
                    Response.newResponse().error(e);
                    LogUtils.error("定時任務名稱 = [" + scheduleJob.getJobName() + "] = 啓動失敗了!!!", e);
                    return;
                }
            } else {
                String jobStr = "定時任務名稱 = [" + scheduleJob.getJobName() + "] = 啓動失敗了!!!";
                LogUtils.error(jobStr, clazz.getName(), "not find method ");
            }

        } catch (Exception e) {//添加最大的異常捕獲
            Response.newResponse().error(e);
            LogUtils.error("定時任務名稱 = [" + scheduleJob.getJobName() + "] = 啓動失敗了!!!", e);
        }

    }

}

3.QuartzJobFactory.java

package com.crcb.task;

import com.crcb.entity.ScheduleJob;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;


/**
 * @Description: 計劃任務執行處 無狀態
 */
public class QuartzJobFactory implements Job {
	public void execute(JobExecutionContext context) throws JobExecutionException {
		ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
		TaskUtils.invokMethod(scheduleJob);
	}
}

4.QuartzJobFactoryDisallowConcurrentExecution.java(若一個方法一次執行不完下次輪轉時則等待改方法執行完後才執行下一次操作)

package com.crcb.task;

import com.crcb.entity.ScheduleJob;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
 * @Description: 若一個方法一次執行不完下次輪轉時則等待改方法執行完後才執行下一次操作
 */
@DisallowConcurrentExecution
public class QuartzJobFactoryDisallowConcurrentExecution implements Job {

	public void execute(JobExecutionContext context) throws JobExecutionException {
		ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
		TaskUtils.invokMethod(scheduleJob);

	}
}

4.Schedule實體類、mapper層、service層的編寫

package com.crcb.entity;

import java.util.Date;

/**
 * @Classname ScheduleJob
 * @Description 任務
 * @Date 2020/3/18 18:47
 * @Created by gangye
 */
public class ScheduleJob {
    public static final String STATUS_RUNNING = "1";
    public static final String STATUS_NOT_RUNNING = "0";
    public static final String CONCURRENT_IS = "1";
    public static final String CONCURRENT_NOT = "0";

    private Long jobId;

    private Date createTime;

    private Date updateTime;
    /**
     * 任務名稱
     */
    private String jobName;
    /**
     * 任務分組
     */
    private String jobGroup;
    /**
     * 任務狀態 是否啓動任務
     */
    private String jobStatus;
    /**
     * cron表達式
     */
    private String cronExpression;
    /**
     * 描述
     */
    private String description;
    /**
     * 任務執行時調用哪個類的方法 包名+類名
     */
    private String beanClass;
    /**
     * 任務是否有狀態
     */
    private String isConcurrent;
    /**
     * spring bean
     */
    private String springId;
    /**
     * 任務調用的方法名
     */
    private String methodName;

    //此處省略getter、setter方法,不推薦使用@Data註解
}

省去mapper層介紹,由於項目代碼已上傳git,在service層中的實體類中引入Job

package com.crcb.service.impl;

import com.crcb.entity.ScheduleJob;
import com.crcb.mapper.SchedulerMapper;
import com.crcb.service.SchedulerService;
import com.crcb.task.QuartzJobFactory;
import com.crcb.task.QuartzJobFactoryDisallowConcurrentExecution;
import com.crcb.utils.LogUtils;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * @Classname SchedulerServiceImpl
 * @Description 定時任務管理
 * @Date 2020/3/18 14:10
 * @Created by gangye
 */
@Service
public class SchedulerServiceImpl implements SchedulerService {

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    @Autowired
    private SchedulerMapper schedulerMapper;

    /**
     * 從數據庫中取 區別於getAllJob
     *
     * @return
     */
    public List<ScheduleJob> getAllTask() {
        return schedulerMapper.getAll();
    }

    /**
     * 添加到數據庫中 區別於addJob
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void addTask(ScheduleJob job) throws SchedulerException {
        job.setCreateTime(new Date());
        schedulerMapper.insert(job);
        addJob(job);
    }

    /**
     * 從數據庫中查詢job
     */
    public ScheduleJob getTaskById(Long jobId) {
        return schedulerMapper.selectByPrimaryKey(jobId);
    }

    /**
     * 更改任務狀態
     *
     * @throws SchedulerException
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void changeStatus(Long jobId, String cmd) throws SchedulerException {
        ScheduleJob job = getTaskById(jobId);
        if (job == null) {
            return;
        }
        if ("stop".equals(cmd)) {
            deleteJob(job);
            job.setJobStatus(ScheduleJob.STATUS_NOT_RUNNING);
        } else if ("start".equals(cmd)) {
            job.setJobStatus(ScheduleJob.STATUS_RUNNING);
            addJob(job);
        }
        schedulerMapper.update(job);
    }

    /**
     * 更改任務 cron表達式
     *
     * @throws SchedulerException
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateCron(Long jobId, String cron) throws SchedulerException {
        ScheduleJob job = getTaskById(jobId);
        if (job == null) {
            return;
        }
        job.setCronExpression(cron);
        if (ScheduleJob.STATUS_RUNNING.equals(job.getJobStatus())) {
            updateJobCron(job);
        }
        schedulerMapper.update(job);

    }

    /**
     * 添加任務
     *
     * @param job
     * @throws SchedulerException
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void addJob(ScheduleJob job) throws SchedulerException {
        if (job == null || !ScheduleJob.STATUS_RUNNING.equals(job.getJobStatus())) {
            return;
        }

        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        LogUtils.info(scheduler + ".......................................................................................add");
        TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());

        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

        // 不存在,創建一個
        if (null == trigger) {
            Class clazz = ScheduleJob.CONCURRENT_IS.equals(job.getIsConcurrent()) ? QuartzJobFactory.class : QuartzJobFactoryDisallowConcurrentExecution.class;

            JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(job.getJobName(), job.getJobGroup()).build();

            jobDetail.getJobDataMap().put("scheduleJob", job);

            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());

            trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup()).withSchedule(scheduleBuilder).build();

            scheduler.scheduleJob(jobDetail, trigger);
        } else {
            // Trigger已存在,那麼更新相應的定時設置
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());

            // 按新的cronExpression表達式重新構建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

            // 按新的trigger重新設置job執行
            scheduler.rescheduleJob(triggerKey, trigger);
        }
    }

    @PostConstruct
    public void init() throws Exception {

        LogUtils.info("實例化List<ScheduleJob>,從數據庫讀取....",this);

        // 這裏獲取任務信息數據
        List<ScheduleJob> jobList = schedulerMapper.getAll();

        for (ScheduleJob job : jobList) {
            addJob(job);
        }
    }

    /**
     * 獲取所有計劃中的任務列表
     *
     * @return
     * @throws SchedulerException
     */
    public List<ScheduleJob> getAllJob() throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        List<ScheduleJob> jobList = new ArrayList<ScheduleJob>();
        for (JobKey jobKey : jobKeys) {
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
            for (Trigger trigger : triggers) {
                ScheduleJob job = new ScheduleJob();
                job.setJobName(jobKey.getName());
                job.setJobGroup(jobKey.getGroup());
                job.setDescription("觸發器:" + trigger.getKey());
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                job.setJobStatus(triggerState.name());
                if (trigger instanceof CronTrigger) {
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    String cronExpression = cronTrigger.getCronExpression();
                    job.setCronExpression(cronExpression);
                }
                jobList.add(job);
            }
        }
        return jobList;
    }

    /**
     * 所有正在運行的job
     *
     * @return
     * @throws SchedulerException
     */
    public List<ScheduleJob> getRunningJob() throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
        List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size());
        for (JobExecutionContext executingJob : executingJobs) {
            ScheduleJob job = new ScheduleJob();
            JobDetail jobDetail = executingJob.getJobDetail();
            JobKey jobKey = jobDetail.getKey();
            Trigger trigger = executingJob.getTrigger();
            job.setJobName(jobKey.getName());
            job.setJobGroup(jobKey.getGroup());
            job.setDescription("觸發器:" + trigger.getKey());
            Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
            job.setJobStatus(triggerState.name());
            if (trigger instanceof CronTrigger) {
                CronTrigger cronTrigger = (CronTrigger) trigger;
                String cronExpression = cronTrigger.getCronExpression();
                job.setCronExpression(cronExpression);
            }
            jobList.add(job);
        }
        return jobList;
    }

    /**
     * 暫停一個job
     *
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.pauseJob(jobKey);
    }

    /**
     * 恢復一個job
     *
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.resumeJob(jobKey);
    }

    /**
     * 刪除一個job
     *
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void deleteJob(ScheduleJob scheduleJob) throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.deleteJob(jobKey);

    }

    /**
     * 立即執行job
     *
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void runAJobNow(ScheduleJob scheduleJob) throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
        scheduler.triggerJob(jobKey);
    }

    /**
     * 更新job時間表達式
     *
     * @param scheduleJob
     * @throws SchedulerException
     */
    public void updateJobCron(ScheduleJob scheduleJob) throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();

        TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());

        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression());

        trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

        scheduler.rescheduleJob(triggerKey, trigger);
    }
}

編寫定時任務的邏輯,注:在數據庫中配置sping_id時,spring依賴注入的時候,類名首字母小寫,所以要小寫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章