1.概述
在某些业务场景中,需要定时执行一些任务,有可能是定时统计然后生成报表,有可能是定时发起一个任务。最近在工作中就正好遇见一个定时发起问卷任务的一个业务场景,此处集合业务场景聊聊如何用spring boot来实现功能。
2.Spring Boot定时任务
2.1.快速使用
spring boot支持了定时任务,通过很简单的配置就可以使用。
依赖:
定时任务是spring boot框架提供的基础能力之一,所以其依赖是在spring-boot-starter里面,但是一般开发的时候我们直接引入web依赖即可,web依赖中包含了spring-boot-starter。要注意的是Spring Boot 从版本1.3.0开始提供对定时任务的支持,本文用的依赖默认是支持定时任务的版本。
<dependencies> <!-- Spring Boot Web Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
开启定时任务:
任务内容:
2.2.cron表达式
spring boot中是使用cron表达式来定义定时任务的执行条件的。cron表达式由6个字段组成,分别表示秒、分、时、日、月、周几。每个字段都可以接受特定的取值范围或通配符来表示时间的匹配规则。
下面是cron表达式的每个字段的含义和可接受的取值:
秒(0-59)
分钟(0-59)
小时(0-23)
日(1-31)
月(1-12或JAN-DEC)
周几(0-7或SUN-SAT,其中0和7都表示周日)
除了具体的取值范围,cron表达式还支持一些特殊字符和符号:
星号(*):表示匹配任意值。例如,使用星号在秒字段中,表示任务在每一秒都会被触发。
问号(?):仅在日期和周几字段中使用,表示不指定具体的值。如果你想在日期字段中匹配任意值,但同时又想在周几字段中指定具体值,就可以使用问号。
斜线(/):用于指定增量。例如,在分钟字段中使用5/10,表示从第5分钟开始,每隔10分钟触发一次。
逗号(,):用于列举多个值。例如,在周几字段中使用1,3,5,表示任务在周一、周三和周五触发。
连接符(-):用于指定范围。例如,在小时字段中使用9-17,表示任务在9点到17点之间触发。
以下是一些示例cron表达式的用法:
0 0 0 * * *:每天凌晨执行任务
0 0/5 * * * *:每隔5分钟执行任务
0 0 9-17 * * MON-FRI:周一至周五的9点到17点之间每小时执行任务
3.业务示例
3.1.业务描述
需求:
- 管理人员新建一个问卷调查任务,任务在固定时间发起,固定时间结束。
- 任务开始后,系统的每个用户会收到一份问卷
- 任务结束后,用户不可再答题
实体关系:
整个实体关系很简单,就是一个任务一种模板,一个任务多份问卷,一个模板N份问卷,一个问卷一个用户。
问卷里记录了模板内容、任务ID、用户ID。
3.2.业务实现
问卷调查任务类:
这里使用JPA去访问数据库
@Entity @Table(name = "survey_task") public class SurveyTask { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String taskName; private LocalDateTime startTime; // 其他属性和对应的getter/setter方法 }
定时任务:
这里只是简单的写了一下定时任务的逻辑,实际的代码实现从简。
@Service public class SurveyTaskScheduler { @Autowired private SurveyTaskRepository surveyTaskRepository; @Scheduled(cron = "0 0 0 * * *") // 每天凌晨执行 public void distributeSurveyTemplates() { LocalDate today = LocalDate.now(); LocalDateTime startOfDay = today.atStartOfDay(); List<SurveyTask> surveyTasks = surveyTaskRepository.findByStartTime(startOfDay); for (SurveyTask task : surveyTasks) { // 根据任务分发问卷模板给用户的逻辑 // ... System.out.println("分发问卷模板给用户: " + task.getTaskName()); } } }
启动定时任务:
@SpringBootApplication @EnableScheduling public class MyApp { public static void main(String[] args) { SpringApplication.run(MyApp.class, args); } }
4.实现原理
spring boot的定时任务底层其实就是使用线程池来实现的,其流程如下:
首先,Spring Boot 根据配置文件中的属性创建一个 TaskScheduler(如果没有自定义线程池会使用默认线程池ThreadPoolTaskScheduler)实例作为调度器,并设置线程池的相关属性,例如核心线程数、最大线程数、队列容量等。
当应用启动时,Spring Boot 会扫描所有被 @Scheduled 注解标记的方法,并将这些方法注册为定时任务。
当定时任务的触发条件满足时,调度器将从线程池中选择一个线程来执行任务。
执行任务时,调度器会通过 TaskExecutor(默认为 ThreadPoolTaskExecutor)实例来执行任务。它会从线程池中选择一个空闲的线程来执行被 @Scheduled 注解标记的方法。
执行完成后,线程将返回到线程池中等待下一次调度。
5.自定义线程池
如果在 Spring Boot 中没有显式配置线程池,那么会使用默认的线程池配置。但是有时候可能我们也需要自定义线程池,自定义线程池的示例如下:
@Configuration public class CustomThreadPoolConfig { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); // 设置线程池大小 // 设置其他线程池属性,如队列容量、线程名前缀等 return scheduler; } }