什么是任务调度?
在生活中,我们经常会制定一些“计划任务”,即在某个时间点做某件事情。同样地,在企业级应用中,也会经常碰到类似的任务调度的需求,下面来看几个例子。
在购物网站,每天凌晨统计商品名、商家排名,每天晚上定点统计当日的销量、销售额、盈利等信息并生成报表,每15分钟查询用户的新订单并推送给对应处理人。
在社区网站,每天统计用户的在线时长,并按照某种规则给予一定的称号和奖励等。
在后台服务中做系统维护,每个工作日的固定时间将数据进行备份。
可见,企业应用中离不开灵活的任务调度。
业务情景
- 1)规定在将来的某一特定的时间点执行设置业务。
- 2)周期性反复执行特定业务。
- 3)资源有效期控制 等等…
从以上例子可以看到,调度的核心是以时间为关注点,即在一个特定的时间点,系统执行指定的一个或一些操作。任务调度本身涉及多线程并发、运行时间规则解析、运行现场的保护与恢复及线程池维护等。这项非常复杂的工作,可以通过一个开源任务调度框架来实现,它就是Quartz框架。
任务调度就是后台服务
了解更多:前端,后端,前台,后台到底应该怎么理解。
Quartz与Spring的关系
Quartz框架是一个开源的企业级任务调度服务,它可以被单独使用,也可以整合进任何Java应用,从小型应用到大型的电子商务系统,Quartz已经被作为任务调度的良好解决方案。Quartz提供了强大的任务调度机制,使用也非常简单。Quartz允许开发人员灵活定义调度时间表,提供和任务进行关联的便捷方法,另外,Quartz提供了调度运行环境的持久化机制,使系统出现故障关闭时,任务调度现场数据可以保存下来不致丢失。
Quartz是一个强大的企业级任务调度框架,Spring中继承并简化了Quartz。
Cron 表达式
特殊字符
Cron 触发器利用一系列特殊字符,如下所示:
-
反斜线(/)字符表示增量值。例如,在秒字段中“5/15”代表从第 5 秒开始,每 15 秒一次。
-
问号(?)字符和字母 L 字符只有在月内日期和周内日期字段中可用。问号表示这个字段不包含具体值。所以,如果指定月内日期,可以在周内日期字段中插入“?”,表示周内日期值无关紧要。字母 L 字符是 last 的缩写。放在月内日期字段中,表示安排在当月最后一天执行。在周内日期字段中,如果“L”单独存在,就等于“7”,否则代表当月内周内日期的最后一个实例。所以“0L”表示安排在当月的最后一个星期日执行。
-
在月内日期字段中的字母(W)字符把执行安排在最靠近指定值的工作日。把“1W”放在月内日期字段中,表示把执行安排在当月的第一个工作日内。
-
井号(#)字符为给定月份指定具体的工作日实例。把“MON#2”放在周内日期字段中,表示把任务安排在当月的第二个星期一。
-
星号(*)字符是通配字符,表示该字段可以接受任何可能的值。
Spring中怎样配置Quartz
基于AOP的 xml 配置
依赖项pom.xml文件:
<properties>
<spring.version>4.3.14.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
被调度的方法:
package com.uplooking.service;
import java.sql.Timestamp;
//任务调度执行方法
public class TeakService {
public void hello(){
System.out.println("Hello Quartz\t"+new Timestamp(System.currentTimeMillis()));
/*
说明:
每秒中都进行数据库查询优惠券表,
查询优惠券状态为正常(排除已经过期和使用过的优惠券),
并且过期时间超过当前系统时间的优惠券,遍历设置为过去,并写入到日志中
*/
}
}
我们在 applicationContext.xml 文件中配置 aop 自动生成代理,进行事务管理:
①、代理目标:要调用的工作类(com.uplooking.service)
②、代理业务:定义调用对象和调用对象的方法(hello)
③、计划安排: 定义触发时间(value="* * * * * ?" 表示每秒钟执行一次)
③、计划工厂:总管理类 如果将lazy-init='false’那么容器启动就会执行调度程序,可以不设置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
">
<!-- 代理目标 -->
<bean id="teakService" class="com.uplooking.service.TeakService"></bean>
<!--代理业务-->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="teakService"></property>
<property name="targetMethod" value="hello"></property>
</bean>
<!--计划安排-->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail"></property>
<property name="cronExpression" value="* * * * * ?"></property>
</bean>
<!-- 计划工厂 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<!-- 触发需要执行的时间计划 -->
<ref bean="cronTrigger"></ref>
</list>
</property>
</bean>
</beans>
启动任务
package com.uplooking.app;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
//容器启动就会执行调度程序
String path="applicationContext.xml";
ApplicationContext ctx=new ClassPathXmlApplicationContext(path);
}
}
运行效果:
基于AOP的 注解 配置
依赖项pom.xml文件:
<properties>
<spring.version>4.3.14.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
被调度的方法:
package com.uplooking.service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
@Service("teakService")
public class TeakService {
@Scheduled(cron = "* * * * * ?")
public void hello(){
System.out.println("@Scheduled Hello Quartz\t"+new Timestamp(System.currentTimeMillis()));
}
}
分为如下两步:
①、在applicationContext.xml 配置任务调度器,将并事务管理器交予spring
②、在目标方法添加注解即可 @Scheduled
在 applicationContext.xml 文件中配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd
">
<!-- 启动自动扫描注解 -->
<context:component-scan base-package="com.uplooking.service"></context:component-scan>
<!-- 启动任务调度 -->
<task:annotation-driven></task:annotation-driven>
</beans>
启动任务
package com.uplooking.app;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
String path="applicationContext.xml";
ApplicationContext ctx=new ClassPathXmlApplicationContext(path);
}
}
运行效果: