spring整合定时任务功能
一、准备工作
1、建立maven子模块:webapp2_schedule,过程省略
2、在webapp2_schedule模块的src/main/java下,建立com.study.schedule包
二、spring自带的task功能
1、涉及的包
org.springframework.core.task
org.springframework.core.task.support
2、TaskExecutor接口
继承自jdk的Executor(spring自己封装了一层),定义了一个接收Runnable接口对象的方法execute
1)增加MyTask1.java
package com.study.schedule;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
@Component
public class MyTask1 implements TaskExecutor{
@Override
public void execute(Runnable task) {
// TODO Auto-generated method stub
System.out.println("hello task.");
task.run();
}
}
2)增加MessagePrinterTask.java
package com.study.schedule;
public class MessagePrinterTask implements Runnable {
private String message;
public MessagePrinterTask(String message) {
this.message = message;
}
public void run() {
for (int i = 0; i < 25; i++) {
System.out.println(message + " num: " +i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3)增加测试类TaskTest.java
增加测试方法testTaskExecutor
@Test
public void testTaskExecutor() {
//执行实现了Runnable接口的类
MyTask1 myTask1 = new MyTask1();
myTask1.execute(new MessagePrinterTask("apple"));
myTask1.execute(new MessagePrinterTask("banana"));
}
4)测试方法执行结果
测试开始----------
hello task.
apple num: 0
apple num: 1
apple num: 2
apple num: 3
apple num: 4
apple num: 5
apple num: 6
apple num: 7
apple num: 8
apple num: 9
apple num: 10
apple num: 11
apple num: 12
apple num: 13
apple num: 14
apple num: 15
apple num: 16
apple num: 17
apple num: 18
apple num: 19
apple num: 20
apple num: 21
apple num: 22
apple num: 23
apple num: 24
hello task.
banana num: 0
banana num: 1
banana num: 2
banana num: 3
banana num: 4
banana num: 5
banana num: 6
banana num: 7
banana num: 8
banana num: 9
banana num: 10
banana num: 11
banana num: 12
banana num: 13
banana num: 14
banana num: 15
banana num: 16
banana num: 17
banana num: 18
banana num: 19
banana num: 20
banana num: 21
banana num: 22
banana num: 23
banana num: 24
测试结束----------
可以看到线程是同步执行的
3、TaskExecutor接口实现类
1)SimpleAsyncTaskExecutor
SimpleAsyncTaskExecutor是TaskExecutor的一个实现类,为每个任务触发一个新线程,异步执行它
线程不会重用,每次调用时都会重新启动一个新的线程。但它有一个最大同时执行的线程数的限制
1-1)增加MyTask2.java
package com.study.schedule;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.stereotype.Component;
@Component
public class MyTask2 extends SimpleAsyncTaskExecutor {
private static final long serialVersionUID = -8958545057173751951L;
@Override
public void execute(Runnable task) {
// TODO Auto-generated method stub
System.out.println("hello task.");
task.run();
}
}
1-2)增加测试方法testSimpleAsyncTaskExecutor
@Test
public void testSimpleAsyncTaskExecutor() {
//异步线程执行
SimpleAsyncTaskExecutor myTask2 = new SimpleAsyncTaskExecutor();
myTask2.execute(new MessagePrinterTask("apple"));
myTask2.execute(new MessagePrinterTask("banana"));
}
1-3)测试方法执行结果
测试开始----------
测试结束----------
apple num: 0
banana num: 0
apple num: 1
banana num: 1
banana num: 2
apple num: 2
apple num: 3
banana num: 3
apple num: 4
banana num: 4
apple num: 5
banana num: 5
apple num: 6
banana num: 6
apple num: 7
banana num: 7
apple num: 8
banana num: 8
apple num: 9
banana num: 9
apple num: 10
banana num: 10
banana num: 11
apple num: 11
apple num: 12
banana num: 12
apple num: 13
banana num: 13
banana num: 14
apple num: 14
apple num: 15
banana num: 15
apple num: 16
banana num: 16
apple num: 17
banana num: 17
apple num: 18
banana num: 18
banana num: 19
apple num: 19
apple num: 20
banana num: 20
apple num: 21
banana num: 21
apple num: 22
banana num: 22
apple num: 23
banana num: 23
banana num: 24
apple num: 24
可以看到主线程执行完后,任务线程是异步执行的
2)SyncTaskExecutor
和自己实现TaskExecutor接口一样,同步执行
2-1)增加测试方法testSyncTaskExecutor
@Test
public void testSyncTaskExecutor() {
//同步线程执行
//和自己实现TaskExecutor接口一样
SyncTaskExecutor myTask3 = new SyncTaskExecutor();
myTask3.execute(new MessagePrinterTask("apple"));
myTask3.execute(new MessagePrinterTask("banana"));
}
2-2)测试方法执行结果
测试开始----------
apple num: 0
apple num: 1
apple num: 2
apple num: 3
apple num: 4
apple num: 5
apple num: 6
apple num: 7
apple num: 8
apple num: 9
apple num: 10
apple num: 11
apple num: 12
apple num: 13
apple num: 14
apple num: 15
apple num: 16
apple num: 17
apple num: 18
apple num: 19
apple num: 20
apple num: 21
apple num: 22
apple num: 23
apple num: 24
banana num: 0
banana num: 1
banana num: 2
banana num: 3
banana num: 4
banana num: 5
banana num: 6
banana num: 7
banana num: 8
banana num: 9
banana num: 10
banana num: 11
banana num: 12
banana num: 13
banana num: 14
banana num: 15
banana num: 16
banana num: 17
banana num: 18
banana num: 19
banana num: 20
banana num: 21
banana num: 22
banana num: 23
banana num: 24
测试结束----------
可以看到线程是同步执行的
4、ThreadPoolTaskExecutor
有线程池的TaskExecutor
1)在webapp2_web模块的src/main/resources下添加spring-job.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd
">
<bean id="threadPoolTaskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 核心线程数 -->
<property name="corePoolSize" value="5" />
<!-- 最大线程数 -->
<property name="maxPoolSize" value="10" />
<!-- 队列最大长度 -->
<property name="queueCapacity" value="25" />
</bean>
</beans>
applicationContext.xml添加引用
<import resource="classpath:/spring-job.xml"/>
2)添加测试方法testThreadPoolTaskExecutor并注入对象
@Autowired
ThreadPoolTaskExecutor threadPoolTaskExecutor;
...
@Test
public void testThreadPoolTaskExecutor() {
//带线程池的TaskExecutor
//异步线程执行
//可自己配置线程池大小
threadPoolTaskExecutor.execute(new MessagePrinterTask("apple"));
threadPoolTaskExecutor.execute(new MessagePrinterTask("banana"));
}
3)测试方法执行结果
测试开始----------
测试结束----------
apple num: 0
banana num: 0
banana num: 1
apple num: 1
apple num: 2
banana num: 2
banana num: 3
apple num: 3
banana num: 4
apple num: 4
banana num: 5
apple num: 5
apple num: 6
banana num: 6
banana num: 7
apple num: 7
banana num: 8
apple num: 8
banana num: 9
apple num: 9
apple num: 10
banana num: 10
apple num: 11
banana num: 11
banana num: 12
apple num: 12
banana num: 13
apple num: 13
banana num: 14
apple num: 14
banana num: 15
apple num: 15
apple num: 16
banana num: 16
banana num: 17
apple num: 17
apple num: 18
banana num: 18
apple num: 19
banana num: 19
apple num: 20
banana num: 20
apple num: 21
banana num: 21
apple num: 22
banana num: 22
apple num: 23
banana num: 23
banana num: 24
apple num: 24
可以看到任务线程是异步执行的
二、spring自带scheduler功能
1、涉及的包
org.springframework.scheduling
org.springframework.scheduling.concurrent
2、TaskScheduler接口
根据不同的triggers执行任务
3、TaskScheduler接口实现类
1)ThreadPoolTaskScheduler
1-1)在spring-job.xml添加bean
<bean id="threadPoolTaskScheduler"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value="5" />
</bean>
1-2)添加测试方法testThreadPoolTaskScheduler并注入对象
@Autowired
ThreadPoolTaskScheduler threadPoolTaskScheduler;
...
@Test
public void testThreadPoolTaskScheduler() {
threadPoolTaskScheduler.schedule(new MessagePrinterTask("apple"), new CronTrigger("0/30 * * * * ?"));
}
1-3)执行测试方法,发生报错
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'java.util.concurrent.ThreadFactory' available: expected single matching bean but found 2: threadPoolTaskExecutor,threadPoolTaskScheduler
4、分析问题,为什么ThreadPoolTaskExecutor和ThreadPoolTaskScheduler不能共存
1)区别
ThreadPoolTaskExecutor是一个专门用于异步执行任务的类(执行线程)
ThreadPoolTaskScheduler是一个专门用于调度执行任务的类(调度+执行线程)
2)从报错信息看
ThreadPoolTaskExecutor提供默认ThreadFactory实现
ThreadPoolTaskScheduler也提供了默认ThreadFactory实现
3)ThreadPoolTaskExecutor在哪里定义ThreadFactory?
在ThreadPoolTaskExecutor类里的initializeExecutor()方法,会初始化ThreadPoolExecutor对象,ThreadFactory由初始化的调用方ExecutorConfigurationSupport提供
4)ThreadPoolTaskScheduler在哪里定义ThreadFactory?ThreadPoolTaskScheduler的执行线程在哪里定义?
和ThreadPoolTaskExecutor初始化过程相同,初始化ScheduledThreadPoolExecutor --> (继承)ThreadPoolExecutor
ThreadPoolTaskExecutor和ThreadPoolTaskScheduler --> (继承)ExecutorConfigurationSupport --> (继承)CustomizableThreadFactory --> 实现了ThreadFactory接口
执行线程就是初始化返回的ScheduledThreadPoolExecutor线程池对象,实现了ScheduledExecutorService接口
5、重新测试ThreadPoolTaskScheduler类
1)注释掉threadPoolTaskExecutor相关的代码
2)修改testThreadPoolTaskScheduler测试方法
@Test
public void testThreadPoolTaskScheduler() {
threadPoolTaskScheduler.schedule(new MessagePrinterTask("apple"), new CronTrigger("0/10 * * * * ?"));
threadPoolTaskScheduler.execute(new MessagePrinterTask("banana"));
try {
System.out.println("main thread sleep start.");
Thread.sleep(36000); //睡眠36秒
System.out.println("main thread sleep stop.");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
3)测试方法执行结果
测试开始----------
main thread sleep start.
banana num: 0
banana num: 1
banana num: 2
banana num: 3
banana num: 4
banana num: 5
banana num: 6
banana num: 7
banana num: 8
banana num: 9
banana num: 10
banana num: 11
banana num: 12
banana num: 13
banana num: 14
banana num: 15
banana num: 16
banana num: 17
banana num: 18
banana num: 19
banana num: 20
banana num: 21
banana num: 22
banana num: 23
banana num: 24
apple num: 0
apple num: 1
apple num: 2
apple num: 3
apple num: 4
apple num: 5
apple num: 6
apple num: 7
apple num: 8
apple num: 9
apple num: 10
apple num: 11
apple num: 12
apple num: 13
apple num: 14
apple num: 15
apple num: 16
apple num: 17
apple num: 18
apple num: 19
apple num: 20
apple num: 21
apple num: 22
apple num: 23
apple num: 24
apple num: 0
apple num: 1
apple num: 2
apple num: 3
apple num: 4
apple num: 5
apple num: 6
apple num: 7
apple num: 8
apple num: 9
apple num: 10
apple num: 11
apple num: 12
apple num: 13
apple num: 14
apple num: 15
apple num: 16
apple num: 17
apple num: 18
apple num: 19
apple num: 20
apple num: 21
apple num: 22
apple num: 23
apple num: 24
apple num: 0
apple num: 1
apple num: 2
apple num: 3
apple num: 4
apple num: 5
apple num: 6
apple num: 7
apple num: 8
apple num: 9
apple num: 10
apple num: 11
apple num: 12
apple num: 13
apple num: 14
apple num: 15
apple num: 16
apple num: 17
apple num: 18
apple num: 19
apple num: 20
apple num: 21
apple num: 22
apple num: 23
apple num: 24
main thread sleep stop.
测试结束----------
apple num: 0
apple num: 1
apple num: 2
apple num: 3
apple num: 4
apple num: 5
apple num: 6
apple num: 7
apple num: 8
apple num: 9
apple num: 10
apple num: 11
apple num: 12
apple num: 13
apple num: 14
apple num: 15
apple num: 16
apple num: 17
apple num: 18
apple num: 19
apple num: 20
apple num: 21
apple num: 22
apple num: 23
apple num: 24
可以看到任务执行都是异步的,主线程启动 --> 主线程睡眠开始 --> 打印banana任务 --> 定时打印apple任务(共触发了4次) --> 主线程睡眠结束 --> 主线程运行结束
6、增加定时任务启动类ScheduleStart.java
package com.study.schedule;
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.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
/**
* 初始化定时任务
* @author User
*
*/
@Component
public class ScheduleStart implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger logger = LoggerFactory.getLogger(ScheduleStart.class);
@Autowired
ThreadPoolTaskScheduler threadPoolTaskScheduler;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext().getParent() == null){
logger.info("applicationContext初始化完成...");
this.start();
}
}
public void start() {
//在这里启动threadPoolTaskScheduler相关的定时任务
}
}
三、小结
用到的方式有
(TaskExecutor接口是spring提供的,它继承jdk中的java.util.concurrent.Executor接口)
1、自己实现TaskExecutor接口,同步执行线程
2、spring的SimpleAsyncTaskExecutor,异步执行线程
3、spring的SyncTaskExecutor,同步执行线程
4、spring的ThreadPoolTaskExecutor,带线程池,异步执行线程
5、spring的ThreadPoolTaskScheduler,带线程池,带任务调度,异步执行线程
注:最新代码上传至https://github.com/csj50/webapp2.git