springboot动态定时器开发

一、背景

需要实现springboot定时调用项目类和第三方接口,并且可以通过页面进行动态配置。这里只展示后台部分,因为前台都是表单,不做展示,核心功能都在后台。代码中存在相关的冗余参数,主要是为相关业务的拓展保留参数。

二、后台功能代码展示

原理:创建map集合,将在spring容器实例化的定时器记录下来。每次新增定时器,一方面存数据库(没做,自行实现),另一方面存在集合中。删除同理。

1. 反射实现方法工具类

package serve.peam.schedule.reflect;

/**
 * spring容器的相关方法,如根据名称获取spring实例
 *
 * @author 天真热
 * @create 2022-04-28 9:55
 * @desc
 **/

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

@Component
public class SpringContextUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

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

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

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

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

    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    public static Class<? extends Object> getType(String name) {
        return applicationContext.getType(name);
    }
}

package serve.peam.schedule.reflect;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * 线程类,主要是实现反射方法
 *
 * @author 天真热
 * @create 2022-04-28 9:52
 * @desc
 **/
public class ScheduleClassReflect implements Runnable {
    private static final Logger logger = LoggerFactory.getLogger(ScheduleClassReflect.class);
    /**
     * 实例化到spring容器的实例名,一般是类名首字母小写
     */
    private String beanName;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 定时规则
     */
    private Long taskId;
    /**
     * 参数,预留
     */
    private String params;


    public ScheduleClassReflect(String beanName, String methodName, Long taskId) {
        this(beanName, methodName, taskId, null);
    }

    public ScheduleClassReflect(String beanName, String methodName, Long taskId, String params) {
        this.beanName = beanName;
        this.methodName = methodName;
        this.taskId = taskId;
        this.params = params;
    }

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

        try {
            //从spring容器获取实例
            Object target = SpringContextUtils.getBean(beanName);
            Method method = null;
            //获取方法
            if (StringUtils.isNotEmpty(params)) {
                method = target.getClass().getDeclaredMethod(methodName, String.class);
            } else {
                method = target.getClass().getDeclaredMethod(methodName);
            }
            //反射实现方法
            ReflectionUtils.makeAccessible(method);
            if (StringUtils.isNotEmpty(params)) {
                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);
    }

}

2. 远程调用接口工具类

package serve.peam.schedule.request;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

/**
 * 线程类,主要是实现反射方法
 *
 * @author 天真热
 * @create 2022-04-28 9:52
 * @desc
 **/
public class ScheduleRequest implements Runnable {
    private static final Logger logger = LoggerFactory.getLogger(ScheduleRequest.class);
    /**
     * 接口
     */
    private String requestInterface;
    /**
     * 接口类型,POST/GET
     */
    String requestType;
    /**
     * 定时规则
     */
    private Long taskId;
    /**
     * 参数,预留
     */
    private String params;

    public ScheduleRequest(String requestInterface, String requestType, Long taskId) {
        this(requestInterface, requestType, taskId, null);
    }

    public ScheduleRequest(String requestInterface, String requestType, Long taskId, String params) {
        this.requestInterface = requestInterface;
        this.requestType = requestType;
        this.params = params;
        this.taskId = taskId;
    }

    /**
     * 利用反射实现方法
     */
    @Override
    public void run() {
        logger.info("定时任务开始执行 - 接口:{},接口类型:{},参数:{}", requestInterface, requestType, params);
        long startTime = System.currentTimeMillis();
        try {
            //远程调用接口逻辑
            ScheduleHttpRequest.request(requestInterface, requestType, requestType);

        } catch (Exception ex) {
            logger.error(String.format("定时任务执行异常 - 接口:%s,接口类型:%s,参数:%s ", requestInterface, requestType, params), ex);
        }
        long times = System.currentTimeMillis() - startTime;
        logger.info("定时任务执行结束 - 接口:{},接口类型:{},参数:{},耗时:{} 毫秒", requestInterface, requestType, params, times);
    }

}

package serve.peam.schedule.request;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * 请求接口
 *
 * @author 天真热
 * @create 2022-04-28 10:11
 * @desc
 */
public class ScheduleHttpRequest {

