SpringBoot创建定时任务的方式

SpringBoot创建定时任务的方式

一、环境搭建

SpringBoot创建定时任务的方式_第1张图片
【pom.xml】


<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.2.2.RELEASEversion>
        <relativePath/> 
    parent>
    <groupId>com.iflytaskgroupId>
    <artifactId>demoartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>schedulename>
    <description>定时任务description>
    <properties>
        <java.version>1.8java.version>
    properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-configuration-processorartifactId>
        dependency>
        
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.8.2version>
        dependency>
        
        <dependency>
            <groupId>org.mapstructgroupId>
            <artifactId>mapstruct-jdk8artifactId>
            <version>1.3.1.Finalversion>
        dependency>
        <dependency>
            <groupId>org.mapstructgroupId>
            <artifactId>mapstruct-processorartifactId>
            <version>1.3.1.Finalversion>
        dependency>
    dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombokgroupId>
                            <artifactId>lombokartifactId>
                        exclude>
                    excludes>
                configuration>
            plugin>
        plugins>
    build>
project>

【application.properties】

此配置可以自动创建数据库,设置时区等

server.port=80
spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
# 数据库基本配置
spring.datasource.url=jdbc:mysql://localhost:3306/jpa?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 表中有数据时不会清空,只会更新
spring.jpa.hibernate.ddl-auto=update

# 控制台显示sql
spring.jpa.show-sql=true
spring.jpa.database=mysql

【启动类】

package com.iflytask.schedule;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ScheduleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ScheduleApplication.class, args);
    }

}


二、基于注解的方式

在代码中写死表达式,每次修改定时任务必须重启整个项目,耗时麻烦;
基于注解是一种静态的方式,只需要几行代码就可以搞定了

添加一个配置类:

SpringBoot创建定时任务的方式_第2张图片

package com.iflytask.schedule.config;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Console;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

/**
 * @program:
 * @description: @Configuration: 标记配置类; @EnableScheduling: 开启定时任务
 * @author: xuYao2
 * @create: 2022-08-11 11:27
 **/
@Configuration
@EnableScheduling
public class ScheduleConfig {

    @Scheduled(cron = "0/5 * * * * ? ")
    private void task(){
        Console.log("执行定时任务: {}", DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss"));
    }
}

上面代码的cron表达式表示每5秒执行一次,可以通过这个网站(http://tools.jb51.net/code/Quartz_Cron_create)去生成要的cron表达式;

启动应用,控制台看效果:

SpringBoot创建定时任务的方式_第3张图片
这个方式的确很简单方便,但是有个缺点就是当我们需要去修改定时任务的执行周期或者停止的时候,我们需要到代码层去修改,重启。

三、数据库动态配置

直接从数据库中读取定时任务的指定执行时间,无需重启。

说明:

​ 通过定时任务线程池(ThreadPoolTaskScheduler),让每个任务类(TaskJob)实现的 ScheduleOfTask 接口(该接口实现Runnable接口,是一个个单独的线程);ScheduleTaskService 接口定义了定时任务的启动、暂停、重启、初始化等功能;ScheduleJobService 主要是对pojo中的定时任务个体做增改的操作。

​ 核心方法还是ScheduleTaskServiceImpl

SpringBoot创建定时任务的方式_第4张图片

3.1、创建pojo

SpringJpa根据创建的实体类自动创建表结构;前提必须创建相应的数据库(我在配置文件中已经开启了自动建库的功能);

SpringBoot创建定时任务的方式_第5张图片

package com.iflytask.schedule.pojo;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.Instant;

/**
 * @program: schedule
 * @description:
 * @author: xuYao2
 * @create: 2022-08-11 11:45
 **/
@EqualsAndHashCode
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "scheduled_job")
@EntityListeners(AuditingEntityListener.class)
public class ScheduleJob {
    @Id
    @Column(name = "id", nullable = false,columnDefinition = "bigint COMMENT '主键id'")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "job_key", columnDefinition = "varchar(128) COMMENT '定时任务完整类名'")
    private String jobKey;

    @Column(name = "cron_expression", columnDefinition = "varchar(20) COMMENT 'cron表达式'")
    private String cronExpression;

    @Column(name = "task_explain", columnDefinition = "varchar(255) COMMENT '任务描述'")
    private String taskExplain;

    @Column(name = "status", columnDefinition = "int(4) DEFAULT 1 COMMENT '状态,1:正常;-1:停用'")
    private Integer status;

    @CreatedBy
    @Column(name = "created_by", columnDefinition = "varchar(255) COMMENT '创建人'")
    private String createdBy;

    @CreatedDate
    @Column(name = "created_date",columnDefinition = "timestamp COMMENT '创建时间'")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Instant createdDate;

    @LastModifiedBy
    @Column(name = "last_modified_by", columnDefinition = "varchar(255) COMMENT '修改人'")
    private String lastModifiedBy;

    @Column(name = "last_modified_date", columnDefinition = "timestamp COMMENT '修改时间'")
    @LastModifiedDate
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Instant lastModifiedDate;

}

这里的实体类使用到了注解 :@EntityListeners(AuditingEntityListener.class)

请添加图片描述

该注解可以在保存数据的同时,自动创建以下四个数据:

SpringBoot创建定时任务的方式_第6张图片

前提是启动类上加入@EnableJpaAuditing 注解

SpringBoot创建定时任务的方式_第7张图片

加入pojo中的@CreatedBy@LastModifiedBy注解,需要一个获取当前用户的配置类:

SpringBoot创建定时任务的方式_第8张图片

package com.iflytask.schedule.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;

import java.util.Optional;

/**
 * @program: schedule
 * @description: 在创建或者更新由 @EntityListeners 注解标注的pojo时, 自动加上用户名
 * @author: xuYao2
 * @create: 2022-08-11 11:50
 **/
@Configuration
public class CurrentUserConfig implements AuditorAware<String> {

    /**
     * 这里是写死的
     * @return
     */
    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.of("jerry");
    }

/**
    // 通过Security即可动态获取用户名, 需要集成springSecurity
    @Override
    public Optional getCurrentAuditor() {
        UserDetails user;
        try {
            user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            return Optional.ofNullable(user.getUsername());
        } catch (Exception e) {
            return Optional.empty();
        }
    }
*/
}

3.2、定时任务线程池:

注释掉了@EnableScheduling,将写死的定时任务也注释掉;

SpringBoot创建定时任务的方式_第9张图片

package com.iflytask.schedule.config;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Console;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

/**
 * @program:
 * @description: @Configuration: 标记配置类; @EnableScheduling: 开启定时任务
 * @author: xuYao2
 * @create: 2022-08-11 11:27
 **/
@Configuration
// @EnableScheduling
public class ScheduleConfig {
    /***********************************************
     // 写死的定时任务
        @Scheduled(cron = "0/5 * * * * ? ")
        private void task(){
            Console.log("执行定时任务: {}", DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss"));
        }
     ***********************************************/

    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
        Console.log("创建定时任务线程池 start...");
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(20);
        threadPoolTaskScheduler.setThreadNamePrefix("taskExecutor-");
        threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
        Console.log("创建定时任务线程池 end...");
        return threadPoolTaskScheduler;
    }
}

3.3、业务处理

即定时任务实体类(ScheduleJob)对应的增改操作 ;

Repository接入层

SpringBoot创建定时任务的方式_第10张图片

package com.iflytask.schedule.repository;

import com.iflytask.schedule.pojo.ScheduleJob;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * @program: schedule
 * @description:
 * @author: xuYao2
 * @create: 2022-08-16 09:49
 **/
@Repository
public interface ScheduleJobRepository extends JpaRepository<ScheduleJob, Long> {
}

service服务层

SpringBoot创建定时任务的方式_第11张图片

package com.iflytask.schedule.service;

import com.iflytask.schedule.pojo.ScheduleJob;
import com.iflytask.schedule.pojo.dto.ScheduleJobDTO;

import java.util.List;

public interface ScheduleJobService {
    /**
     * 更新或新增
     * @param dto
     * @return
     */
    ScheduleJob updateTask(ScheduleJobDTO dto);

    /**
     * 保存多条ScheduleJob入库
     * @param scheduleJobList
     * @return
     */
    List<ScheduleJob> saveAll(List<ScheduleJob> scheduleJobList);

    /**
     * 获取所有的定时任务
     * @return
     */
    List<ScheduleJob> findAll();
}

服务层updateTask接口中的请求实体ScheduleJobDTO定义如下:

SpringBoot创建定时任务的方式_第12张图片

package com.iflytask.schedule.pojo.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
 * @program: schedule
 * @description:
 * @author: xuYao2
 * @create: 2022-08-11 11:45
 **/
