你知道怎么实现定时任务吗?

news2024/11/15 19:57:09

诸位读者都知道笔者写东西都是用到才写,笔者的学习足迹自从参加工作之后就是 非系统 学习了,公司里源代码只要有笔者不知道的技术细节,笔者就会仔细的研究清楚,笔者是不喜欢给自己留下问题的那种学习习惯。

为何要写

笔者最近负责了消息发送的一些业务需求,由于笔者工作年限不到一年,且笔者目前只是普通本科大四学生,技术栈并不是很完善(crud程序员只是起点),例如国内很多公司都在用的许雪里大神开发的xxl-job计划任务框架,tk-mybaytis框架(笔者也不知道公司为什么不直接用mybatis而是选用了它的其他版本),自封装的ORM框架(用法与mybatis不一样的地方在于它需要自己写Sql语句进行封装),以及各种自封装的自研框架。由于接触了需求,定时任务发送消息提醒用户。所以笔者不得不学习测试一下定时任务框架来加深更多的了解,不能在开发过程中知其然不知其所以然。

定时任务的实现方式

说到头,定时任务就是类似于将cron语句: 秒 分 时 日 月 周 年  交给linux系统进行执行,到了指定的时间就得执行这些命令,在业务需求里,我们需要系统去定时催促实现某些事情,其实这种场景非常多,要写程序实现计时的功能我觉得读者们应该都有自己的方式:例如 利用java线程sleep的方式,利用 timerTak的工具包,利用redis键值对过期消息回调,利用前端计时器,利用spring框架自带的注解@Scheduled (计划的)搭配cron语句,利用数据库event实现,利用xxl-job框架实现,利用quartz框架实现等等......

之前笔者就分享了一套用java线程池实现的定时任务工具,底层就是sleep方法,缺陷太多,不适合生产环境,所以为了加深对公司项目中xxl-job业务代码的理解,笔者研究了quartz框架实现计划任务的原理与实现方式,记录下来供笔者日后进一步深研。

下面笔者举例部分代码供读者阅读:

1、java线程sleep实现计时任务:

 2、java工具包TimerTask实现定时任务:

 3、利用redis键值对过期回调:

4、前端js函数setTimeOut(函数,毫秒)也可定时;

5、 spring注解@Scheduled实现定时任务:

6、利用数据库事件event实现:

事件调度器: Event Scheduler可以用做定时执行某些特定任务(例如:删除记录、数据统计报告、数据备份等等),来取代原先只能由操作系统的计划任务来执行的工作(可以精确到秒)区分表级别的触发器Tigger(事件触发的任务是周期时间决定,而触发器是由表级动作决定的)。

(1)开启数据库事件调度:

SHOW VARIABLES LIKE 'event%';  表中的event_scheduler值为on即为已经开启。

开启:SET GLOBAL event_scheduler = 1;SET GLOBAL event_scheduler = ON;
关闭:SET GLOBAL event_scheduler = 0;SET GLOBAL event_scheduler = OFF;

如果想持久化配置项可以将event_scheduler=1写入到数据库配置文件my.cnf文件(区别my.inf文件)中。

(2)创建事件调度:

CREATE EVENT 【调度事件名称】
ON SCHEDULE 【指定好调度时间】
DO 【SQL可执行语句】

具体细节以及其他语法读者们请自行www.baidu,com;

(3)举例实现调度语句:

CREATE EVENT delete_db_and_run
ON SCHEDULE  AT  TIMESTEP '2023-04-16 16:00:00'
DO  DELETE FROM alldata

以上就是在2023年4月16日16点整删除数据表alldata的定时计划语法

Quartz框架的技术细节与底层原理

笔者总结的技术点若有任何问题,欢迎读者指出,阅即改正。

技术细节:Quartz是通过数据库持久化来实现计划的数据保留,但它的原理并不是基于数据库event的方式进行实现的计划任务。它可以扩展更多的需求也业务,你可以自定义事件只要它们继承了Job接口。Quartz优质的地方在于它与xxl-job一样是基于数据库进行事件调度的,这样就算是server宕机了也不影响我们定时任务的存储,只要在容忍时间内重启服务器我们的定时任务还是可以正常执行的。

底层原理:我们只需要搞清楚两个问题即可明白该框架的主流程脉络,至于其他的枝叶逻辑笔者并没有深究,这两个主流程问题就是该框架是如何将任务存入数据库的?该框架是如何获取定时任务的时间并判断给出动作的?