    /**
     * 请求接口
     *
     * @param httpUrl     地址
     * @param requestType 请求方式
     * @param param       参数
     * @return
     */
    public static Boolean request(String httpUrl, String requestType, String param) {
        boolean isSuccess = false;
        HttpURLConnection connection = null;
        InputStream is = null;
        OutputStream os = null;
        BufferedReader br = null;
        String result = null;
        try {
            URL url = new URL(httpUrl);
            // 通过远程url连接对象打开连接
            connection = (HttpURLConnection) url.openConnection();
            // 设置连接请求方式
            connection.setRequestMethod(requestType);
            // 设置连接主机服务器超时时间:15000毫秒
            connection.setConnectTimeout(15000);
            // 设置读取主机服务器返回数据超时时间:60000毫秒
            connection.setReadTimeout(60000);

            // 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true
            connection.setDoOutput(true);
            // 默认值为:true,当前向远程服务读取数据时,设置为true,该参数可有可无
            connection.setDoInput(true);

            connection.setRequestProperty("Content-Type", "application/x-www-formurlencoded");
            connection.setRequestProperty("Accept", "application/json;charset=UTF-8");
            // 通过连接对象获取一个输出流
            os = connection.getOutputStream();
            // 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的
            os.write(param.getBytes());
            // 通过连接对象获取一个输入流,向远程读取
            int code = connection.getResponseCode();
            if (connection.getResponseCode() == 200) {

                is = connection.getInputStream();
                // 对输入流对象进行包装:charset根据工作项目组的要求来设置
                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));

                StringBuffer sbf = new StringBuffer();
                String temp = null;
                // 循环遍历一行一行读取数据
                while ((temp = br.readLine()) != null) {
                    sbf.append(temp);
                    sbf.append("\r\n");
                }
                result = sbf.toString();
            }
            isSuccess = true;
        } catch (MalformedURLException e) {
            isSuccess = false;
            e.printStackTrace();
        } catch (IOException e) {
            isSuccess = false;
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (null != br) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != os) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // 断开与远程地址url的连接
            connection.disconnect();
        }
        return isSuccess;
    }


}

3. 定时器线程池配置

package serve.peam.schedule;

import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

/**
 * 开启线程池
 *
 * @author 天真热
 * @create 2022-04-28 10:11
 * @desc
 **/
public class SchedulingThreadPoolConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); // 定时任务执行线程池核心线程数
        taskScheduler.setPoolSize(10);
        taskScheduler.setRemoveOnCancelPolicy(true);
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
        return taskScheduler;
    }
}

4. 定时器实体类

package serve.peam.schedule;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 定时器管理对象 schedule_task
 *
 * @author 天真热
 * @create 2022-04-28 10:11
 * @desc
 */
@Data
public class ScheduleTask implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;

    /**
     * 任务名称
     */
    private String taskName;

    /**
     * 任务状态
     */
    private String taskStatus;

    /**
     * 任务类型(接口调用/类调用)
     */
    private String taskType;

    /**
     * 定时器表达式
     */
    private String cronExpression;

    /**
     * 接口
     */
    private String cronInterface;

    /**
     * 接口类型,GET/POST
     */
    private String cronInterfaceType;

    /**
     * service类
     */
    private String cronServiceName;

    /**
     * service方法
     */
    private String cronServiceMethod;


}

5. 定时实际工作类

package serve.peam.schedule;

import java.util.concurrent.ScheduledFuture;

/**
 * 定时器实际工作类
 *
 * @author 天真热
 * @create 2022-04-28 9:43
 * @desc
 **/
public class ScheduledRealTask {
    volatile ScheduledFuture<?> future;

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

6. 定时器api类

package serve.peam.schedule;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 定时器新增、删除Api
 *
 * @author 天真热
 * @create 2022-04-28 10:11
 * @desc
 */
@Component
public class ScheduleApi implements DisposableBean {
    //线程、定时器任务
    private Map<Long, Map<String, Object>> taskMaps = new ConcurrentHashMap<>(16);

    //定时器实现类
    @Autowired
    private TaskScheduler taskScheduler;

    public TaskScheduler getScheduler() {
        return this.taskScheduler;
    }

    /**
     * 添加并启动定时器
     *
     * @param task
     * @param cronExpression
     */
    public void addCronTask(Long taskId, Runnable task, String cronExpression) {
        //先移除
        if (this.taskMaps.containsKey(taskId)) {
            removeCronTask(taskId);
        }
        //创建定时器
        CronTask cronTask = new CronTask(task, cronExpression);
        //启动定时器
        ScheduledRealTask scheduledRealTask = new ScheduledRealTask();
        scheduledRealTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());

        //构造任务map
        Map<String, Object> map = new HashMap();
        map.put("runnable", task);
        map.put("scheduledRealTask", scheduledRealTask);
        //添加定时器
        this.taskMaps.put(taskId, map);
    }