@EqualsAndHashCode
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ScheduleJobDTO {

    private Long id;

    private String jobKey;

    private String cronExpression;

    private String taskExplain;

    private Integer status;

}

接入层实现类有个updateTask更新方法需要注意,这里使用到了反射原理,postman或者前端传给后端的数据,可能是数据库中某个数据条目的部分字段,我们再做更新的时候 ,不能将不需要更新的数据覆盖了或者直接复制空值,比如说,这里更新的时候就不会覆盖数据库中的created_date字段对应的值;

package com.iflytask.schedule.service.impl;

import cn.hutool.core.lang.Console;
import cn.hutool.core.util.ObjectUtil;
import com.iflytask.schedule.pojo.ScheduleJob;
import com.iflytask.schedule.pojo.dto.ScheduleJobDTO;
import com.iflytask.schedule.repository.ScheduleJobRepository;
import com.iflytask.schedule.service.ScheduleJobService;
import com.iflytask.schedule.service.ScheduleTaskService;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @program: schedule
 * @description:
 * @author: xuYao2
 * @create: 2022-08-16 09:57
 **/
@Service
public class ScheduleJobServiceImpl implements ScheduleJobService {

    private final ScheduleJobRepository scheduleJobRepository;

    private final ScheduleTaskService scheduleTaskService;

    public ScheduleJobServiceImpl(ScheduleJobRepository scheduleJobRepository, ScheduleTaskService scheduleTaskService){
        this.scheduleJobRepository = scheduleJobRepository;
        this.scheduleTaskService = scheduleTaskService;

    }

    @Override
    public List<ScheduleJob> saveAll(List<ScheduleJob> scheduleJobList) {
        return scheduleJobRepository.saveAll(scheduleJobList);
    }

    @Override
    public ScheduleJob updateTask(ScheduleJobDTO dto) {

        ScheduleJob scheduleJob = scheduleJobRepository.findById(dto.getId()).get();

        scheduleJob = convert(dto, scheduleJob);

        ScheduleJob save = scheduleJobRepository.save(scheduleJob);

        if (ObjectUtil.isNotEmpty(save)) {
            scheduleTaskService.restart(save);
        } 

        return save;
    }

    /**
     * 将dto中非空属性对应的值赋值到scheduleJob
     * @param dto 请求体
     * @param scheduleJob 数据库中存在的实体
     * @return
     */
    private ScheduleJob convert(ScheduleJobDTO dto, ScheduleJob scheduleJob) {

        try {
            Class<?> entityClazz = scheduleJob.getClass();
            Class<?> patchDtoClass = dto.getClass();
            Class<?> generatedPatchDtoClass = patchDtoClass.getSuperclass();

            List<Field> fields = new ArrayList<>(Arrays.asList(patchDtoClass.getDeclaredFields()));
            fields.addAll(Arrays.asList(generatedPatchDtoClass.getDeclaredFields()));
            for (Field dtoField : fields.stream().filter(field -> !"serialVersionUID".equals(field.getName())).collect(Collectors.toList())) {
                try {
                    Field entityField = entityClazz.getDeclaredField(dtoField.getName());
                    dtoField.setAccessible(true);
                    if (dtoField.get(dto) != null) {
                        entityField.setAccessible(true);
                        entityField.set(scheduleJob, dtoField.get(dto));
                    }
                } catch (Exception e) {
                    Console.error("更新{}字段出现异常!", dtoField.getName(), e);
                }
            }
        } catch (Exception e) {
            Console.error("更新部分字段出现异常!", e);
            return null;
        }
        scheduleJob.setLastModifiedDate(Instant.now());
        return scheduleJob;

    }

    /**
     * 获取所有的定时任务
     * @return
     */
    @Override
    public List<ScheduleJob> findAll() {
        return scheduleJobRepository.findAll();
    }

}

3.4、定时任务执行接口和实现类;

SpringBoot创建定时任务的方式_第13张图片

ScheduleTaskServiceScheduleTaskServiceImpl主要是对定义好的定时任务pojo(这里就是第一步定义的ScheduleJob),启动、暂停、重启和初始化等操作;

package com.iflytask.schedule.service;

import com.iflytask.schedule.pojo.ScheduleJob;

public interface ScheduleTaskService {
    Boolean start(ScheduleJob scheduleJob);
    Boolean stop(String jobKey);
    Boolean restart(ScheduleJob scheduleJob);
    void initTask();
}

