java中的实时定时任务管理系统

java中的实时定时任务管理系统

前言

本文将介绍在不集成quartz框架的情况下,用java代码实现定时任务的开启、关闭、修改等功能。

新增定时任务的线程池配置类

@Configuration
public class SchedulingConfig {
    @Bean
    public TaskScheduler taskScheduler(){
        /*
         * ThreadPoolTaskScheduler:线程池任务调度类,能够开启线程池进行任务调度。
         * 
         */
        ThreadPoolTaskScheduler poolTaskScheduler = new ThreadPoolTaskScheduler();

        // 定时任务执行线程池核心线程数
        poolTaskScheduler.setPoolSize(5);

        // 开启 remove-on-cancel
        poolTaskScheduler.setRemoveOnCancelPolicy(true);
        poolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler--");
        return poolTaskScheduler;
    }
}

配置ScheduledFuture的包装类。ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。

public final class ScheduledTask {
    /**
     *  ScheduledFuture 是 ScheduledExecutorService 定时任务线程池的执行结果。
     */
    volatile ScheduledFuture<?> future;

    /**
     * 取消 定时任务
     */
    public void cancel(){
        ScheduledFuture<?> future = this.future;
        if (future != null) {
            future.cancel(true);
        }
    }
}

实现Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法

这里的notiId与notiType是我的业务参数,可删除,定时任务执行run()的时候只需要枚举类taskEnum里的参数就够了。

