背景
最近帮人做点东西,主要完成功能如下:
两张表,一张信息明细表,一张发送任务表。需要做两个定时任务,一个循环去读取信息明细表,将没有读取过的信息读取到任务表中;一个循环去读取任务表,将没有发送过的消息发送出去。
功能简单来说,就是这样子。一开始用的是让任务类继承TimerTask,实现run方法,然后再去调用任务类。
如下:
public class MyTask extends TimerTask {
@Override
public void run() {
// todo...
System.out.println("执行任务");
}
public static void main(String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new MyTask(), 0, 1000); // 每1秒执行一次,也可以在xml中配置
}
}
这时候又遇到一个问题:调用service时,没法注入。
原因:继承TimerTask后,会直接运行run()方法,还没来得及执行注解进行注入。
解决:引入了SpringContextUtil的工具类。(具体不讨论,可以百度一下,啊哈哈~~)
最终还是能够实现功能,但是代码既不美观,又很复杂。哈哈
后面再搜了一下,发现spring中的@Scheduled注解实在是太方便了,所以记录下来,加深印象,以后还能回顾一下。
实现
1、在spring配置文件中添加相应配置,以支持定时任务的注解实现
- 在xml里加入task的命名空间
<!-- beans里添加:-->
xmlns:task="http://www.springframework.org/schema/task"
<!-- xsi:schemaLocation里添加:-->
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd
- 配置注解驱动的定时任务
<!-- 启用定时任务 -->
<task:annotation-driven scheduler="scheduler"/>
<!-- 配置定时任务的线程池,spring定时任务默认单线程,推荐配置线程池,若不配置多任务下会有问题。 -->
<task:scheduler id="scheduler" pool-size="5" />
3. 配置注解扫描路径
这个需要指定,当我把任务放在controller层里面时,明明springmvc.xml中已经配置了路径,可是还是不行,需要在spring配置文件中配置,定时任务才能正常启动起来。
<context:component-scan base-package="com.xxx.xxx"
2、在代码中加上注解
需要有两个注解 @Component、@Scheduled
@Component
@Lazy(false)
public class Test {
@Scheduled(cron = "0/5 * * * * ?")
public void showTime() {
System.out.println(new Date());
}
}
此时,将项目跑起来就能成功调起定时任务了。
注意点:
- 类中如果引用其他service层,用属性注入@Autowired即可(与TimerTask不同)。但是如果通过main来调用,则不能用@Autowired注解(未启用项目),此时可用getBean来获取实例对象。
- 定时器的任务方法不能有返回值,如果有返回值,spring初始化的时候报错误:Only void-returning methods may be annotated with @Scheduled。
- 定时器的任务方法不能有参数,如果有参数,启动时会报错:Only no-arg methods may be annotated with @Scheduled
CRON表达式。
@Scheduled参数部分:
- cron:一个类似cron的表达式,扩展了通常的UN * X定义,包括秒、分、时、星期、月、年的触发器。(等待上一次任务完成,在开始时间之后周期倍数时间后才开始下一次任务)
@Scheduled(cron = “0/5 * * * * ?”),开始时间00:00:00,结束时间00:00:06,那么下一次开始时间为00:00:10 - fixedRate:在调用之间以固定的周期(以毫秒为单位)执行带注释的方法。(等待上一次任务完成,如果已超过周期时间,就马上执行,如果还没到周期时间,则等待周期时间结束执行)
@Scheduled(fixedRate = 5 * 1000),开始时间00:00:00,结束时间00:00:06,那么下一次开始时间为00:00:06 - fixedDelay:在最后一次调用结束和下一次调用开始之间以固定周期(以毫秒为单位)执行带注释的方法。(等待上一次任务完成,完成时间再过周期时间后,开始下一次任务)
@Scheduled(fixedDelay = 5 * 1000),开始时间00:00:00,结束时间00:00:06,那么下一次开始时间为00:00:11
如果是强调任务间隔的定时任务,建议使用fixedRate和fixedDelay,如果是强调任务在某时某分某刻执行的定时任务,建议使用cron表达式
详细解释可以看一下这篇文章学习一下:如果我的任务在5秒内没有执行完呢?spring会怎么处理呢?
CRON表达式实例:
"0 0 12 * * ?" 每天中午十二点触发
"0 15 10 ? * *" 每天早上10:15触发
"0 15 10 * * ?" 每天早上10:15触发
"0 15 10 * * ? *" 每天早上10:15触发
"0 15 10 * * ? 2005" 2005年的每天早上10:15触发
"0 * 14 * * ?" 每天从下午2点开始到2点59分每分钟一次触发
"0 0/5 14 * * ?" 每天从下午2点开始到2:55分结束每5分钟一次触发
"0 0/5 14,18 * * ?" 每天的下午2点至2:55和6点至6点55分两个时间段内每5分钟一次触发
"0 0-5 14 * * ?" 每天14:00至14:05每分钟一次触发
"0 10,44 14 ? 3 WED" 三月的每周三的14:10和14:44触发
"0 15 10 ? * MON-FRI" 每个周一、周二、周三、周四、周五的10:15触发
另附(在线cron表示式生成器:http://cron.qqe2.com/)
更复杂的表达式可以参考这篇文章,spring如何设置定时任务详解(@Scheduled)。这篇文章还从源码分析了执行过程,很值得学习!
3. 问题
后面又遇到一些问题:
-
启动定时时,出现bean循环依赖的情况,当前任务类有两个service注入,其中一个service还依赖于另一个service
解决:加Lazy延迟加载注解@Autowired
@Lazy
private ServiceA serviceA; -
针对我的项目背景,扫描表的时候,可能遇到多个任务堆积,但是其实扫描表的动作,其实我只需要他再执行一次就可以了。现在还没遇到问题,所以还没有想方法去解决。留待后面回头再看,嘻嘻~