这里可能会出现循环依赖,使用@Lazy注解(当我使用高版本的springboot会出现,本文的下面这个版本就不会出现);见【pom.xml】。

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.2.2.RELEASEversion>
    <relativePath/> 
parent>
package com.iflytask.schedule.service.impl;

import cn.hutool.core.lang.Console;
import com.iflytask.schedule.pojo.ScheduleJob;
import com.iflytask.schedule.service.ScheduleJobService;
import com.iflytask.schedule.service.ScheduleTaskService;
import com.iflytask.schedule.task.ScheduleOfTask;
import com.iflytask.schedule.util.SpringContextUtil;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @program: schedule
 * @description: 定时任务管理实现类
 * @author: xuYao2
 * @create: 2022-08-16 09:30
 **/
@Service
public class ScheduleTaskServiceImpl implements ScheduleTaskService {

    /**
     * 可重入锁
     */
    private ReentrantLock lock = new ReentrantLock();

    /**
     * 启动状态的定时任务集合
     */
    public Map<String, ScheduledFuture> scheduleFutureMap = new ConcurrentHashMap<String, ScheduledFuture>();

    /**
     * 定时任务线程池
     */
    @Resource
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    @Resource
    @Lazy
    private ScheduleJobService scheduleJobService;

    @Override
    public Boolean start(ScheduleJob scheduleJob) {

        String jobKey = scheduleJob.getJobKey();
        Console.log("启动定时任务: {}", jobKey);
        // 添加锁放一个线程启动,防止多人多次启动多次
        lock.lock();
        Console.log("加锁完成");

        try {
            if (this.isStart(jobKey)) {
                Console.log("当前任务启动状态中");
                return false;
            }
            // 任务启动
            this.doStartTask(scheduleJob);
        } finally {
            lock.unlock();
            Console.log("解锁完毕");
        }
        return true;
    }

    /**
     * 执行启动任务
     * @param scheduleJob
     */
    private void doStartTask(ScheduleJob scheduleJob) {
        Console.log("{}, 正在执行",scheduleJob.getJobKey());
        if (scheduleJob.getStatus() != 1) {
            return;
        }

        Class<?> clazz;
        ScheduleOfTask task;
        try {
            clazz = Class.forName(scheduleJob.getJobKey());
            task = (ScheduleOfTask) SpringContextUtil.getBean(clazz);

        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("scheduled_job 表数据 【" + scheduleJob.getJobKey() + "】有误", e);
        }

        Assert.isAssignable(ScheduleOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口");
        ScheduledFuture scheduledFuture = threadPoolTaskScheduler.schedule(task, (triggerContext -> new CronTrigger(scheduleJob.getCronExpression()).nextExecutionTime(triggerContext)));
        scheduleFutureMap.put(scheduleJob.getJobKey(), scheduledFuture);
    }

    /**
     * 任务是否启动
     * @param jobKey
     * @return
     */
    private boolean isStart(String jobKey) {
        // 校验是否已经启动
        if (scheduleFutureMap.containsKey(jobKey)) {
            if (!scheduleFutureMap.get(jobKey).isCancelled()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Boolean stop(String jobKey) {
        Console.log("停止任务: {}", jobKey);
        boolean flag = scheduleFutureMap.containsKey(jobKey);
        Console.log("当前任务是否存在: {}", flag);

        if (flag) {
            ScheduledFuture scheduledFuture = scheduleFutureMap.get(jobKey);
            scheduledFuture.cancel(true);
            scheduleFutureMap.remove(jobKey);
        }

        return flag;
    }

    @Override
    public Boolean restart(ScheduleJob scheduleJob) {
        Console.log("重启定时任务: {}", scheduleJob.getJobKey());

        // 停止
        this.stop(scheduleJob.getJobKey());
        return this.start(scheduleJob);
    }

    @Override
    public void initTask() {
        List<ScheduleJob> list = scheduleJobService.findAll();
        for (ScheduleJob scheduleJob : list) {
            if (scheduleJob.getStatus() == -1) {
                continue;
            }
            doStartTask(scheduleJob);
        }
    }
}

使用到的工具类:

SpringBoot创建定时任务的方式_第14张图片

package com.iflytask.schedule.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @program: schedule
 * @description:
 * @author: xuYao2
 * @create: 2022-08-19 16:26
 **/
@Component
public class SpringContextUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringContextUtil.applicationContext == null) {
            SpringContextUtil.applicationContext = applicationContext;
        }
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

}

3.5、ApplicationRunner

启动项目后自动添加定时任务

SpringBoot创建定时任务的方式_第15张图片

实现了ApplicationRunner接口,注入ScheduleTaskService(定时任务执行接口和实现类)

package com.iflytask.schedule.config;

import cn.hutool.core.lang.Console;
import com.iflytask.schedule.service.ScheduleTaskService;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @program: schedule
 * @description:
 * @author: xuYao2
 * @create: 2022-08-16 13:51
 **/
@Component
public class ScheduleTaskRunner implements ApplicationRunner {
    @Resource
    private ScheduleTaskService scheduleTaskService;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Console.log("--- 初始化定时任务 ---");
        scheduleTaskService.initTask();
        Console.log("--- 初始化定时完成 ---");
    }
}

3.7、定时任务

定义定时任务,前面的pojo是为了记录定时任务记录 ,其中的job_key字段就是现在定时任务的包名+类名的完整路径;

SpringBoot创建定时任务的方式_第16张图片

先定义接口,该接口继承Runnable类,表示每个定时任务都是单个的线程;

package com.iflytask.schedule.task;

public interface ScheduleOfTask extends Runnable{