public class TaskRunnable implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(TaskRunnable.class);

    private String notiId;

    private Integer notiType;

    private TaskEnum taskEnum;

    public TaskRunnable(String notiId, Integer notiType, TaskEnum taskEnum) {
        this.notiId = notiId;
        this.notiType = notiType;
        this.taskEnum = taskEnum;
    }

    @Override
    public void run() {
        LOGGER.info("定时任务开始执行:notiId:{},notiType:{},taskEnum:{}", notiId, notiType, taskEnum);

        // 开始执行时间
        long startTime = System.currentTimeMillis();

        try {
            // 获取到bean
            Object target = SpringContextUtil.getBean(taskEnum.getBeanName());
            // 获取到bean里面的方法
            Method method = null;
            method = target.getClass().getDeclaredMethod(taskEnum.getMethodName());
            ReflectionUtils.makeAccessible(method);
            method.invoke(target);
        } catch (Exception e) {
            LOGGER.error("定时任务执行异常 - notiId:{},notiType:{},taskEnum:{},exception:{}", notiId, notiType, taskEnum,e);
        }
        long times = System.currentTimeMillis() - startTime;
        LOGGER.info("定时任务执行结束 - notiId:{},notiType:{},taskEnum:{},time:{}", notiId, notiType, taskEnum, times);

    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof TaskRunnable)) {
            return false;}
        TaskRunnable that = (TaskRunnable) o;
        return Objects.equals(notiType, that.notiType) &&
                Objects.equals(notiId, that.notiId) &&
                taskEnum == that.taskEnum;
    }

    @Override
    public int hashCode() {

        return Objects.hash(notiType, notiId, taskEnum);
    }

    public static boolean isBlank(String str) {
        int strLen;
        if (str == null || (strLen = str.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

}

定时任务注册类,用来增加、删除定时任务

@Component
public class CronTaskRegistrar implements DisposableBean {

    /**
     * 存放定时任务
     */
    private final Map<Runnable, ScheduledTask>  scheduledTaskMap = new ConcurrentHashMap<>();

    @Autowired
    private TaskScheduler taskScheduler;

    /**
     * 添加定时任务类
     *
     * @param task
     * @param cronExpression cron表达式
     */
    public void addCronTask(Runnable task, String cronExpression) {
        addCronTask(new CronTask(task, cronExpression));
    }

    /**
     * 添加定时任务类
     *
     * @param cronTask
     */
    public void addCronTask(CronTask cronTask) {
        if (cronTask != null) {
            Runnable task = cronTask.getRunnable();
            if (this.scheduledTaskMap.containsKey(task)) {
                this.removeCronTask(task);
            }
            this.scheduledTaskMap.put(task, scheduledCronTask(cronTask));
        }

    }

    /**
     * 移除 定时任务,并取消
     *
     * @param task
     */
    public void removeCronTask(Runnable task) {
        ScheduledTask scheduledTask = this.scheduledTaskMap.remove(task);
        if (scheduledTask != null) {
            scheduledTask.cancel();
        }
    }

    /**
     * 调用线程池
     * ThreadPoolTaskScheduler.schedule()方法会创建一个定时计划ScheduledFuture,
     * 在这个方法需要添加两个参数,Runnable(线程接口类) 和CronTrigger(定时任务触发器)
     *
     * @param cronTask 定时任务
     * @return
     */
    public ScheduledTask scheduledCronTask(CronTask cronTask) {
        ScheduledTask scheduledTask = new ScheduledTask();
        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
        return scheduledTask;
    }


    /**
     * 重写销毁方法
     *
     * @throws Exception
     */
    @Override
    public void destroy() throws Exception {
        for (ScheduledTask task : this.scheduledTaskMap.values()) {
            task.cancel();
        }
        this.scheduledTaskMap.clear();
    }
}

定时任务示例类

@Component("SendMailTask")
public class SendMailTask{

    private static final Logger LOGGER = LoggerFactory.getLogger(SendMailTask.class);


    public void sendMail(){
        String transId = CommonFunction.getTransId();
        LOGGER.info("[{}] 定时发送邮件开始",transId);
        // 要实现的业务逻辑
        LOGGER.info("[{}] 定时发送邮件结束",transId);
    }

}

定时任务实体类SysJobVo

@Data
public class SysJobVo {

    /**
     *  定时任务id
     */
    private Long jobId;
    /**
     *  通知类型
     */
    private Integer notiType;
    /**
     *  通知配置表业务主键
     */
    private String notiId;
    /**
     *  定时任务表达式
     */
    private String cronExpression;
    /**
     *  定时任务状态
     */
    private Integer jobStatus;
    /**
     *  定时任务key 值
     */
    private String schKey;


}

定时任务实体类NotiSchRuleVo

@Data
public class NotiSchRuleVo {

    /**
     * id 自增主键
     */
    private Long id;

    /**
     * notiId 业务主键
     */
    private String notiId;

    /**
     * notiType 通知类型(0-系统消息;1-邮件通知;2-短信通知)
     */
    private Integer notiType;

    /**
     *  定时任务在数据库中存的key
     */
    private String schKey;

    /**
     *  定时事件描述
     */
    private String schDesc;

    /**
     *  status 任务状态,0-不启用(忽略,不做任何操作),1-立即执行一次,2-启动定时任务
     */
    private Integer status;

    /**
     * notiSch 通知事件提醒规则
     */
    private String notiSch;

    /**
     * createBy 创建人
     */
    private Long createBy;

    /**
     * createTime 创建时间
     */
    private Date createTime;

    /**
     * modifyBy 修改人
     */
    private Long modifyBy;

    /**
     * modifyTime 修改时间
     */
    private Date modifyTime;

    /**
     * deleted 删除标记
     */
    private Integer deleted;
}

枚举类,用schKey映射不同的定时任务要执行的方法

这里就是用SEND_MAIL去映射SendMailTask 类里面的sendMail方法。

public enum TaskEnum {

	
    SEND_MAIL_TASK("SEND_MAIL","SendMailTask","sendMail"),

    ;

    /**
    *   @desc  表中的SCH_KEY
    */
    private String schKey;

    /**
    *   @desc 要调用的类名
    */
    private String beanName;

    /**
    *   @desc 要调用的方法名
    */
    private String methodName;


    public String getSchKey() {
        return schKey;
    }

    public void setSchKey(String schKey) {
        this.schKey = schKey;
    }

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    TaskEnum() {
    }

    TaskEnum(String schKey) {
        this.schKey = schKey;
    }

    TaskEnum(String schKey, String beanName, String methodName) {
        this.schKey = schKey;
        this.beanName = beanName;
        this.methodName = methodName;
    }
}

定时任务实现类

@Service
public class SysTaskServiceImpl implements SysTaskService {

    @Autowired
    private CronTaskRegistrar cronTaskRegistrar;

    private static List<SysJobVo> jobList = new ArrayList<>();

    @Autowired
    private NoticeSchRuleMapper noticeSchRuleMapper;

    private final static Logger log = LoggerFactory.getLogger(SysTaskServiceImpl.class);

    /**
     * 查询进行中的定时任务类
     *
     * @param status
     */
    @Override
    public List<NotiSchRuleVo> selectTask(Integer status) {

        log.info("SysTaskServiceImpl selectTask, status={}",status);
        List<NotiSchRuleVo> noticeSchRuleList = noticeSchRuleMapper.selectAllSche(status);
        for(NotiSchRuleVo notiSchRuleVo : noticeSchRuleList){
            // 添加到定时任务列表
            SysJobVo jobVo = new SysJobVo();
            jobVo.setJobId(notiSchRuleVo.getId());
            jobVo.setCronExpression(notiSchRuleVo.getNotiSch());
            jobVo.setJobStatus(notiSchRuleVo.getStatus());
            jobVo.setNotiType(notiSchRuleVo.getNotiType());
            jobVo.setNotiId(notiSchRuleVo.getNotiId());
            jobVo.setSchKey(notiSchRuleVo.getSchKey());
            jobList.add(jobVo);
        }
        return noticeSchRuleList;
    }

    /**
     * 添加定时任务
     *
     * @param notiSchRuleVo
     */
    @Override
    public String addTask(NotiSchRuleVo notiSchRuleVo) {

        log.info("SysTaskServiceImpl addTask, status={}",notiSchRuleVo);
        // 处理数据 插入数据库
        noticeSchRuleMapper.insertOne(notiSchRuleVo);

        if(StringUtils.isNotEmpty(notiSchRuleVo.getNotiSch())){
            // 添加到定时任务列表
            SysJobVo jobVo = new SysJobVo();
            jobVo.setJobId(notiSchRuleVo.getId());
            jobVo.setCronExpression(notiSchRuleVo.getNotiSch());
            jobVo.setJobStatus(notiSchRuleVo.getStatus());
            jobVo.setNotiType(notiSchRuleVo.getNotiType());
            jobVo.setNotiId(notiSchRuleVo.getNotiId());
            jobVo.setSchKey(notiSchRuleVo.getSchKey());
            jobVo.setSchKey(notiSchRuleVo.getSchKey());
            jobList.add(jobVo);
            this.changeTaskStatus(Boolean.TRUE, jobVo);
        }
        return "addSuccess";

    }

    /**
     * 修改定时任务
     *
     * @param notiSchRuleVo
     */
    @Override
    public String updateTask( NotiSchRuleVo notiSchRuleVo) {
        // 获取数据库中已存在的数据
        Long jobId = notiSchRuleVo.getId();
        NoticeSchRule rule =  new NoticeSchRule();
        rule.setId(jobId);
        rule.setStatus(Constants.NOTI_SCH_STATUS_TWO);
        rule.setDeleted(DictConstant.DELETED_FALSE_INTEGER);
        NoticeSchRule noticeSchRule = noticeSchRuleMapper.selectOne(rule);

        Integer status = notiSchRuleVo.getStatus();
        // 创建任务 VO
        SysJobVo jobVo = new SysJobVo();
        jobVo.setJobId(notiSchRuleVo.getId());
        jobVo.setCronExpression(notiSchRuleVo.getNotiSch());
        jobVo.setJobStatus(status);
        jobVo.setNotiType(notiSchRuleVo.getNotiType());
        jobVo.setNotiId(notiSchRuleVo.getNotiId());
        jobVo.setSchKey(notiSchRuleVo.getSchKey());

        SysJobVo existJob = null;
        // 获取已经在执行的定时任务列表中已存在的数据
        for(SysJobVo sysJobVo : jobList){
            if(jobVo.getJobId().equals(sysJobVo.getJobId())){
                existJob = sysJobVo;
            }
        }

        if(null == existJob ){
            // 内存里没有此定时任务
            if(null == noticeSchRule){
                // 数据库也没有此定时任务
                if(2 == status){
                    // 启用状态,则开启新定时任务
                    // 开启新的定时任务
                    this.changeTaskStatus(Boolean.TRUE, jobVo);
                    jobList.add(jobVo);
                }
            }else {
                // 内存没有数据库有,则先从数据库修复此定时任务
                this.changeTaskStatus(Boolean.TRUE, jobVo);
                jobList.add(jobVo);
                existJob = jobVo;
                // 判断status
                if(2 == status){
                    // 启用状态,则先关闭现有的定时任务
                    this.changeTaskStatus(Boolean.FALSE, existJob);
                    jobList.remove(existJob);
                    // 启动新的定时任务
                    this.changeTaskStatus(Boolean.TRUE, jobVo);
                    jobList.add(jobVo);
                }else {
                    // 不启用状态,则把原来的关闭
                    this.changeTaskStatus(Boolean.FALSE, existJob);
                    jobList.remove(existJob);
                }
            }
        }else {
            // 内存里有此定时任务,则先关闭此定时任务,再判断是否启用
            this.changeTaskStatus(Boolean.FALSE, existJob);
            jobList.remove(existJob);
            if( 2 == status){
                // 开启新的定时任务
                this.changeTaskStatus(Boolean.TRUE, jobVo);
                jobList.add(jobVo);
            }
        }
        // 更新数据库
        BeanCopyUtil.copyPropertiesIgnoreNull(notiSchRuleVo,rule);
        noticeSchRuleMapper.updateByPrimaryKeySelective(rule);
        return "updateSuccess";

    }

    /**
     * 删除定时任务
     *
     * @param jobId
     */
    @Override
    public String deleteTask(Long jobId, Long operId) {
        NoticeSchRule noticeSchRule = noticeSchRuleMapper.selectByPrimaryKey(jobId);
        // 获取已经在执行的定时任务列表中已存在的数据
        SysJobVo existJob = new SysJobVo();
        existJob.setJobId(noticeSchRule.getId());
        existJob.setNotiId(noticeSchRule.getNotiId());
        existJob.setNotiType(noticeSchRule.getNotiType());
        existJob.setCronExpression(noticeSchRule.getNotiSch());
        existJob.setJobStatus(noticeSchRule.getStatus());
        existJob.setSchKey(noticeSchRule.getSchKey());

        // 判断定时任务是否开启
        if (Objects.equals(noticeSchRule.getStatus(), 2)) {
            // 关闭定时任务
            this.changeTaskStatus(Boolean.FALSE, existJob);
        }

        // 处理数据 插入数据库
        for (int i = 0; i < jobList.size(); i++) {
            Long jobId2 = jobList.get(i).getJobId();
            if (Objects.equals(jobId2,jobId)) {
                jobList.remove(i);
                break;
            }
        }
        // 更新数据库
        noticeSchRule.setModifyBy(operId);
        noticeSchRule.setModifyTime(new Date());
        noticeSchRule.setDeleted(Constants.DELETED_TRUE);
        noticeSchRuleMapper.updateByPrimaryKey(noticeSchRule);
        return "deleteSuccess";
    }

    /**
     * 修改定时任务类状态
     *
     * @param add
     * @param jobVo
     */
    private void changeTaskStatus(boolean add, SysJobVo jobVo) {
        if (add) {
            TaskRunnable task = new TaskRunnable( jobVo.getNotiId(),jobVo.getNotiType(),TaskEnum.valueOf(jobVo.getSchKey()+"_TASK"));
            cronTaskRegistrar.addCronTask(task, jobVo.getCronExpression());
        } else {
            TaskRunnable task = new TaskRunnable( jobVo.getNotiId(),jobVo.getNotiType(),TaskEnum.valueOf(jobVo.getSchKey()+"_TASK"));
            cronTaskRegistrar.removeCronTask(task);
        }
    }
}

初始化数据库中的定时任务

这里查询status为2的定时任务,则是查询数据库中状态为开启的定时任务。

@Service
public class SysJobRunner implements CommandLineRunner {
    private final static Logger LOGGER = LoggerFactory.getLogger(SysJobRunner.class);


    @Autowired
    private CronTaskRegistrar cronTaskRegistrar;

    @Autowired
    private SysTaskServiceImpl sysTaskService;

    @Override
    public void run(String... args) throws Exception {
        String transId = CommonFunction.getTransId();
        List<NotiSchRuleVo> noticeSchRuleList = sysTaskService.selectTask(2);
        LOGGER.error("[{}] SysJobRunner run,初始化定时任务 noticeSchRuleList = [{}]", transId, noticeSchRuleList);
        for(NotiSchRuleVo notiSchRuleVo : noticeSchRuleList){
            if(StringUtils.isNotEmpty(notiSchRuleVo.getNotiSch()) && StringUtils.isNotEmpty(notiSchRuleVo.getSchKey())){
                TaskRunnable task = new TaskRunnable(notiSchRuleVo.getNotiId(),notiSchRuleVo.getNotiType(),TaskEnum.valueOf(notiSchRuleVo.getSchKey()+"_TASK"));
                cronTaskRegistrar.addCronTask(task, notiSchRuleVo.getNotiSch());
            }
        }
        LOGGER.error("[{}] SysJobRunner run,定时任务初始化完毕", transId);
    }
}

总结

本文所实现的定时任务管理系统是通过每一个定时任务单独开启一个线程,用future对象去管理线程从而管理定时任务的。

可以达到定时任务的开启、关闭、修改执行计划后立即生效。

并且,将定时任务要执行的不同方法隐藏于代码之中,并不保存在数据库中,提高了系统的安全性。

在拓展定时任务时,只需1.写好实现定时任务要执行的业务逻辑,2.添加枚举类,3.调用接口添加定时任务(KEY值对应枚举类中的KEY值),就能够添加新的定时任务并开启对应的线程。

有问题请留言,一起交流学习。
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值