ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

springboot实现闹钟功能(动态定时器)

2021-11-30 19:01:48  阅读:204  来源: 互联网

标签:定时器 methodName springboot corn beanName params 闹钟 return public


文章目录

前言

项目中遇到一个延迟闹钟功能,谨以此篇博客和大家分享下。

需求

有个日程功能需要添加个闹钟提醒功能,可以设置一次提醒和多次提醒,并且可以设置提醒时间范围。

总体流程

  1. 通过接口新增一个闹铃(选择提醒时间,设置范围)
  2. 解析参数生成corn表达式,并生成一条任务数据存入数据库
  3. 判断闹铃的下一次时间是否有今天,有的话需要马上新增一条任务
  4. 每天晚上定时去获取数据库的数据通过判断时间范围来区分,找到有效的闹铃加到任务中去,因为有些任务并不是当天执行的,可能设在几个月后
  5. 任务失效后去数据库把失效字段设为失效,扫描的时候不扫描失效数据
  6. 服务重启也要把所有有效任务加上

实现

新增任务关键逻辑

String corn = "";
        if (appSchedule.getPushType() == DataConstant.ONE) {
            int day = localDateTime.getDayOfMonth();
            int monthValue = localDateTime.getMonthValue();
            int year = localDateTime.getYear();
            // 一次
            corn = second + " " + minute + " " + hour + " " + day + " " + monthValue + " ?";
        } else if (appSchedule.getPushType() == DataConstant.TWO) {
            // 每天
            corn = second + " " + minute + " " + hour + " * * ?";
        } else if (appSchedule.getPushType() == DataConstant.ZERO) {
            // 每月
            int day = localDateTime.getDayOfMonth();
            corn = second + " " + minute + " " + hour + " " + day + " * ?";
        } else  {
            // 每周几 pushtype-2就是周几
            int week = appSchedule.getPushType() - DataConstant.TWO;
            corn = second + " " + minute + " " + hour + " ? * " + week;
        }
        appSchedule.setCorn(corn);
        // 新增
        appScheduleService.save(appSchedule);
        // 如果是今天有执行的任务就注册定时器,不然就是晚上凌晨自动注册
        List<String> recentDataByCorn = getRecentDataByCorn(corn, 1, new Date());
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime nextDay = LocalDateTime.parse(recentDataByCorn.get(DataConstant.ZERO), dateTimeFormatter);
        if (nextDay.toLocalDate().isEqual(LocalDate.now())) {
            // 注册定时器,定时器执行的时候会调用appScheduleService的pushOne方法
            SchedulingRunnable task = new SchedulingRunnable("appScheduleService", "pushOne", appSchedule);
            cronTaskRegistrar.addCronTask(appSchedule.getId(), task, corn);
        }

注意: appScheduleService是注册到spring里的beanName

解析corn

 /**
     * 解析corn获取最近数据
     * @param corn
     * @param size 获取条数
     * @param startDate 开始时间
     * @return
     */
    private List<String> getRecentDataByCorn(String corn, Integer size, Date startDate) {
        CronSequenceGenerator cronSequenceGenerator = new CronSequenceGenerator(corn);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        List<String> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            // 计算下次时间点的开始时间
            startDate = cronSequenceGenerator.next(startDate);
            list.add(sdf.format(startDate));
        }
        return list;
    }

在这里插入图片描述
任务的管理通过CronTaskRegistrar类实现:
任务开启:
在这里插入图片描述
任务关闭:
在这里插入图片描述

ScheduledTask.java

public final class ScheduledTask {

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

线程池配置类:SchedulingConfig.class

@Configuration
public class SchedulingConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 定时任务执行线程池核心线程数
        taskScheduler.setPoolSize(4);
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        return taskScheduler;
    }
}

程序启动时加载数据库中的定时器

@Component
@Transactional(rollbackFor = Exception.class)
public class SchedulingInitConfig {

    @Resource
    AppScheduleMapper appScheduleMapper;

    @Resource
    CronTaskRegistrar cronTaskRegistrar;