    void execute();

    @Override
    default void run() {
        execute();
    }
}

定义两个定时任务实现类:

package com.iflytask.schedule.task.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Console;
import com.iflytask.schedule.task.ScheduleOfTask;
import org.springframework.stereotype.Component;

/**
 * @program: schedule
 * @description:
 * @author: xuYao2
 * @create: 2022-08-16 09:39
 **/
@Component
public class TaskJob1 implements ScheduleOfTask {
    @Override
    public void execute() {
        Console.log("执行任务1: " + DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss"));
    }
}
package com.iflytask.schedule.task.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Console;
import com.iflytask.schedule.task.ScheduleOfTask;
import org.springframework.stereotype.Component;

/**
 * @program: schedule
 * @description:
 * @author: xuYao2
 * @create: 2022-08-16 09:39
 **/
@Component
public class TaskJob2 implements ScheduleOfTask {
    @Override
    public void execute() {
        Console.log("执行任务2: " + DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss"));
    }
}

3.8、业务控制层

初始化数据库数据和更新定时任务,在不关闭系统的情况下,实时更新

SpringBoot创建定时任务的方式_第17张图片

package com.iflytask.schedule.controller;

import cn.hutool.core.util.ObjectUtil;
import com.iflytask.schedule.mapper.ScheduleConvertMapper;
import com.iflytask.schedule.pojo.ScheduleJob;
import com.iflytask.schedule.pojo.dto.ScheduleJobDTO;
import com.iflytask.schedule.service.ScheduleJobService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

/**
 * @program: schedule
 * @description:
 * @author: xuYao2
 * @create: 2022-08-11 14:05
 **/
@RestController
@RequestMapping("api")
public class ScheduleTaskController {

    private final ScheduleJobService scheduleJobService;

    private final ScheduleConvertMapper scheduleConvertMapper;

    public ScheduleTaskController(ScheduleJobService scheduleJobService, ScheduleConvertMapper scheduleConvertMapper){
        this.scheduleJobService = scheduleJobService;
        this.scheduleConvertMapper = scheduleConvertMapper;
    }

    @PostMapping("/init")
    public String init(){
        ArrayList<ScheduleJobDTO> jobArrayList = new ArrayList<>();

        try {
            jobArrayList.add(ScheduleJobDTO.builder()
                .jobKey("com.iflytask.schedule.task.impl.TaskJob1")
                .cronExpression("0/5 * * * * ?")
                .taskExplain("定时任务1")
                .status(1)
                .build());

            jobArrayList.add(ScheduleJobDTO.builder()
                .jobKey("com.iflytask.schedule.task.impl.TaskJob2")
                .cronExpression("0/12 * * * * ?")
                .taskExplain("定时任务2")
                .status(1)
                .build());

            List<ScheduleJob> scheduleJobList = scheduleConvertMapper.convertToScheduleJobList(jobArrayList);

            scheduleJobService.saveAll(scheduleJobList);

            return "初始化成功";
        } catch (Exception e) {
            return String.format("初始化失败:%s", e.getMessage());
        }
    }

