Maven依賴配置
<!-- 引入 spring-boot-starter-web 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 spring-boot-starter-data-jpa 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 引入 mysql-connector-java 依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 引入 spring-boot-starter-quartz 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- 引入 spring-boot-starter-test 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
application.properties配置
# DATASOURCE CONFIGURATION
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc\:mysql\://localhost\:3306/spring_boot_demo_db_2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=tb890705
# Tomcat will use the above plus the following to setup connection pooling
spring.datasource.tomcat.max-active=100
spring.datasource.tomcat.max-idle=8
spring.datasource.tomcat.min-idle=8
spring.datasource.tomcat.initial-size=10
spring.datasource.tomcat.init-s-q-l=SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci
# spring.datasource.tomcat.validation-query=
# spring.datasource.tomcat.test-on-borrow=false
# spring.datasource.tomcat.test-on-return=false
# spring.datasource.tomcat.test-while-idle=
# spring.datasource.tomcat.time-between-eviction-runs-millis=
# spring.datasource.tomcat.min-evictable-idle-time-millis=
# spring.datasource.tomcat.max-wait=
# JPA (JpaBaseConfiguration, HibernateJpaAutoConfiguration)
# spring.jpa.properties.*= # properties to set on the JPA connection
spring.jpa.open-in-view=true
spring.jpa.show-sql=false
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.database=MYSQL
spring.jpa.generate-ddl=false
# spring.jpa.hibernate.naming-strategy= # naming classname
# spring.jpa.hibernate.ddl-auto= # defaults to create-drop for embedded dbs
spring.data.jpa.repositories.enabled=true
SpringBoot啓動類
package com.yuanx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
*
* @ClassName: DemoApplication
* @Description: Demo啓動類
* @author YuanXu
* @date 2019年7月5日 下午4:09:33
*
*/
@SpringBootApplication
@EnableScheduling
public class DemoApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(DemoApplication.class);
}
/**
* @Title main
* @Description Demo啓動入口
* @author YuanXu
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
創建Entity、Repository、Service類
基於註解的靜態單線程定時任務實現
package com.yuanx.schedule.task;
import java.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
*
* @ClassName: SaticScheduleTask
* @Description: 靜態定時任務(基於註解@Scheduled 默認爲單線程,開啓多個任務時,任務的執行時機會受上一個任務執行時間的影響)
* @author YuanXu
* @date 2019年7月22日 下午5:30:33
*
*/
@Component
public class SaticScheduleTask {
@SuppressWarnings("unused")
private static Logger log = LoggerFactory.getLogger(SaticScheduleTask.class);
/**
*
* @Title: task_one
* @Description: 靜態定時任務一(每10秒執行一次)
* @author YuanXu
*/
@Scheduled(cron = "0/10 * * * * ?")
private void task_one() throws InterruptedException {
System.out.println(String.format("靜態定時任務一\r\n{%s}-[%s]\r\n執行時間: %s", Thread.currentThread().getName(), this.getClass().getName(), LocalDateTime.now()));
Thread.sleep(6 * 1_000);
System.out.println();
}
/**
*
* @Title: task_two
* @Description: 靜態定時任務二(每15秒執行一次)
* @author YuanXu
*/
@Scheduled(cron = "0/15 * * * * ?")
private void task_two() {
System.out.println(String.format("靜態定時任務二\r\n{%s}-[%s]\r\n執行時間: %s", Thread.currentThread().getName(), this.getClass().getName(), LocalDateTime.now()));
System.out.println();
}
}
基於註解的靜態單線程定時任務測試結果
基於註解的靜態多線程定時任務實現
package com.yuanx.schedule.task;
import java.time.LocalDateTime;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
*
* @ClassName: MultithreadScheduleTask
* @Description: 多線程定時任務
* @author YuanXu
* @date 2019年7月24日 上午11:41:59
*
*/
@Component
@EnableAsync
public class MultithreadScheduleTask {
/**
*
* @Title: task_one
* @Description: 多線程定時任務一
* @author YuanXu
* @throws InterruptedException
*/
@Async
@Scheduled(fixedDelay = 1000)
public void task_one() throws InterruptedException {
System.out.println(String.format("多線程定時任務一\r\n{%s}-[%s]\r\n執行時間: %s", Thread.currentThread().getName(), this.getClass().getName(), LocalDateTime.now().toLocalTime()));
System.out.println();
Thread.sleep(1000 * 10);
}
/**
*
* @Title: task_two
* @Description: 多線程定時任務二
* @author YuanXu
*/
@Async
@Scheduled(fixedDelay = 2000)
public void task_two() {
System.out.println(String.format("多線程定時任務二\r\n{%s}-[%s]\r\n執行時間: %s", Thread.currentThread().getName(), this.getClass().getName(), LocalDateTime.now().toLocalTime()));
System.out.println();
}
}
基於註解的靜態多線程定時任務測試結果
基於接口的動態多線程定時任務實現
package com.yuanx.schedule.task;
import java.time.LocalDateTime;
import java.util.List;
import org.quartz.CronExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import com.yuanx.schedule.entity.TTaskCronEntity;
import com.yuanx.schedule.service.ITTaskCronService;
/**
*
* @ClassName: DynamicScheduleTask
* @Description: 動態定時任務(基於接口[SchedulingConfigurer])
* @author YuanXu
* @date 2019年7月23日 下午4:32:51
*
*/
@Component
public class DynamicScheduleTask implements SchedulingConfigurer {
private static Logger log = LoggerFactory.getLogger(DynamicScheduleTask.class);
@Autowired
private ITTaskCronService taskCronService;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// 動態定時任務添加多線程支持
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(20);
taskScheduler.initialize();
taskRegistrar.setTaskScheduler(taskScheduler);
List<TTaskCronEntity> tasks = taskCronService.getEnableTask();
if (tasks != null && tasks.size() > 0) {
tasks.forEach(task -> {
String corn = task.getCron();
if (!StringUtils.isEmpty(corn) && CronExpression.isValidExpression(corn)) {
// 任務內容
Runnable runnable = () -> {
System.out.println(String.format("[1]動態定時任務: %s\r\n{%s}-[%s]\r\n執行時間: %s", task.getId(), Thread.currentThread().getName(), this.getClass().getName(), LocalDateTime.now()));
System.out.println();
};
// 任務觸發器
Trigger trigger = triggerContext -> {
TTaskCronEntity tsk = taskCronService.findById(task.getId());
String corn_new = tsk.getCron();
if (!StringUtils.isEmpty(corn_new) && !corn_new.equals(corn) && CronExpression.isValidExpression(corn_new)) {
CronTrigger cronTrigger = new CronTrigger(corn_new);
return cronTrigger.nextExecutionTime(triggerContext);
} else {
CronTrigger cronTrigger = new CronTrigger(corn);
return cronTrigger.nextExecutionTime(triggerContext);
}
};
// 註冊任務
taskRegistrar.addTriggerTask(runnable, trigger);
} else if (task.getFixedDelay() != null) {
// 任務內容
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(String.format("[2]動態定時任務: %s\r\n{%s}-[%s]\r\n執行時間: %s", task.getId(), Thread.currentThread().getName(), this.getClass().getName(), LocalDateTime.now()));
System.out.println();
}
};
// 註冊任務
taskRegistrar.addFixedDelayTask(runnable, task.getFixedDelay());
} else if (task.getFixedRate() != null) {
Runnable runnable = () -> {
System.out.println(String.format("[3]動態定時任務: %s\r\n{%s}-[%s]\r\n執行時間: %s", task.getId(), Thread.currentThread().getName(), this.getClass().getName(), LocalDateTime.now()));
System.out.println();
};
// 註冊任務
taskRegistrar.addFixedRateTask(runnable, task.getFixedRate());
} else {
log.error("ID爲[{}]的定時任務參數異常,初始化失敗!", task.getId());
}
});
}
}
}
說明:默認情況下SchedulingConfigurer採用的是單線程的實現方式,如果需要實現多線程,則需要指定PoolSize,代碼如下:
// 動態定時任務添加多線程支持
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(20);
taskScheduler.initialize();
taskRegistrar.setTaskScheduler(taskScheduler);
動態定時任務配置表結構
基於接口的動態多線程定時任務測試結果
@Scheduled註解的cron表達式說明
Cron表達式是一個字符串,字符串以5或6個空格隔開,分爲6或7個域,每一個域代表一個含義,Cron有如下兩種語法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year或 Seconds Minutes Hours DayofMonth Month DayofWeek
說明:每一個域都使用數字,但還可以出現如下特殊字符,它們的含義是:
*
:表示匹配該域的任意值,假如在Minutes域使用*, 即表示每分鐘都會觸發事件。- ? :只能用在DayofMonth和DayofWeek兩個域。因爲DayofMonth和 DayofWeek會相互影響,所以按我的理解
?
應該是佔位符。例如想在每月的20日觸發調度,不管20日到底是星期幾,則只能使用如下寫法:13 13 15 20 * ?
, 其中最後一位只能用?
,而不能使用*。
-
:表示範圍,例如在Minutes域使用5-20,表示從5分到20分鐘每分鐘觸發一次/
:表示起始時間開始觸發,然後每隔固定時間觸發一次,例如在Minutes域使用5/20,則意味着5分鐘觸發一次,而25,45等分別觸發一次。,
:表示列出枚舉值值。例如:在Minutes域使用5,20,則意味着在5和20分每分鐘觸發一次。L
:表示最後,只能出現在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最後的一個星期四觸發。W
: 表示有效工作日(週一到週五),只能出現在DayofMonth域,系統將在離指定日期的最近的有效工作日觸發事件。例如:在 DayofMonth使用5W,如果5日是星期六,則將在最近的工作日:星期五,即4日觸發。如果5日是星期天,則在6日(週一)觸發;如果5日在星期一 到星期五中的一天,則就在5日觸發。另外一點,W的最近尋找不會跨過月份。LW
:這兩個字符可以連用,表示在某個月最後一個工作日,即最後一個星期五。#
:用於確定每個月第幾個星期幾,只能出現在DayofMonth域。例如在4#2,表示某月的第二個星期三。
corn表達式例子:
cron表達式 | 描述 |
---|---|
0 0 10,15,16 * * ? | 每天上午10點,下午3點,4點 |
0 0/30 9-17 * * ? | 朝九晚五工作時間內每半小時 |
0 0 12 ? * WED | 表示每個星期三中午12點 |
0 0 12 * * ? | 每天中午12點觸發 |
0 15 10 ? * * | 每天上午10:15觸發 |
0 15 10 * * ? | 每天上午10:15觸發 (跟上面的一樣) |
0 15 10 * * ? 2005 | 2005年的每天上午10:15觸發 |
0 * 14 * * ? | 在每天下午2點到下午2:59期間的每1分鐘觸發 |
0 0/5 14 * * ? | 在每天下午2點到下午2:55期間的每5分鐘觸發 |
0 0/5 14,18 * * ? | 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發 |
0 0-5 14 * * ? | 在每天下午2點到下午2:05期間的每1分鐘觸發 |
0 10,44 14 ? 3 WED | 每年三月的星期三的下午2:10和2:44觸發 |
0 15 10 ? * MON-FRI | 週一至週五的上午10:15觸發 |
0 15 10 15 * ? | 每月15日上午10:15觸發 |
0 15 10 L * ? | 每月最後一日的上午10:15觸發 |
0 15 10 ? * 6L | 每月的最後一個星期五上午10:15觸發 |
0 15 10 ? * 6L 2002-2005 | 2002年至2005年的每月的最後一個星期五上午10:15觸發 |
corn表達式正則校驗
String regEx = "(((^([0-9]|[0-5][0-9])(\\,|\\-|\\/){1}([0-9]|[0-5][0-9]))|^([0-9]|[0-5][0-9])|^(\\* ))((([0-9]|[0-5][0-9])(\\,|\\-|\\/){1}([0-9]|[0-5][0-9]) )|([0-9]|[0-5][0-9]) |(\\* ))((([0-9]|[01][0-9]|2[0-3])(\\,|\\-|\\/){1}([0-9]|[01][0-9]|2[0-3]) )|([0-9]|[01][0-9]|2[0-3]) |(\\* ))((([0-9]|[0-2][0-9]|3[01])(\\,|\\-|\\/){1}([0-9]|[0-2][0-9]|3[01]) )|(([0-9]|[0-2][0-9]|3[01]) )|(\\? )|(\\* )|(([1-9]|[0-2][0-9]|3[01])L )|([1-7]W )|(LW )|([1-7]\\#[1-4] ))((([1-9]|0[1-9]|1[0-2])(\\,|\\-|\\/){1}([1-9]|0[1-9]|1[0-2]) )|([1-9]|0[1-9]|1[0-2]) |(\\* ))(([1-7](\\,|\\-|\\/){1}[1-7])|([1-7])|(\\?)|(\\*)|(([1-7]L)|([1-7]\\#[1-4]))))|(((^([0-9]|[0-5][0-9])(\\,|\\-|\\/){1}([0-9]|[0-5][0-9]) )|^([0-9]|[0-5][0-9]) |^(\\* ))((([0-9]|[0-5][0-9])(\\,|\\-|\\/){1}([0-9]|[0-5][0-9]) )|([0-9]|[0-5][0-9]) |(\\* ))((([0-9]|[01][0-9]|2[0-3])(\\,|\\-|\\/){1}([0-9]|[01][0-9]|2[0-3]) )|([0-9]|[01][0-9]|2[0-3]) |(\\* ))((([0-9]|[0-2][0-9]|3[01])(\\,|\\-|\\/){1}([0-9]|[0-2][0-9]|3[01]) )|(([0-9]|[0-2][0-9]|3[01]) )|(\\? )|(\\* )|(([1-9]|[0-2][0-9]|3[01])L )|([1-7]W )|(LW )|([1-7]\\#[1-4] ))((([1-9]|0[1-9]|1[0-2])(\\,|\\-|\\/){1}([1-9]|0[1-9]|1[0-2]) )|([1-9]|0[1-9]|1[0-2]) |(\\* ))(([1-7](\\,|\\-|\\/){1}[1-7] )|([1-7] )|(\\? )|(\\* )|(([1-7]L )|([1-7]\\#[1-4]) ))((19[789][0-9]|20[0-9][0-9])\\-(19[789][0-9]|20[0-9][0-9])))";
String tests = "0 0 0 L * ?";
System.out.println(tests.matches(regEx));
corn表達式CronExpression校驗
這個方法需要引入quartz的jar包,然後只需要一行代碼就可以驗證了。
CronExpression.isValidExpression(cron)
@Scheduled註解的fixedRate屬性說明
fixedDelay是設定上一個任務結束後多久執行下一個任務,也就是fixedDelay只關心上一任務的結束時間和下一任務的開始時間。
@Scheduled(fixedDelay = 5*1_000)
public void doTask() throws InterruptedException {
logger.info(Thread.currentThread().getName()+"===task run");
Thread.sleep(6*1_000);
logger.info(Thread.currentThread().getName()+"===task end");
}
@Scheduled註解的fixedDelay屬性說明
fixedRate設置的上一個任務的開始時間到下一個任務開始時間的間隔。
說明:
- fixedRate配置了上一次任務的開始時間到下一次任務的開始時間的間隔,每次任務都會執行;
- fixedDelay配置了上一次任務的結束時間到下一次任務的開始時間的間隔,每次任務都會執行;
- cron表達式配置了在哪一刻執行任務,會在配置的任務開始時間判斷任務是否可以執行,如果能則執行,不能則會跳過本次執行;
- 如果是強調任務間隔的定時任務,建議使用fixedRate和fixedDelay,如果是強調任務在某時某分某刻執行的定時任務,建議使用cron表達式。