一、背景:
由于前段时间(一直在忙项目,现在才有空总结一下。。。。)搞微信开发,大家都知道,微信access_token是会过期的(7200s),所以要定时刷新,基于此需求,所以把定时任务调度整合进了项目。其实实现定时任务有几种方法,有spring原生定时器功能、quartz也可以实现,但quartz配置起来麻烦(要配置JobDetail,Trigger,Scheduler等等),本文主要采用ThreadPoolTaskScheduler 任务调度器实现,下面简要的说明步骤:
二、定义任务调度抽象基类BaseTask.java
/**
* @Description: 定时任务基类
* @ClassName: BaseTask
* @author weishihuai
* @date 2018年8月28日 下午1:51:10
*
*/
public abstract class BaseTask {
private static Logger logger = LoggerFactory.getLogger(BaseTask.class);
/**
* 多线程定时任务执行. 可以设置执行线程池数(默认一个线程)
* 1. 使用前必须得先调用initialize()进行初始化
* 2. schedule(Runnable task, Trigger trigger) 指定一个触发器执行定时任务。可以使用CronTrigger来指定Cron表达式,执行定时任务
* 3. shutDown()方法,执行完后可以关闭线程
*/
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
/**
* @Description: 获取定时任务执行时间规则表达式
* @date 2018年8月28日下午1:57:34
* @Title: getCron
* @author weishihuai
* @param @return 参数
* @return String 返回类型
* @throws
*/
protected abstract String getCron();
/**
* @Description: 控制定时器的开关
* @date 2018年8月28日下午1:58:34
* @Title: isOpen
* @author weishihuai
* @param @return 参数
* @return boolean 返回类型
* @throws
*/
protected abstract boolean isOpen();
/**
* @Description: 定时任务执行的方法
* @date 2018年8月28日下午2:00:04
* @Title: executeTask
* @author weishihuai
* @param 参数
* @return void 返回类型
* @throws
*/
protected abstract void executeTask();
/**
* @Description: 定时任务开始执行的方法
* @date 2018年8月28日下午2:02:55
* @Title: startTask
* @author weishihuai
* @param 参数
* @return void 返回类型
* @throws
*/
public void startTask() {
// 1. 判断定时器开关
if (!isOpen()) {
return ;
}
// 2. 实例化一个线程池任务调度器
if (null == threadPoolTaskScheduler) {
threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
}
// 3. 初始化threadPoolTaskScheduler
threadPoolTaskScheduler.initialize();
// 4. 创建一个任务执行线程
Runnable runnable = new Runnable() {
public void run() {
try {
if (!isOpen()) {
stopTask();
return;
}
//执行任务
executeTask();
} catch (Exception e) {
logger.error("定时任务执行失败,{}", e.getMessage());
e.printStackTrace();
}
}
};
// 5. 创建一个触发器
Trigger trigger = new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
String cron = getCron();
if (StringUtils.isBlank(cron)) {
return null;
}
CronTrigger cronTrigger = new CronTrigger(cron);
return cronTrigger.nextExecutionTime(triggerContext);
}
};
// 6. 执行schedule(Runnable task, Trigger trigger)任务调度方法
threadPoolTaskScheduler.schedule(runnable, trigger);
}
/**
* @Description: 定时任务终止执行的方法
* @date 2018年8月28日下午2:03:08
* @Title: stopTask
* @author weishihuai
* @param 参数
* @return void 返回类型
* @throws
*/
public void stopTask() {
if (null != threadPoolTaskScheduler) {
//关闭线程
threadPoolTaskScheduler.shutdown();
}
}
}
二、定义定时任务统一启动类:TaskRunner.java,这里可以注入一些具体的任务,调用start()方法即可。当spingboot项目启动之后会自动根据配置cron表达式规则执行相应的任务。
/**
* @Description: 定时任务统一启动类
* @ClassName: TaskRunner
* @author weishihuai
* @date 2018年8月28日 下午3:42:18
* 实现CommandLineRunner接口,我们可以在项目启动的时候进行一些数据的加载、执行某个特定的动作等,如果存在多个加载动作,可以使用@Order注解来排序。 (数字越小,优先级越高)
* 执行时机 : 容器启动完成时
*/
@Component
public class TaskRunner implements CommandLineRunner {
@Autowired
private WeiXinRefreshAccessTokenTask weiXinRefreshAccessTokenTask;
@Override
public void run(String... arg0) throws Exception {
// 启动刷新AccessToken任务
weiXinRefreshAccessTokenTask.startTask();
}
}
三、定义一个配置文件taskConfig.properties放于resources目录下,主要是配置定时器的开关、以及执行的cron表达式(其实可以放在application.properties或者application.yml中,这里只是不想污染那些配置,所以自定义了一个配置文件统一维护任务调度相关相关的)
########################################################################################################
#################################spring boot 定时任务 (控制开关/cron表达式) ##################################
########################################################################################################
# 刷新微信第三方AccessToken的定时器开关
com.ly.cloud.task.weixin.access.token.switch = true
# 刷新微信第三方AccessToken的定时器CRON表达式
com.ly.cloud.task.weixin.access.token.cron = 0 0/5 * * * ?
四、定义读取配置文件的工具类:ProUtil.java
/**
* @Description: 读取自定义配置文件中的内容
* @ClassName: ProUtil
* @author weishihuai
* @date 2018年8月28日 下午4:39:25
*
*/
public class ProUtil {
private static Logger logger = LoggerFactory.getLogger(ProUtil.class);
private ProUtil() {
};
public static Properties propertie = null;
static {
propertie = new Properties();
InputStream inputStream = ProUtil.class.getResourceAsStream("/taskConfig.properties");
// 解决中文乱码
BufferedReader buff = new BufferedReader(new InputStreamReader(inputStream));
try {
propertie.load(buff);
inputStream.close();
} catch (IOException e) {
logger.info("读取配置文件失败!");
e.printStackTrace();
}
}
}
五、定义具体的任务逻辑实现类:如WeiXinRefreshAccessTokenTask.java:
/**
* @Description: 刷新微信第三方平台access_token
* @ClassName: WeiXinRefreshAccessTokenTask
* @author weishihuai
* @date 2018年8月28日 下午2:34:37
*
*/
@Component
public class WeiXinRefreshAccessTokenTask extends BaseTask {
private static Logger logger = LoggerFactory.getLogger(WeiXinRefreshAccessTokenTask.class);
@Autowired
private WeiXinApiService weiXinApiService;
@Override
protected String getCron() {
String cron = (String) ProUtil.propertie.get("com.ly.cloud.task.weixin.access.token.cron");
if (StringUtils.isBlank(cron)) {
logger.info("获取微信初始化信息-刷新第三方access_token的cron为空,请先配置!");
}
return cron;
}
@Override
protected boolean isOpen() {
String isOpenStr = (String) ProUtil.propertie.get("com.ly.cloud.task.weixin.access.token.switch");
if (StringUtils.isBlank(isOpenStr)) {
logger.info("获取微信初始化信息-刷新第三方access_token的定时器开关为空,请先配置!");
}
return Boolean.parseBoolean(isOpenStr);
}
@Override
protected void executeTask() {
// 获取AccessToken请求结果对象
WeiXinAccessTokenResult weiXinAccessTokenResult = weiXinApiService.getWeiXinAccessTokenResult();
// 获取AccessToken最后一次刷新的时间
Date accessTokenLastRefreshTime = weiXinApiService.getAccessTokenLastRefreshTime();
if (null == weiXinAccessTokenResult || StringUtils.isBlank(weiXinAccessTokenResult.getAccess_token())
|| null == accessTokenLastRefreshTime) {
logger.info("WeiXinRefreshAccessTokenTask >> executeTask() >> 执行token超时刷新方法");
weiXinApiService.refreshAccessToken();
} else {
Calendar calendar = Calendar.getInstance();
calendar.setTime(accessTokenLastRefreshTime);
// 设置时间为access_token过期前五分钟
calendar.add(Calendar.SECOND, weiXinAccessTokenResult.getExpires_in() - (5 * 60));
// access_token需要刷新的时间,距离过期还有五分钟
Date expiresTime = calendar.getTime();
if (new Date().after(expiresTime)) {
logger.info("WeiXinRefreshAccessTokenTask >> executeTask() >> 执行token超时刷新方法");
weiXinApiService.refreshAccessToken();
}
}
}
}
六、在项目启动类加上@EnableScheduling注解开启定时器功能:
@SpringBootApplication(scanBasePackages={"com.ly.cloud"})
@EnableTransactionManagement
@EnableEurekaClient
//@EnableScheduling注解用来开启定时器功能,相当于定时器的总开关
@EnableScheduling
public class YxxtServiceApplication{
public static void main(String[] args) {
SpringApplication.run(YxxtServiceApplication.class, args);
}
}
注:本文章是笔者在做项目后的一些总结,仅供参考,大家一起学习,共同进步!