    @PostMapping("/update")
    public String updateTask(@RequestBody ScheduleJobDTO dto){
        ScheduleJob save = scheduleJobService.updateTask(dto);
        if (ObjectUtil.isNotEmpty(save)) {
            return "修改成功";
        } else {
            return "修改失败";
        }
    }

    @GetMapping("/findAll")
    public List<ScheduleJob> findAll(){
        return scheduleJobService.findAll();
    }
}

这里使用到了mapstruct,需要在POM文件中导入坐标:

<dependency>
    <groupId>org.mapstructgroupId>
    <artifactId>mapstruct-jdk8artifactId>
    <version>1.3.1.Finalversion>
dependency>
<dependency>
    <groupId>org.mapstructgroupId>
    <artifactId>mapstruct-processorartifactId>
    <version>1.3.1.Finalversion>
dependency>

并定定义mapper接口:相当于一个对象转换;

SpringBoot创建定时任务的方式_第18张图片

package com.iflytask.schedule.mapper;

import com.iflytask.schedule.pojo.ScheduleJob;
import com.iflytask.schedule.pojo.dto.ScheduleJobDTO;
import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy;
import org.springframework.stereotype.Component;

import java.util.List;

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
@Component
public interface ScheduleConvertMapper {

    List<ScheduleJob> convertToScheduleJobList(List<ScheduleJobDTO> jobDTOList);
}

3.9、测试

直接启动项目,自动为我们创建好数据库和表;

注意:这里我重新定义了PropertyContainer.java,并在项目的同级目录下创建包org.hibernate.cfg,为的是让jpa在自动创建表时,就根据pojo中定义的属性位置,按照顺序填充表的字段;(注意高版本的springboot可能不适用,需要找其他方法)

SpringBoot创建定时任务的方式_第19张图片

package org.hibernate.cfg;

import org.hibernate.AnnotationException;
import org.hibernate.annotations.Any;
import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.Target;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.MappingException;
import org.hibernate.boot.jaxb.Origin;
import org.hibernate.boot.jaxb.SourceType;
import org.hibernate.cfg.annotations.HCANNHelper;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.jboss.logging.Logger;

import javax.persistence.*;
import java.util.*;

class PropertyContainer {
    private static final CoreMessageLogger LOG = (CoreMessageLogger)Logger.getMessageLogger(CoreMessageLogger.class, org.hibernate.cfg.PropertyContainer.class.getName());
    private final XClass xClass;
    private final XClass entityAtStake;
    private final AccessType classLevelAccessType;
    private final LinkedHashMap<String, XProperty> persistentAttributeMap;

    PropertyContainer(XClass clazz, XClass entityAtStake, AccessType defaultClassLevelAccessType) {
        this.xClass = clazz;
        this.entityAtStake = entityAtStake;
        if (defaultClassLevelAccessType == AccessType.DEFAULT) {
            defaultClassLevelAccessType = AccessType.PROPERTY;
        }

        AccessType localClassLevelAccessType = this.determineLocalClassDefinedAccessStrategy();

        assert localClassLevelAccessType != null;

        this.classLevelAccessType = localClassLevelAccessType != AccessType.DEFAULT ? localClassLevelAccessType : defaultClassLevelAccessType;

        assert this.classLevelAccessType == AccessType.FIELD || this.classLevelAccessType == AccessType.PROPERTY;

        this.persistentAttributeMap = new LinkedHashMap<>();
        List<XProperty> fields = this.xClass.getDeclaredProperties(AccessType.FIELD.getType());
        List<XProperty> getters = this.xClass.getDeclaredProperties(AccessType.PROPERTY.getType());
        this.preFilter(fields, getters);
        Map<String, XProperty> persistentAttributesFromGetters = new HashMap();
        this.collectPersistentAttributesUsingLocalAccessType(this.persistentAttributeMap, persistentAttributesFromGetters, fields, getters);
        this.collectPersistentAttributesUsingClassLevelAccessType(this.persistentAttributeMap, persistentAttributesFromGetters, fields, getters);
    }

    private void preFilter(List<XProperty> fields, List<XProperty> getters) {
        Iterator propertyIterator = fields.iterator();

        XProperty property;
        while(propertyIterator.hasNext()) {
            property = (XProperty)propertyIterator.next();
            if (mustBeSkipped(property)) {
                propertyIterator.remove();
            }
        }

        propertyIterator = getters.iterator();

        while(propertyIterator.hasNext()) {
            property = (XProperty)propertyIterator.next();
            if (mustBeSkipped(property)) {
                propertyIterator.remove();
            }
        }

    }

