Spring boot realizes alarm clock function (dynamic timer)

preface

There is a delayed alarm clock function in the project. I would like to share it with you on this blog.

demand

There is a schedule function that needs to add an alarm clock reminder function. You can set one reminder and multiple reminders, and you can set the reminder time range.

Overall process

  1. Add an alarm through the interface (select the reminder time and set the range)
  2. Parse the parameters, generate a corn expression, and generate a task data to store in the database
  3. Judge whether the next time of the alarm is today. If so, you need to add a task immediately
  4. Get the data from the database regularly every night, distinguish by judging the time range, and find an effective alarm to add to the task, because some tasks are not executed on the same day and may be set a few months later
  5. After the task fails, go to the database and set the failure field as failure. The failure data will not be scanned during scanning
  6. Service restart should also add all effective tasks

realization

Add task Critical Logic

String corn = "";
        if (appSchedule.getPushType() == DataConstant.ONE) {
            int day = localDateTime.getDayOfMonth();
            int monthValue = localDateTime.getMonthValue();
            int year = localDateTime.getYear();
            // once
            corn = second + " " + minute + " " + hour + " " + day + " " + monthValue + " ?";
        } else if (appSchedule.getPushType() == DataConstant.TWO) {
            // Every day
            corn = second + " " + minute + " " + hour + " * * ?";
        } else if (appSchedule.getPushType() == DataConstant.ZERO) {
            // monthly
            int day = localDateTime.getDayOfMonth();
            corn = second + " " + minute + " " + hour + " " + day + " * ?";
        } else  {
            // Day of the week pushtype-2 is the day of the week
            int week = appSchedule.getPushType() - DataConstant.TWO;
            corn = second + " " + minute + " " + hour + " ? * " + week;
        }
        appSchedule.setCorn(corn);
        // newly added
        appScheduleService.save(appSchedule);
        // If there are tasks to be performed today, register the timer, or register automatically in the early morning of the evening
        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())) {
            // Register the timer. When the timer is executed, it will call the pushOne method of appScheduleService
            SchedulingRunnable task = new SchedulingRunnable("appScheduleService", "pushOne", appSchedule);
            cronTaskRegistrar.addCronTask(appSchedule.getId(), task, corn);
        }

Note: appScheduleService is the beanName registered in spring

Parse corn

 /**
     * Parse corn to get the latest data
     * @param corn
     * @param size Get number of entries
     * @param startDate start time
     * @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++) {
            // Calculate the start time of the next time point
            startDate = cronSequenceGenerator.next(startDate);
            list.add(sdf.format(startDate));
        }
        return list;
    }


Task management is implemented through the CronTaskRegistrar class:
Task on:

Task close:

ScheduledTask.java

public final class ScheduledTask {

    public volatile ScheduledFuture<?> future;
    /**
     * Cancel scheduled task
     */
    public void cancel() {
        ScheduledFuture<?> future = this.future;
        if (future != null) {
            future.cancel(true);
        }
    }
}

Thread pool configuration class: schedulengconfig.class

@Configuration
public class SchedulingConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // Number of scheduled task execution thread pool core threads
        taskScheduler.setPoolSize(4);
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        return taskScheduler;
    }
}

Load the timer in the database when the program starts

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

    @Resource
    AppScheduleMapper appScheduleMapper;

    @Resource
    CronTaskRegistrar cronTaskRegistrar;

    /**
     * Query database data and start related tasks when the service is started
     */
    @PostConstruct
    public void initFileSuffix() {
        //Query database data and start related tasks when the service is started
        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());
        });
    }

    /**
     * Start effective scheduled tasks every morning and remove outdated scheduled tasks at the same time
     */
    @Async("DefaultExecutor")
    @Scheduled(cron = "0 0 0 * * ?")
    public void updateRechargeRecord() {
        // Find outdated and valid data and change it to invalid
        LocalDate now = LocalDate.now();
        List<AppSchedule> appSchedules = appScheduleMapper.selectNotValidList(now);
        if (DataUtil.isNotEmpty(appSchedules)) {
            // Batch failure
            appScheduleMapper.batchUpdateIfValid(appSchedules.stream().map(v -> {return v.getId().toString();}).collect(Collectors.joining(",")));
        }
        // Start a valid scheduled task
        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());
            });
        }
    }
}

Timer execution class:

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("Scheduled task start execution - bean: {},method:{},Parameters:{}", 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("Scheduled task execution exception - bean: %s,method:%s,Parameters:%s ", beanName, methodName, params), ex);
        }

        long times = System.currentTimeMillis() - startTime;
        logger.info("Scheduled task execution end - bean: {},method:{},Parameters:{},Time consuming:{} millisecond", 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);
    }
}

Get context class:

@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;
        }
    }

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

    //Get Bean. By name
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

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

    //Return the specified Bean through name and Clazz
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

In this way, the timer can be flexibly configured

Write at the end

Thank you very much for your careful reading. If you have other useful skills or other code skills, you can communicate with me. If there are deficiencies, please criticize and correct them more=_=
Technical exchange group: 719023986

Micro x attention: take out coupons are necessary for dry meals, and you can get large coupons every day
Micro x attention: just want to buy, self-help check taobaojing d roll

Tags: Java Windows Mybatis Spring Spring Boot

Posted on Tue, 30 Nov 2021 06:13:47 -0500 by mightymax