问题1:quartz框架是如何将任务存储进它的配置数据表中的?

笔者还是以源码跟踪的方式与读者们一起研究这里面的主要脉络:

首先在官方demo中我们能发现至关重要的语句,也是查找源码的起始点:

 org.quartz.Scheduler类实现的调度任务方法,就是添加计划任务的主要实现代码,笔者点进源码发现以下方法链调用:

 

 (触发器同理也能找到类似于更新与插入方法的调用,我们先揪着一个走就行)

 这里其实已经很清晰了,运用了JDBC采用动态注入的方式进行数据库的更新与插入操作。

其实quartz还有其他数据库类型可以使用,但是入库的主流程脉络大差不差,我们首先要知道它就是通过我们crud程序员最擅长的方式进行的数据库存储定时任务信息的。

问题2:quartz是如何获取定时任务的时间并判断给出动作的?

关于这个问题,笔者查看了依赖包的层级结构,找到了core中的核心QuartzSchedulerThread类;

 显然这就是quartz做出动作的核心线程类,关于它做了什么,我们只需要看线程的run方法即可:

由于此类的run方法行数比较长,笔者将分开叙述它做了什么:

首先在while中优先给出了同步持有对象锁sigLock,通先查询halted的原子布尔型变量的判断,如果停止的值为false则进入等待阶段等到之后进行下一次扫描:

 下一步判断线程池可用线程数,可用数大于0后获取将要触发的触发器链表Tiggers:

 注意方法 this.qsRsrcs.getJobStore().acquireNextTriggers 是获取将要触发的触发器列表:

这里笔者直接将获取将要触发的触发器列表方法核心逻辑展示出来供读者阅读: 

之后就是 判断时间有没有到该触发的时间,因为触发器列表中的触发器都是将要触发的,判断到快要触发了。所以quartz这里做了等待,如果trigger的nextFireTime比当前时间大2ms则循环等待。timeUntilTrigger就是nextFireTime和当前时间之间的差值,它在循环中不断更新,直到它的值非常小了,之后才继续向下到真正的触发代码:

 然后就是触发了:

 我们继续看触发方法tiggersFired()的部分核心代码:

 之后的逻辑方法就是数据库操作,记录操作:

 将库修改之后就要进行脚本的执行了(这里需要注意的是执行是线程的异步操作,并行执行,但也有一定的控制逻辑),根据数据库的触发结果获取脚本shell:

 拿到shell之后就可以到线程池中丢过去执行了:

 实现了Runnable接口的shell当然可以直接执行了,最后我们发现它对于未获取到将要触发的触发器列表时,它会自动等待:

 开始获取锁等待是为了定期执行,后面的获取锁等待是为了在等最近可以触发的触发器列表。

到这里笔者和读者们大概也明白了quartz的获取判断与触发逻辑,理解了第二大主流程脉络,当然里面的逻辑肯定不止这些,但是笔者认为理解了主流程脉络结合实践加深吃入程度即可。

 Quartz框架的实现demo实践

笔者也做了一个小demo实现我们的计划任务业务需求,当然仅仅供读者们阅读与学习,不支持生产使用。

依赖:

        <!-- quartz依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
            <version>2.5.4</version>
        </dependency>

quartz.sql

#1 保存已经触发的触发器状态信息
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
#2 存放暂停掉的触发器表表
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
#3 调度器状态表
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
#4 存储程序的悲观锁的信息(假如使用了悲观锁)
DROP TABLE IF EXISTS QRTZ_LOCKS;
#5 简单的触发器表
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
#6 存储两种类型的触发器表
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
#7 定时触发器表
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
#8 以blob 类型存储的触发器
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
#9 触发器表
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
#10 job 详细信息表
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
#11 日历信息表
DROP TABLE IF EXISTS QRTZ_CALENDARS;

#job 详细信息表
CREATE TABLE QRTZ_JOB_DETAILS
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    JOB_CLASS_NAME   VARCHAR(250) NOT NULL,
    IS_DURABLE VARCHAR(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_TRIGGERS
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(200) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
        REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);


CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    REPEAT_COUNT BIGINT(7) NOT NULL,
    REPEAT_INTERVAL BIGINT(12) NOT NULL,
    TIMES_TRIGGERED BIGINT(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);


CREATE TABLE QRTZ_CRON_TRIGGERS
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    CRON_EXPRESSION VARCHAR(200) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);


CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);