    private void collectPersistentAttributesUsingLocalAccessType(LinkedHashMap<String, XProperty> persistentAttributeMap, Map<String, XProperty> persistentAttributesFromGetters, List<XProperty> fields, List<XProperty> getters) {
        Iterator propertyIterator = fields.iterator();

        XProperty xProperty;
        Access localAccessAnnotation;
        while(propertyIterator.hasNext()) {
            xProperty = (XProperty)propertyIterator.next();
            localAccessAnnotation = (Access)xProperty.getAnnotation(Access.class);
            if (localAccessAnnotation != null && localAccessAnnotation.value() == javax.persistence.AccessType.FIELD) {
                propertyIterator.remove();
                persistentAttributeMap.put(xProperty.getName(), xProperty);
            }
        }

        propertyIterator = getters.iterator();

        while(propertyIterator.hasNext()) {
            xProperty = (XProperty)propertyIterator.next();
            localAccessAnnotation = (Access)xProperty.getAnnotation(Access.class);
            if (localAccessAnnotation != null && localAccessAnnotation.value() == javax.persistence.AccessType.PROPERTY) {
                propertyIterator.remove();
                String name = xProperty.getName();
                XProperty previous = (XProperty)persistentAttributesFromGetters.get(name);
                if (previous != null) {
                    throw new MappingException(LOG.ambiguousPropertyMethods(this.xClass.getName(), HCANNHelper.annotatedElementSignature(previous), HCANNHelper.annotatedElementSignature(xProperty)), new Origin(SourceType.ANNOTATION, this.xClass.getName()));
                }

                persistentAttributeMap.put(name, xProperty);
                persistentAttributesFromGetters.put(name, xProperty);
            }
        }

    }

    private void collectPersistentAttributesUsingClassLevelAccessType(LinkedHashMap<String, XProperty> persistentAttributeMap, Map<String, XProperty> persistentAttributesFromGetters, List<XProperty> fields, List<XProperty> getters) {
        Iterator var5;
        XProperty getter;
        if (this.classLevelAccessType == AccessType.FIELD) {
            var5 = fields.iterator();

            while(var5.hasNext()) {
                getter = (XProperty)var5.next();
                if (!persistentAttributeMap.containsKey(getter.getName())) {
                    persistentAttributeMap.put(getter.getName(), getter);
                }
            }
        } else {
            var5 = getters.iterator();

            while(var5.hasNext()) {
                getter = (XProperty)var5.next();
                String name = getter.getName();
                XProperty previous = (XProperty)persistentAttributesFromGetters.get(name);
                if (previous != null) {
                    throw new MappingException(LOG.ambiguousPropertyMethods(this.xClass.getName(), HCANNHelper.annotatedElementSignature(previous), HCANNHelper.annotatedElementSignature(getter)), new Origin(SourceType.ANNOTATION, this.xClass.getName()));
                }

                if (!persistentAttributeMap.containsKey(name)) {
                    persistentAttributeMap.put(getter.getName(), getter);
                    persistentAttributesFromGetters.put(name, getter);
                }
            }
        }

    }

    public XClass getEntityAtStake() {
        return this.entityAtStake;
    }

    public XClass getDeclaringClass() {
        return this.xClass;
    }

    public AccessType getClassLevelAccessType() {
        return this.classLevelAccessType;
    }

    public Collection<XProperty> getProperties() {
        this.assertTypesAreResolvable();
        return Collections.unmodifiableCollection(this.persistentAttributeMap.values());
    }

    private void assertTypesAreResolvable() {
        Iterator var1 = this.persistentAttributeMap.values().iterator();

        XProperty xProperty;
        do {
            if (!var1.hasNext()) {
                return;
            }

            xProperty = (XProperty)var1.next();
        } while(xProperty.isTypeResolved() || discoverTypeWithoutReflection(xProperty));

        String msg = "Property " + StringHelper.qualify(this.xClass.getName(), xProperty.getName()) + " has an unbound type and no explicit target entity. Resolve this Generic usage issue or set an explicit target attribute (eg @OneToMany(target=) or use an explicit @Type";
        throw new AnnotationException(msg);
    }

