# spring 定时任务(注解方式)
## 一、说明
### 1.1 项目结构说明
关于任务的调度配置定义在ServletConfig.java中,为方便观察项目定时执行的情况,项目以web的方式构建。
### 1.2 依赖说明
导入基本依赖
```xml
4.0.0
com.heibaiying
spring-scheduling
1.0-SNAPSHOT
org.apache.maven.plugins
maven-compiler-plugin
8
8
5.1.3.RELEASE
org.springframework
spring-context
${spring-base-version}
org.springframework
spring-beans
${spring-base-version}
org.springframework
spring-core
${spring-base-version}
org.springframework
spring-web
${spring-base-version}
org.springframework
spring-webmvc
${spring-base-version}
javax.servlet
javax.servlet-api
4.0.1
provided
```
## 二、spring scheduling
#### 2.1 创建定时任务
```java
/**
* @author : heibaiying
*/
@Component
public class Task {
/**
* 基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。
*/
@Scheduled(fixedDelay = 5000, initialDelay = 1000)
public void methodA() {
Thread thread = Thread.currentThread();
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
}
/**
* 基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。
*/
@Scheduled(fixedRate = 5000)
@Async
public void methodB() throws InterruptedException {
Thread thread = Thread.currentThread();
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
Thread.sleep(10 * 1000);
}
@Scheduled(cron = "0/10 * * * * ?")
public void methodC() {
Thread thread = Thread.currentThread();
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
}
}
```
#### 2.2 配置定时任务
```java
/**
* @author : heibaiying
* spring 主配置类
*/
@Configuration
@EnableWebMvc
@EnableScheduling //启用Spring的计划任务执行功能
@EnableAsync //启用Spring的异步方法执行功能
@ComponentScan(basePackages = {"com.heibaiying.task"})
public class ServletConfig implements WebMvcConfigurer, AsyncConfigurer, SchedulingConfigurer {
private ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 任务执行器线程池配置
@Override
public Executor getAsyncExecutor() {
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("MyExecutor-");
executor.initialize();
return executor;
}
// 这个方法可以监听到异步程序发生的错误
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
System.out.println(method.getName() + "发生错误:" + ex.getMessage());
}
};
}
// 如果程序结束,需要关闭线程池 不然程序无法完全退出 只能kill才能完全退出
@PreDestroy
public void destroy() {
if (executor != null) {
executor.shutdown();
}
}
// 调度程序线程池配置
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
// 如果程序结束,需要关闭线程池
@Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(50);
}
}
```
**关于调度程序线程池作用说明**:
按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
**关于任务执行线程池作用说明**:
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)