日常系统开发中定时任务用的非常地普遍,比如我们可能想做个定时器去查询某笔交易的状态并进行汇总,又或者我们想在凌晨4点清楚数据库的相关数据、又或者我们想在每月月底0点定时去开启一个事务对当月、季度的数据做统计做成报表,灰常多呀!我也说不完啦!
有一点小编对于定时任务的理解,那就是:定时任务只是告诉系统在某个时刻执行某个任务,而至于该任务什么时候执行完成,这不是定时任务要关心的范围,定时任务只需要保证某个时刻发出调用某个任务的指令即可。
在Java领域定时任务实现的佼佼者就是 quartz
了, quartz
框架在spring 3.0之前用的很广泛,当然现在也是, quartz
支持数据持久化以及相应的集群方式部署,spring 3.0 之后的spring版本自己实现了一套定时任务框架,我们可以把看做是 quartz
的低配版本,它没有提供对集群的支持,不过其他功能已经灰常强大啦!
跟随spring传统,spring自己实现的定时任务框架spring-task同时支持xml和注解方式配置定时任务,当然小编还会讲解我们如何自定义定时任务。翻开spring源码包我们可以发现,其实spring除了自家的spring-task,同时还提供了 quartz
的集成,关于spring如何集成 quartz
,请阅读小编之前写的一篇文章 简单说说Java 定时任务框架---Quartz
下面我将分别从xml、注解、自定义3个方面讲解如何使用spring-task.
定时任务组件分为三个:调度器、任务以及执行时间点。我们需要引入 bean
、 context
、 core
三个spring包。
application.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:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <!-- 创建一个调度器 --> <task:scheduler id="schedualer"/> <!-- 配置任务类的bean --> <bean id="xmlTask" class="wokao666.club.spring_task.tasks.XmlTask"></bean> <task:scheduled-tasks scheduler="schedualer"> <!-- 每2秒执行一次 --> <task:scheduled ref="xmlTask" method="say" cron="0/2 * * * * ?"/> </task:scheduled-tasks> </beans>
XmlTask.java
package wokao666.club.spring_task.tasks; import java.text.SimpleDateFormat; import wokao666.club.spring_task.util.DateFormatter; /** * 基于XML的spring定时任务 */ public class XmlTask { public void say() { SimpleDateFormat format = DateFormatter.getDateFormatter(); System.err.println(format.format(System.currentTimeMillis()) + " I am spring xml-based task!"); } }
DateFormatter.java
package wokao666.club.spring_task.util; import java.text.SimpleDateFormat; /** * 日期格式 * @author hjw * */ public class DateFormatter { private static volatile SimpleDateFormat formater = null; private DateFormatter() {} public static SimpleDateFormat getDateFormatter() { if(null == formater) { synchronized (DateFormatter.class) { if(null == formater) { formater = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } } } return formater; } }
App.java
package wokao666.club.spring_task; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Hello world! * */ public class App { private static ClassPathXmlApplicationContext ctx; public static void main( String[] args ) { ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); } }
程序输出:
六月 10, 2018 10:47:11 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2328c243: startup date [Sun Jun 10 22:47:11 CST 2018]; root of context hierarchy 六月 10, 2018 10:47:11 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [applicationContext.xml] 六月 10, 2018 10:47:12 下午 org.springframework.scheduling.concurrent.ExecutorConfigurationSupport initialize 信息: Initializing ExecutorService 'schedualer' 2018-06-10 10:47:14 I am spring xml-based task! 2018-06-10 10:47:16 I am spring xml-based task! 2018-06-10 10:47:18 I am spring xml-based task!
applicationContext.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:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <!-- 开启注解驱动 --> <task:annotation-driven/> <!-- bean 扫描 --> <context:component-scan base-package="wokao666.club.spring_task.tasks"></context:component-scan> </beans>
AnnotationTask.java
package wokao666.club.spring_task.tasks; import java.text.SimpleDateFormat; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import wokao666.club.spring_task.util.DateFormatter; /** * 测试基于注解的定时任务 * @author hjw */ @Service public class AnnotationTask { @Scheduled(cron="0/2 * * * * ?") public void say() { SimpleDateFormat format = DateFormatter.getDateFormatter(); System.err.println(format.format(System.currentTimeMillis()) + " good morning"); } }
App.java
、 DateFormatter.java
和上面一样,程序输出如下:
六月 10, 2018 10:53:45 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2328c243: startup date [Sun Jun 10 22:53:45 CST 2018]; root of context hierarchy 六月 10, 2018 10:53:45 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [applicationContext.xml] 六月 10, 2018 10:53:46 下午 org.springframework.beans.factory.support.DefaultListableBeanFactory registerBeanDefinition 信息: Overriding bean definition for bean 'org.springframework.context.annotation.internalScheduledAnnotationProcessor' with a different definition: replacing [Generic bean: class [org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.scheduling.annotation.SchedulingConfiguration; factoryMethodName=scheduledAnnotationProcessor; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/scheduling/annotation/SchedulingConfiguration.class]] 六月 10, 2018 10:53:46 下午 org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor finishRegistration 信息: No TaskScheduler/ScheduledExecutorService bean found for scheduled processing 2018-06-10 10:53:48 good morning 2018-06-10 10:53:50 good morning 2018-06-10 10:53:52 good morning 2018-06-10 10:53:54 good morning
注意到上面输出 No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
这句话,因为你可以传入一个 java.util.Executor
线程池对象来实现对异步任务的调用,如果你指定了 executor
属性,则spring会默认创建一个 SimpleAsyncTaskExecutor
对象去执行, executor
属性指定定时任务执行的线程池,强调执行,而 scheduler
属性表示任务调度的线程池,因为spring默认是单线程串行调度,如果想允许多线程并发调度,则需要配置 scheduler
属性,配置示例如下:
<!-- 调度线程池 --> <task:scheduler id="scheduler" pool-size="10" /> <!-- 任务执行线程池 --> <task:executor id="executor" pool-size="10" /> <task:annotation-driven executor="executor" scheduler="scheduler"/> <context:component-scan base-package="wokao666.club.spring_task.tasks"></context:component-scan>
(此处还需另行深入学习spring异步任务相关知识原理,异步执行使用 @Async
标注方法)
实现自定义定时任务,或者说我们可以更改相关的 cron
表达式,这看起来是很不可思议的,但确实可以做到,小编推荐一篇例文 Spring @Scheduled定时任务动态修改cron参数
CustomSchedual.java
package wokao666.club.spring_task.tasks; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component; import wokao666.club.spring_task.util.DateFormatter; /** * 演示自定义定时器 */ @EnableScheduling @Component public class CustomSchedual implements SchedulingConfigurer{ private static String cron = "0/2 * * * * ?"; private CustomSchedual() { new Thread(()->{ try { Thread.sleep(16000); } catch (InterruptedException e) {} cron = "0/10 * * * * ?"; System.err.println("change") }).start(); } public void configureTasks(ScheduledTaskRegistrar arg0) { arg0.addTriggerTask(()->{ System.err.println(DateFormatter.getDateFormatter().format(System.currentTimeMillis()) + " good night!"); },(triggerContext)->{ CronTrigger trigger = new CronTrigger(cron); return trigger.nextExecutionTime(triggerContext); }); } }
程序输出如下,可以看到,程序过来16秒之后,执行频率从2秒/次变为10秒/次:
六月 10, 2018 11:49:13 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2328c243: startup date [Sun Jun 10 23:49:13 CST 2018]; root of context hierarchy 六月 10, 2018 11:49:13 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [applicationContext.xml] 六月 10, 2018 11:49:14 下午 org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor finishRegistration 信息: No TaskScheduler/ScheduledExecutorService bean found for scheduled processing 2018-06-10 11:49:16 good night! 2018-06-10 11:49:18 good night! 2018-06-10 11:49:20 good night! 2018-06-10 11:49:22 good night! 2018-06-10 11:49:24 good night! 2018-06-10 11:49:26 good night! 2018-06-10 11:49:28 good night! 2018-06-10 11:49:30 good night! change 2018-06-10 11:49:32 good night! 2018-06-10 11:49:40 good night! 2018-06-10 11:49:50 good night! 2018-06-10 11:50:00 good night! 2018-06-10 11:50:10 good night! 2018-06-10 11:50:20 good night!
最后,单纯会使用还不行,我们不仅要知其然,还要知其所以然,后期有时间多学学原理,其实底层无非就是java线程池的使用,加油,骚年们,晚安!