    private AccessType determineLocalClassDefinedAccessStrategy() {
        AccessType hibernateDefinedAccessType = AccessType.DEFAULT;
        AccessType jpaDefinedAccessType = AccessType.DEFAULT;
        org.hibernate.annotations.AccessType accessType = (org.hibernate.annotations.AccessType)this.xClass.getAnnotation(org.hibernate.annotations.AccessType.class);
        if (accessType != null) {
            hibernateDefinedAccessType = AccessType.getAccessStrategy(accessType.value());
        }

        Access access = (Access)this.xClass.getAnnotation(Access.class);
        if (access != null) {
            jpaDefinedAccessType = AccessType.getAccessStrategy(access.value());
        }

        if (hibernateDefinedAccessType != AccessType.DEFAULT && jpaDefinedAccessType != AccessType.DEFAULT && hibernateDefinedAccessType != jpaDefinedAccessType) {
            throw new org.hibernate.MappingException("@AccessType and @Access specified with contradicting values. Use of @Access only is recommended. ");
        } else {
            AccessType classDefinedAccessType;
            if (hibernateDefinedAccessType != AccessType.DEFAULT) {
                classDefinedAccessType = hibernateDefinedAccessType;
            } else {
                classDefinedAccessType = jpaDefinedAccessType;
            }

            return classDefinedAccessType;
        }
    }

    private static boolean discoverTypeWithoutReflection(XProperty p) {
        if (p.isAnnotationPresent(OneToOne.class) && !((OneToOne)p.getAnnotation(OneToOne.class)).targetEntity().equals(Void.TYPE)) {
            return true;
        } else if (p.isAnnotationPresent(OneToMany.class) && !((OneToMany)p.getAnnotation(OneToMany.class)).targetEntity().equals(Void.TYPE)) {
            return true;
        } else if (p.isAnnotationPresent(ManyToOne.class) && !((ManyToOne)p.getAnnotation(ManyToOne.class)).targetEntity().equals(Void.TYPE)) {
            return true;
        } else if (p.isAnnotationPresent(ManyToMany.class) && !((ManyToMany)p.getAnnotation(ManyToMany.class)).targetEntity().equals(Void.TYPE)) {
            return true;
        } else if (p.isAnnotationPresent(Any.class)) {
            return true;
        } else if (p.isAnnotationPresent(ManyToAny.class)) {
            if (!p.isCollection() && !p.isArray()) {
                throw new AnnotationException("@ManyToAny used on a non collection non array property: " + p.getName());
            } else {
                return true;
            }
        } else if (p.isAnnotationPresent(Type.class)) {
            return true;
        } else {
            return p.isAnnotationPresent(Target.class);
        }
    }

    private static boolean mustBeSkipped(XProperty property) {
        return property.isAnnotationPresent(Transient.class) || "net.sf.cglib.transform.impl.InterceptFieldCallback".equals(property.getType().getName()) || "org.hibernate.bytecode.internal.javassist.FieldHandler".equals(property.getType().getName());
    }
}

这个类可以在maven中的jar包中搜索到

SpringBoot创建定时任务的方式_第20张图片

区别是就是把里面的TreeMap全改成了LinkedHashMap,相当于方法重写吧;

SpringBoot创建定时任务的方式_第21张图片

就是不加上面的配置自动生成的表是下面这样:

在这里插入图片描述

加了上面的配置,直接启动项目,自动创建了表,创建的表是下面这种,只是都没有数据。

在这里插入图片描述

postman上初始化数据

SpringBoot创建定时任务的方式_第22张图片

刷新数据库库表

在这里插入图片描述

如果数据库中一开始就有这种数据的话,启动就会立即执行定时任务,现在刚加入数据,需要我们手动刷新一下

刷新定时任务1

SpringBoot创建定时任务的方式_第23张图片

刷新定时任务2

SpringBoot创建定时任务的方式_第24张图片

清空控制台看打印结果

SpringBoot创建定时任务的方式_第25张图片

任务1是每5秒执行一次,任务2是每12秒执行一次;

现在我修改任务2每2秒执行一次

SpringBoot创建定时任务的方式_第26张图片

关闭任务1

SpringBoot创建定时任务的方式_第27张图片

再看控制台

任务1不再使用,任务2每2秒执行一次;
SpringBoot创建定时任务的方式_第28张图片

你可能感兴趣的:(工作随笔,spring,boot,java,后端,mysql)