SpringBoot 任务调度(开发总结)

一、背景:

由于前段时间(一直在忙项目,现在才有空总结一下。。。。)搞微信开发,大家都知道,微信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);
	}
	
}

注:本文章是笔者在做项目后的一些总结,仅供参考,大家一起学习,共同进步!

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/82465229