    /**
     * 在服务启动时查询数据库数据启动相关任务
     */
    @PostConstruct
    public void initFileSuffix() {
        //在服务启动时查询数据库数据启动相关任务
        LocalDate now = LocalDate.now();
        List<AppSchedule> appSchedules = appScheduleMapper.selectInitList(now);
        if (DataUtil.isEmpty(appSchedules)) {
            return;
        }
        appSchedules.forEach(v -> {
            SchedulingRunnable task = new SchedulingRunnable("appScheduleService", "pushOne", v);
            cronTaskRegistrar.addCronTask(v.getId(), task, v.getCorn());
        });
    }

    /**
     * 每天凌晨启动有效的定时任务,同时去掉过时的定时任务
     */
    @Async("DefaultExecutor")
    @Scheduled(cron = "0 0 0 * * ?")
    public void updateRechargeRecord() {
        // 找过时且有效的数据, 改成失效
        LocalDate now = LocalDate.now();
        List<AppSchedule> appSchedules = appScheduleMapper.selectNotValidList(now);
        if (DataUtil.isNotEmpty(appSchedules)) {
            // 批量失效
            appScheduleMapper.batchUpdateIfValid(appSchedules.stream().map(v -> {return v.getId().toString();}).collect(Collectors.joining(",")));
        }
        // 启动有效的定时任务
        List<AppSchedule> validList = appScheduleMapper.selectInitList(now);
        if (DataUtil.isNotEmpty(validList)) {
            validList.forEach(v -> {
                SchedulingRunnable task = new SchedulingRunnable("appScheduleService", "pushOne", v);
                cronTaskRegistrar.addCronTask(v.getId(), task, v.getCorn());
            });
        }
    }
}

定时器执行类:

public class SchedulingRunnable implements Runnable {

    private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);

    private String beanName;

    private String methodName;

    private Object[] params;

    public SchedulingRunnable(String beanName, String methodName) {
        this(beanName, methodName, null);
    }

    public SchedulingRunnable(String beanName, String methodName, Object...params ) {
        this.beanName = beanName;
        this.methodName = methodName;
        this.params = params;
    }

    @Override
    public void run() {
        logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);
        long startTime = System.currentTimeMillis();

        try {
            Object target = SpringContextTaskUtils.getBean(beanName);

            Method method = null;
            if (null != params && params.length > 0) {
                Class<?>[] paramCls = new Class[params.length];
                for (int i = 0; i < params.length; i++) {
                    paramCls[i] = params[i].getClass();
                }
                method = target.getClass().getDeclaredMethod(methodName, paramCls);
            } else {
                method = target.getClass().getDeclaredMethod(methodName);
            }

            ReflectionUtils.makeAccessible(method);
            if (null != params && params.length > 0) {
                method.invoke(target, params);
            } else {
                method.invoke(target);
            }
        } catch (Exception ex) {
            logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);
        }

        long times = System.currentTimeMillis() - startTime;
        logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SchedulingRunnable that = (SchedulingRunnable) o;
        if (params == null) {
            return beanName.equals(that.beanName) &&
                    methodName.equals(that.methodName) &&
                    that.params == null;
        }

        return beanName.equals(that.beanName) &&
                methodName.equals(that.methodName) &&
                params.equals(that.params);
    }

    @Override
    public int hashCode() {
        if (params == null) {
            return Objects.hash(beanName, methodName);
        }

        return Objects.hash(beanName, methodName, params);
    }
}

获取上下文类:

@Component
public class SpringContextTaskUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;

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

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取 Bean.
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

这样就实现了灵活配置定时器了

写在最后

非常感谢大家的认真阅读,如果有其他好用的技巧或者其他代码技巧都可以和我交流哦,如有不足,还望各位看官多批评指正=_=
技术交流秋秋群:719023986

微x关注:干饭必备外卖神券,每天领大额卷
微x关注:正好想买,自助查桃宝京d卷

标签:定时器,methodName,springboot,corn,beanName,params,闹钟,return,public
来源: https://blog.csdn.net/Levi_Wu/article/details/121633829

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有