SpringTask动态配置定时任务的使用
一、需求
- 在项目开发中、根据业务的需求需要要在在一定的时间或每隔多长时间去执行特定的程序,比如:每天凌晨去统计一些报表。
二、技术分类
2.1、java自带的java.util.Timer
类
这个类允许调度一个`java.utils.TimerTask`任务。使用这种方式可以让程序在某一个时间间隔循环执行,但不能在指定时间运行。(一般用的较少)
2.2、使用Quartz
这个是一个功能比较强大的任务调度器,可以让你的程序在每到指定时间执行,也可以按照某一时间间隔循环执行。在功能强大的同时,配置起来稍微的复杂。
2.3、SpringTask
(自Spring3.0
以后自带task功能)
SpringTask可以看成一个轻量级的Quartz,用起来比Quartz简单的多,配置也比较少,和自家的集成也是非常方便。(同样支持每隔指定时间触发执行、每到指定时间触发执行)
- 注:
- 时间间隔为毫秒
- 指定时间为
cron
表达式
三、SpringTask
普通用法说明
3.1、SpringTask简述
本文使用的是Spring3.0以后自主开发的定时任务工具,他可以看做一个轻量级的Quartz,而且使用起来很简单,除spring相关的包外不需要额外的包,而且支持注解和配置文件两种方式。这里两种方式都介绍一下:
3.2、XML
配置文件的使用
第一步:在
spring
的配置文件中引入命名空间1
2
3
4xmlns:task="http://www.springframework.org/schema/task"
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd"第二步:添加定时任务扫描注解
1
2
3
4
5
6
7
8
9
10<context:component-scan base-package="package" />
<!-- 定时任务扫描注解(使用@Scheduled时可不配置)-->
<task:annotation-driven/>
<task:scheduled-tasks scheduler="scheduler">
<task:scheduled ref="ReportCacheTask" method="cacheUpdateSchedule" cron="0 17 16 * * ?" />
</task:scheduled-tasks>
<task:scheduler id="scheduler" pool-size="10" />
<!--注意这边需要配置供扫描的包 (如果类上加@Service可不配置)-->
<bean id="ReportCacheTask" class="com.cym.bip.report.data.cache.ReportCacheTask" />第三步:创建一个
com.cym.bip.report.data.cache.ReportCacheTask
类,里面
有个cacheUpdateSchedule
方法。(代码省略)
3.3、注解方式(重点)
第一步:在spring的配置文件中引入task的命名空间
1
2
3
4xmlns:task="http://www.springframework.org/schema/task"
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd"第二步:添加定时任务扫描注解
1
2
3
4
5<context:component-scan base-package="package" />
<!-- 定时任务扫描注解(使用@Scheduled时可不配置)-->
<task:annotation-driven scheduler="scheduler"/>
<!--配置定时任务的线程池(推荐配,若不配置多任务下可能有问题)-->
<task:scheduler id="scheduler" pool-size="10" />第三步:创建定时任务类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.leaseBack.apollo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 定时获得定时任务列表
*/
//可不配置
public class SpringTimeTask {
private static Logger logger = LoggerFactory.getLogger(SpringTimeTask.class);
// @Scheduled(cron = "0 0/6 * * * ?") //根据cron表达式执行
10 * 1000) //每隔10秒执行一次 (fixedDelay =
public void timeTaskRun() {
logger.info("Refresh TimeTask......");
}
}注:
@EnableScheduling
:此注解添加在类上表示开启对定时任务的支持(添加此注解,可不在xml中配置注解驱动)@Scheduled
:此注解表示在方法上表示此方法按一定时间规则执行
四、SpringTask高级应用
(实现动态配置定时任务)
4.1、简述
业务需求
根据项目需要定时任务方法的逐渐增多和时间规则有可能发变化,以上普通做法在变动时改动比较大,并且定时任务运行起来无法手动操作,不易编码及操作。所以就有了实现动态配置定时任务的想法。
动态定时任务设想
当我们想定时执行一个任务时,只需要告诉程序要执行的类名(类的全路径)、方法名及cron表达式即可,并且定时任务有开启和关闭功能,根据需求来配置就行。这样做优点:增加了程序的灵活性,减少了硬编码,易于维护。
实现方案
当我们开启定时任务时,从数据库中获得类的全路径、方法名、cron表达式,根据全路径方法名利用反射机制来新建一个线程去执行方法,并且指定一个cron时间规则
4.2、动态定时任务的实(注解方式)
4.2.1、配置参考普通应用配置(在此省略)
4.2.2、创建一个反射类实现Runnable接口
- 根据类名,方法名利用反射机制创建一个线程去执行
1 | package com.leaseBack.apollo; |
4.2.3、定时任务开启关闭的Service、
- 用
ThreadPoolTaskScheduler
(定时任务线程池)去执行一个线程并且按cron
时间表达式去执行任务,将返回的ScheduledFuture
(线程信息)保存在private static Map<String, ScheduledFuture> map = new HashMap<>();
,后面根据ScheduledFuture
信息来进行关闭。
1 | package com.leaseBack.apollo; |
4.2.4、编写定时任务的方法
- 创建一个普通类
1 | package com.timetask.apollo; |
五、总结
传输定时任务详细信息时:
问题:因为
p2p-schedule
、P2PManager
是都web
项目service
不能相互调用,导致定时任务详细信息不能传递。解决方案:一、模仿
HTTP
请求 二、借助于Redis
来储存信息,并且写个定时任务每十秒获得一次Redis
中的信息,根据信息去执行(本项目中使用的)利用反射执行方法时,嵌套了其他service
问题:利用反射执行方法时,嵌套了其他service(用
@Autowired
注入),在执行时service
报空指针异常,不能正常注入。解决方案:一、编写一个工具类 二、实现
ApplicationContextAware
接口本文章中定时任务的service
方法)p2p-schedule
项目启动就要执行开启的定时任务问题:
p2p-schedule
项目启动就要执行开启的定时任务,并且还要把所有service
都注入之后执行。一开始试了好几种方式,都是加载
service
报错(貌似是null指针异常,具体忘啦),经过一番查资料找到了一些解决办法。解决方案:编写一个类实现了
ApplicationListener<ContextRefreshedEvent>
接口,重写onApplicationEvent
方法。业务编码逻辑:当加载完所有Bean
时去掉用service
服务方法。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29> package com.leaseBack.apollo;
>
> import org.slf4j.Logger;
> import org.slf4j.LoggerFactory;
> import org.springframework.beans.factory.annotation.Autowired;
> import org.springframework.context.ApplicationListener;
> import org.springframework.context.event.ContextRefreshedEvent;
> import org.springframework.stereotype.Component;
> import java.util.Date;
>
> /**
> * 项目启动时执行定时任务
> */
>
> public class StartConstruct implements ApplicationListener<ContextRefreshedEvent> {
> private static Logger logger = LoggerFactory.getLogger(StartConstruct.class);
>
> private TaskService taskService;
>
>
> public void onApplicationEvent(ContextRefreshedEvent event) {
> if (event.getApplicationContext().getParent() == null) {
> logger.info("Start all TimeTask--"+new Date().toLocaleString());
> taskService.timeTaskRun();
> logger.info("Start all TimeTask Success--" + new Date().toLocaleString());
> }
> }
> }
>