CREATE TABLE QRTZ_BLOB_TRIGGERS
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    BLOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);


CREATE TABLE QRTZ_CALENDARS
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    CALENDAR_NAME  VARCHAR(200) NOT NULL,
    CALENDAR BLOB NOT NULL,
    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);


CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP  VARCHAR(200) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_FIRED_TRIGGERS
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    FIRED_TIME BIGINT(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(200) NULL,
    JOB_GROUP VARCHAR(200) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);


CREATE TABLE QRTZ_SCHEDULER_STATE
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);

CREATE TABLE QRTZ_LOCKS
(
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME  VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);

配置文件application.yml

server:
  port: 9000
spring:
  application:
    name: quartz-service
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://localhost:3306/your quartz db ?useUnicode=true&characterEncoding=utf8
    username: your db username
    password: your db password
    driver-class-name: com.mysql.cj.jdbc.Driver
  quartz:
    # 相关属性配置
    properties:
      org:
        quartz:
          # 数据源
          dataSource:
            globalJobDataSource:
              # URL必须大写
              URL: jdbc:mysql://127.0.0.1:3306/your quartz db ?useUnicode=true&characterEncoding=utf-8&useSSL=false
              driver: com.mysql.cj.jdbc.Driver
              maxConnections: 5
              username: your db username
              password: your db password 
              # 必须指定数据源类型
              provider: hikaricp
          scheduler:
            instanceName: globalScheduler
            # 实例id
            #instanceId: AUTO
            type: com.alibaba.druid.pool.DruidDataSource
          jobStore:
            # 数据源
            dataSource: globalJobDataSource
            # JobStoreTX将用于独立环境,提交和回滚都将由这个类处理
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            # 驱动配置
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            # 表前缀
            tablePrefix: QRTZ_
          # 线程池配置
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            # 线程数
            threadCount: 10
            # 优先级
            threadPriority: 5

实体类

@Data
public class JobInfo {
    private String jobName;
    private String jobGroup;
    private String triggerName;
    private String triggerGroup;
    private String cron;
    private String className;
    private String status;
    private String nextTime;
    private String prevTime;
    private String config;
}
@Data
public class Message {
    private String title;
    private String message;
    private String jobGroup;
    private String jobName;
    private String sendUserName;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private String sendDate;
}

 核心任务处理类

@Component
public class JobHandler {
    @Resource
    private Scheduler scheduler;
    
    public void addJob(JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {
        Assert.notNull(jobInfo, LocalDateTime.now().toString() + "-> 任务信息为null拒绝生成定时任务");
        JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup());
        if (!scheduler.checkExists(jobKey)) {
            Class<Job> jobClass = (Class<Job>) Class.forName(jobInfo.getClassName());
            JobDetail jobDetail = JobBuilder.newJob(jobClass)
                    .withIdentity(jobKey)
                    .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup())
                    .withIdentity(jobInfo.getJobName())
                    .build();
            jobDetail.getJobDataMap().put("config", jobInfo.getConfig());
            TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getTriggerName(), jobInfo.getTriggerGroup());
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(triggerKey)
                    .withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCron()))
                    .build();
            scheduler.scheduleJob(jobDetail,trigger);
        } else {
            throw new SchedulerException(jobInfo.getJobName() + "->定时任务计划库已存在");
        }
    }

    public void pauseJob(String jobGroup, String jobName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        if (scheduler.checkExists(jobKey)) {
            scheduler.pauseJob(jobKey);
        }
    }

    public void resumeJob(String jobGroup, String jobName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        if (scheduler.checkExists(jobKey)) {
            scheduler.resumeJob(jobKey);
        }
    }

    public Boolean deleteJob(String jobGroup, String jobName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        if (scheduler.checkExists(jobKey)) {
            return scheduler.deleteJob(jobKey);
        }
        return false;
    }
    
    public JobInfo getJobInfo(String jobGroup, String jobName) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName,jobGroup);
        if (!scheduler.checkExists(jobKey)){
            return null;
        }
        List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(jobKey);
        Assert.notNull(triggersOfJob, LocalDateTime.now().toString()+"->触发器信息为空->"+jobGroup+"/"+jobName);
        TriggerKey key = triggersOfJob.get(0).getKey();
        Trigger.TriggerState state = scheduler.getTriggerState(key);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        JobInfo jobInfo = new JobInfo();
        jobInfo.setJobGroup(jobGroup);
        jobInfo.setJobName(jobName);
        jobInfo.setTriggerGroup(key.getGroup());
        jobInfo.setJobName(key.getName());
        jobInfo.setClassName(jobDetail.getJobClass().getName());
        jobInfo.setStatus(state.toString());
        if (Objects.nonNull(jobDetail.getJobDataMap())){
            jobInfo.setConfig(JSONObject.toJSONString(jobDetail.getJobDataMap()));
        }
        CronTrigger trigger = (CronTrigger) triggersOfJob.get(0);
        jobInfo.setCron(trigger.getCronExpression());
        return jobInfo;
    }
}