    /**
     * 移除并关闭定时器
     *
     * @param taskId
     */
    public void removeCronTask(Long taskId) {
        //移除定时器
        Map<String, Object> map = this.taskMaps.remove(taskId);
        if (map != null) {
            ScheduledRealTask scheduledRealTask = (ScheduledRealTask) map.get("scheduledRealTask");

            //关闭定时器
            if (scheduledRealTask != null) {
                scheduledRealTask.cancel();
            }
        }

    }


    /**
     * 销毁并关闭所有定时器
     */
    @Override
    public void destroy() {
        for (Map<String, Object> map : this.taskMaps.values()) {
            ScheduledRealTask scheduledRealTask = (ScheduledRealTask) map.get("scheduledRealTask");
            if (scheduledRealTask != null) {
                scheduledRealTask.cancel();
            }

        }
        this.taskMaps.clear();
    }
}

7. 定时器容器初始化启动类

package serve.peam.schedule;

import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import serve.peam.schedule.reflect.ScheduleClassReflect;
import serve.peam.schedule.request.ScheduleRequest;

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

/**
 * 项目启动加载定时器任务
 *
 * @author 天真热
 * @create 2022-04-28 9:39
 * @desc
 **/
@Component
public class ScheduleInit implements CommandLineRunner {
    private static final Logger logger = LoggerFactory.getLogger(ScheduleInit.class);
    @Autowired
    private ScheduleApi scheduleApi;

    @Override
    public void run(String... args) {
        // 初始加载数据库里状态为正常的定时任务,这里暂时取空,具体根据业务获取
        List<ScheduleTask> ScheduleTaskList = new ArrayList<>();
        //遍历
        for (ScheduleTask scheduleTask : ScheduleTaskList) {
            //启动状态的才注入spring
            if (scheduleTask.getTaskStatus().equals("开启")) {
                if (scheduleTask.getTaskType().equals("类调用")) {
                    //类调用
                    ScheduleClassReflect task = new ScheduleClassReflect(scheduleTask.getCronServiceName(), scheduleTask.getCronServiceMethod(), scheduleTask.getId());
                    scheduleApi.addCronTask(scheduleTask.getId(), task, scheduleTask.getCronExpression());
                } else if (scheduleTask.getTaskType().equals("接口调用")) {
                    //接口调用
                    ScheduleRequest task = new ScheduleRequest(scheduleTask.getCronInterface(), scheduleTask.getCronInterfaceType(), scheduleTask.getId());
                    scheduleApi.addCronTask(scheduleTask.getId(), task, scheduleTask.getCronExpression());
                }

            }
        }
        logger.info("定时任务已加载完毕...");
    }
}

8. 容器demo调用类

package serve.peam.schedule;

import org.springframework.beans.factory.annotation.Autowired;
import serve.peam.schedule.reflect.ScheduleClassReflect;
import serve.peam.schedule.request.ScheduleRequest;

/**
 * @author 天真热
 * @create 2022-04-30 12:43
 * @desc
 **/
public class Demo {
    @Autowired
    private ScheduleApi scheduleApi;

    public void demo() {
        //获取ScheduleTask
        ScheduleTask scheduleTask = new ScheduleTask();

        //新增类定时调用
        ScheduleClassReflect task = new ScheduleClassReflect(scheduleTask.getCronServiceName(), scheduleTask.getCronServiceMethod(), scheduleTask.getId());
        scheduleApi.addCronTask(scheduleTask.getId(), task, scheduleTask.getCronExpression());

        //新增接口定时调用
        ScheduleRequest task1 = new ScheduleRequest(scheduleTask.getCronInterface(), scheduleTask.getCronInterfaceType(), scheduleTask.getId());
        scheduleApi.addCronTask(scheduleTask.getId(), task1, scheduleTask.getCronExpression());

        //移除定时调用
        scheduleApi.removeCronTask(scheduleTask.getId());
    }
}

三、前台cron组件功能代码展示

前端功能基本是表单啥的,这里不做演示。值得一提的是vue有提供cron表达式插件:czr-vue-cron

  1. 下载:npm install czr-vue-cron
  2. vue组件
<template>
  <div id="app">
    <input v-model="cronData"/>
    <CzrVueCron :cron.sync="cronData" :recent="[5, 5]"/>
  </div>
</template>
<script>
import CzrVueCron from 'czr-vue-cron'

export default {
  components: {
    CzrVueCron
  },
  data() {
    return {
      cronData: ''
    }
  }
}
</script>

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值