事件类型

@Component
@DisallowConcurrentExecution
public class PlanRemindJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("执行计划任务" + jobExecutionContext.getJobDetail().getDescription());
    }
}
@Component
@DisallowConcurrentExecution
public class TimeEventJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("执行定时任务" + jobExecutionContext.getJobDetail().getDescription());
    }
}

事件类型枚举

public enum JobType {
    //计划任务与定时任务
     PLAN_REMIND_MESSAGE ,
     TIME_EVENT_MESSAGE 
}

业务接口

public interface QuartzService {
    Boolean insertMessage(Message message, JobType jobType) throws SchedulerException, ClassNotFoundException;
    JobInfo getJob(Message message);
    Boolean deleteJob(Message message);
    Boolean resumeJob(Message message);
}

 业务实现类

@Service("QuartzService")
public class QuartzServiceImpl implements QuartzService {

    @Resource
    private JobHandler jobHandler;

    @Override
    public synchronized Boolean insertMessage(Message message, JobType jobType) {
        JobInfo jobInfo = JobGenerator.getJobInfo(message, jobType);
        try {
            jobHandler.addJob(jobInfo);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public JobInfo getJob(Message message) {
        try {
            return jobHandler.getJobInfo(message.getJobGroup(), message.getJobName());
        } catch (SchedulerException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public synchronized Boolean deleteJob(Message message) {
        try {
            return jobHandler.deleteJob(message.getJobGroup(), message.getJobName());
        } catch (SchedulerException e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public synchronized Boolean resumeJob(Message message) {
        try {
            jobHandler.resumeJob(message.getJobGroup(), message.getJobName());
            return true;
        } catch (SchedulerException e) {
            e.printStackTrace();
            return false;
        }
    }
}

工具类

cron与日期(“yyyy-MM-dd hh:mm:ss”)转换类

public class CronGenerator {
    public static String geCron(Message message){
        String date = message.getSendDate().toString();
        String yyyy = date.substring(0, 4);
        String MM = date.substring(5, 7);
        String dd = date.substring(8, 10);
        String hh = date.substring(11, 13);
        String mm = date.substring(14, 16);
        String ss = date.substring(17, 19);
        return ss+" "+mm+" "+hh+" "+dd+" "+MM+" "+"? "+yyyy;
    }
}

JobInfo实体转换工具类

public class JobGenerator {
    public static JobInfo getJobInfo(Message message, JobType jobType) {
        String cron = CronGenerator.geCron(message);
        JobInfo jobInfo = new JobInfo();
        jobInfo.setCron(cron);
        if (jobType == JobType.PLAN_REMIND_MESSAGE){
            jobInfo.setClassName("com.hlc.quartzservice.jobType.PlanRemindJob");
            jobInfo.setJobGroup(message.getJobGroup());
            jobInfo.setJobName(message.getJobName());
            jobInfo.setTriggerGroup("PLAN_REMIND_MESSAGE");
            jobInfo.setTriggerName("计划任务触发器");
            jobInfo.setConfig(message.getMessage());
        }else if (jobType == JobType.TIME_EVENT_MESSAGE){
            jobInfo.setClassName("com.hlc.quartzservice.jobType.TimeEventJob");
            jobInfo.setJobGroup(message.getJobGroup());
            jobInfo.setJobName(message.getJobName());
            jobInfo.setTriggerGroup("TIME_EVENT_MESSAGE");
            jobInfo.setTriggerName("定时任务触发器");
            jobInfo.setConfig(message.getMessage());
        }
        return jobInfo;
    }
}

开启应用程序进行测试

@SpringBootApplication
public class QuartzServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(QuartzServiceApplication.class, args);
        
        QuartzService quartzService = SpringContextHolder.getBean(QuartzService.class);
        Message message = new Message();
        message.setMessage("测试");
        message.setSendDate("2023-04-15 14:30:00");
        message.setTitle("消息");
        message.setSendUserName("hlc");
        try {
            quartzService.insertMessage(message, JobType.PLAN_REMIND_MESSAGE);
        } catch (SchedulerException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

静态方法获取bean的获取容器上下文工具类(实现ApplicationContextAware接口)

@Component
@Lazy(value = false)
public class SpringContextHolder implements ApplicationContextAware {
    /**
     * 将上下文静态设置,在初始化组件时就进行静态上下文的覆盖(这个覆盖是将远spring容器的上下文对象引用加到我们预定设置)
     */
    private static ApplicationContext applicationContext = null;

    public static ApplicationContext getApplicationContext() {
        assertContextInjected();
        return applicationContext;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        assertContextInjected();
        return (T) applicationContext.getBean(name);
    }

    public static  <T> T getBean(Class<T> beanType) {
        assertContextInjected();
        return applicationContext.getBean(beanType);
    }

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        SpringContextHolder.applicationContext = applicationContext;
    }
    
    public void destroy() {
        applicationContext = null;
    }

    private static void assertContextInjected() {
        Assert.notNull(applicationContext,
                "applicationContext属性未注入, 请在applicationContext.xml中定义SpringContextHolder.");
    }

    public static void pushEvent(ApplicationEvent event){
        assertContextInjected();
        applicationContext.publishEvent(event);
    }

}

还有另外一种方法可以测试,就是查库,看看数据库表中有没有发生数据变化?

给的么详细,确定不点点赞,收藏一下吗?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/420306.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用Thymeleaf给web项目中的网页渲染显示动态数据?

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 服务器软件&#xff1a;apache-tomcat-8.5.27 目录一. 什么是Thymeleaf&#xff1f;二. MVC2.1 为什么需要MVC&#xff1f;2.2 MVC是什么&#xff1f;2.3 MVC和三层架构之间的关系及工…

AI绘图体验:想象力无限,创作无穷!(文生图)

基础模型&#xff1a;3D二次元 PIXEL ART &#xff08;1&#xff09;16-bit pixel art, outside of caf on rainy day, light coming from windows, cinematic still(电影剧照), hdr (2) 16-bit pixel art, island in the clouds, by studio ghibli&#xff08;吉卜力工作室…

配置基于WSL2的Docker环境并支持CUDA

导言 Content 正如前文windows 10 开启WSL2介绍的&#xff0c;我们可以在windows10中使用linux子系统。今天本文介绍如何在此基础上安装Docker并支持在wsl中使用GPU。 准备工作 加入windows insider preview。建议选Dev通道&#xff0c;不要选Beta。 安装Nvidia WSL2-compa…

【数据结构】-计数排序

&#x1f387;作者&#xff1a;小树苗渴望变成参天大树 &#x1f389; 作者宣言&#xff1a;认真写好每一篇博客 &#x1f38a;作者gitee:link 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; 文章目录前言一、计数排序二、排序算法复杂度…

Nginx网站服务配置

一、Nginx概述 1.1 Nginx概述 Nginx&#xff1a; Nginx 是开源、高性能、高可靠的 Web 和反向代理服务器&#xff0c;而且支持热部署&#xff0c;几乎可以做到 7 * 24 小时不间断运行&#xff0c;即使运行几个月也不需要重新启动&#xff0c;还能在不间断服务的情况下对软件…

分布式计算技术(上):经典计算框架MapReduce、Spark 解析

当一个计算任务过于复杂不能被一台服务器独立完成的时候&#xff0c;我们就需要分布式计算。分布式计算技术将一个大型任务切分为多个更小的任务&#xff0c;用多台计算机通过网络组装起来后&#xff0c;将每个小任务交给一些服务器来独立完成&#xff0c;最终完成这个复杂的计…

07 -全局状态管理

全局状态管理 7-1&#xff1a;开篇 在上一章中我们完成了 “一半” 的文章搜索功能&#xff0c;并且留下了一些问题。那么这些历史残留的问题&#xff0c;我们将会在本章节中通过 全局状态管理工具 进行处理。 那么究竟什么是 全局状态管理工具&#xff0c;如何在 uniapp 中…

【Flutter进阶】聊一聊组件中的生命周期、状态管理及局部重绘

前言 说到生命周期&#xff0c;熟悉Android开发的小伙伴一定第一时间会想到Activity的生命周期&#xff0c;由于在Flutter中一切都是组件&#xff0c;所以组件的生命周期其实是类似的。 在这个过程中组件的状态——State就非常重要&#xff0c;它记录这整个组件内可变部分的状…

【SSM整合】1—Spring和Mybatis整合

⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ Spring专栏&#x1f449;https://blog.csdn.net/weixin_53580595/category_12279588.html SpringMVC专栏&#x1f449;htt…

linux安装kafka

目录 目录 一.安装包准备&#xff1a; 二.解压安装&#xff1a; 先将该安装包放入到/opt/install目录&#xff1a; 解压该文件到soft目录中&#xff1a; 改名&#xff0c;方便后续使用&#xff1a; 三修改其中配置和配置环境变量&#xff1a; 3.1 修改/opt/soft/kafka2…

camunda工作流引擎开发架构

Camunda的开发架构可以分为前端开发架构和后端开发架构。 前端开发架构&#xff1a; Camunda前端使用Angular框架进行开发&#xff0c;主要包括以下组件&#xff1a; 1、Cockpit&#xff1a;流程监控和管理界面。 2、Tasklist&#xff1a;任务管理和审批界面。 3、Admin&…

答题积分小程序云开发实战-开篇:项目介绍以及效果图

答题积分小程序云开发实战 开篇:项目介绍以及效果图 前言 我也看过不少的册子或者文章,大部分都很优秀,但也有的就长篇累牍,从时代背景讲起,复述各种基本概念、底层原理......嗯,看似很高级~ 但我阅读的时候,给我的感觉是,把你绕晕、把你劝退的感觉,相信大家都有同感,…

C++输入输出、缺省参数、函数重载、引用【C++初阶】

目录 一、C输入&输出 二、缺省参数 1、概念 2、分类 &#xff08;1&#xff09;全缺省 &#xff08;2&#xff09;半缺省 三、函数重载 1、概念 2、原理------名字修饰 一、C输入&输出 在C语言中&#xff0c;我们常用printf和scanf这两个函数进行输入输出。 …

产品-Axure9(英文版),.rp文件与.rplb文件的转换与区分

文章目录1、区分2、相互转换2.1 rp转为rplb2.1 rplb转为rp1、区分 rp文件是文档文件&#xff0c;可以理解为作品文件&#xff0c;自己的工作输出就是rp文件&#xff0c;图标如下。 rplb文件是库文件&#xff0c;是在制作文件过程中一个快捷库&#xff0c;图标如下 在点击绿色…

GitHub 上诞生了一个可视化低代码神器

作为开发者&#xff0c;你是否早已厌倦了日复一日的“增删改查”&#xff0c;每天都在重复造轮子&#xff0c;今天给大家推荐一款开源、靠谱、实用的低代码开发平台 -- ILLA Builder。 产品介绍 ILLA Builder 是 ILLA 的核心产品&#xff0c;是一款开源的低代码开发工具。通过…

ROS话题通信自定义+发布订阅代码--03

话题通信自定义msg 在 ROS 通信协议中&#xff0c;数据载体是一个较为重要组成部分&#xff0c;ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是&#xff0c;这些数据一般只包含一个 data 字段&#xff0c;结构的单一意味…

C++实现JPEG格式图片解析(附代码)

在网上看了好多解析JPEG图片的文章&#xff0c;多多少少都有问题&#xff0c;下面是我参考过的文章链接&#xff1a; 首先&#xff0c;解析的步骤1.读取文件的信息2.Huffman编码解码3.直流交流编码解析然而&#xff0c;读取多少个88矩阵才能解析出一个MCU呢&#xff1f;4.反量化…

8年测试老鸟总结,接口自动化测试测试用例编写(全覆盖场景)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 自动化测试&#xf…

15-721 Chapter 6 索引

最先是解释了一个古老的&#xff0c;现在没什么人用数据结构----T-tree&#xff0c;因为现代的cpu到cache和到memory差异巨大&#xff0c;同时memory的容量也变大了。 T-tree 两个key标志着范围&#xff0c;决定到哪里找key&#xff0c;然后存的都是指针&#xff0c;指向pare…

CANopen | 对象字典OD 05 - 创建对象字典变量,映射到RPDO

文章目录一、前言二、实验目的三、对象字典OD四、通过RPDO修改变量rx_Value4.1、NMT指令让CANopen从站进入操作状态4.2、RPDO修改变量rx_Value一、前言 该章节的源代码地址&#xff1a;github 以上摘自《CANopen_easy_begin》的第7章。 二、实验目的 CANopen从站有一个变量…