1 Spring任务调度
在实际应用中,有些业务并不是有用户操作执行的,而是根据时间需要去调度的。例如:一个电商系统,可能需要在每天晚上(系统闲时)定时检查商品库存,并把库存量告警的商品汇总成EMAIL发送给系统管理员。这种需要定时执行的事情称为“任务调度”。
Quartz是Java开源世界中最著名的任务调度框架,Spring作为容器框架可以很方便的与Quartz集成;在Spring 3.x之后,Spring甚至内置了轻量级的任务调度功能。如果要实现的任务调度不复杂仅仅Spring就足够了,如果要更复杂的控制,则需要Quartz。
下面简单介绍Spring任务调度的使用。
1.1 使用@Scheduled注解定时调用任务
(1)导入依赖
实际上@Scheduled就位于spring-context依赖中,因此无需导入额外的依赖。
代码语言:javascript复制 <!-- Spring DI容器 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.5.RELEASE </version>
</dependency>
(2)在spring配置文件中添加task命名空间声明,并开启注解驱动任务调度
Spring 3.x的任务调度配置,需要导入task命名空间
代码语言:javascript复制<?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:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd">
……
<!-- 注解驱动任务调度 -->
<task:annotation-driven/>
</beans>
(3)创建任务服务,并标注任务的调度规则
一般情况下,使用@Scheduled去标记任务调度方法。
任务调度方法应该是一个“public”修饰的返回“void ”的“无参”方法。
@Scheduled的“fixedDelay”是任务调用的周期,以毫秒为单位。
@Scheduled的“initialDelay”是Spring启动后任务开始的延时时间。
代码语言:javascript复制@Service
public class ProductJobs {
@Autowired
private ProductBiz productBiz;
@Scheduled(fixedDelay=5000, initialDelay=3000)
public void checkWarningProducts() throws IOException {
//创建以当前“日期时间”为签名的文件
Calendar cal = Calendar.getInstance();
String path = String.format("d:/Product-Stock.csv",
cal.get(Calendar.YEAR),cal.get(Calendar.MONTH),cal.get(Calendar.DATE), cal.get(Calendar.HOUR_OF_DAY),cal.get(Calendar.MINUTE),cal.get(Calendar.SECOND));
System.out.println("---输出文件:" path " ---");
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter( new FileOutputStream(path), "gbk"));
//调用并输出数据
for(Product p : productBiz.getWarningProducts()) {
String line = String.format("%s,%s,%f,%d", p.getCode(), p.getName(), p.getUnitPrice(), p.getQuantity());
bw.write(line);
bw.newLine();
}
bw.flush();
bw.close();
}
}
1.2 使用Cron表达式定义精确的时刻:
“fixedDelay”只能按时间周期来运行,如果希望在特定时刻(时钟时间)去执行,如晚上0点整,则需要使用Cron表达式。
1.2.1 Cron表达式
(1)Cron表达式由6~7个由空格分隔的时间元素组成,第7个元素可选。Cron表达式的每个字段,都可以显式地规定一个值(如49)、一个范围(如1-6)、一个列表(如1,3,5)或者一个通配符(如*)。
位置 | 字段含义 | 范围 | 允许的特殊字符 |
---|---|---|---|
1 | 秒 | 0~59 | * / |
2 | 分钟 | 0~59 | * / |
3 | 小时 | 0~23 | * / |
4 | 月份中的哪一天 | 1~31 | * / ? L |
5 | 月份 | 1~12 或 JAN~DEC | * / |
6 | 星期几 | 1~7 或 SUN~SAT | * / ? L # |
7 | 年份 | 1970~2099 | * / |
(2)Cron表达式有几个特殊的字符,说明如下
“ - ”:中划线,表示一个范围
“ , ”:使用逗号间隔的数据,表示一个列表
“ * ”:表示每一个值,它可以用于所有字段。例如:在小时字段表示每小时
“ ? ”:该字符仅用于“月份中的哪一天”字段和“星期几”字段,表示不指定值
“ / ”:通常表示为x/y,x为起始值,y表示值的增量。
“ L ”:表示最后一天,仅在日期和星期字段中使用
“ # ”:只能用于“星期几”字段,表示这个月的第几个周几。例如:“6#3”指这个月第三个周五
(3)一些示例
Cron表达式 | 含义 |
---|---|
0 0 8-12 ? * MON-FRI | 每个工作日的8点到12点 |
0 15 4 * * ? | 每天凌晨4点15分 |
30 0 0 1 1 ? 2014 | 2014年1月1日凌晨过30秒 |
0 0 14 1,10,20 * ? * | 每月的1号、10号、20号的下午2点 |
0 0 17 L * ? | 每月最后一天17:00运行 |
0 0 10 ? * 6L | 每月最后一个星期五10:00运行 |
0 0/5 15,17 * * ? | 每天15点到16点每5分钟运行一次, 此外,每天17点到18点每5分钟运行一次 |
0 30 10 ? * 6#3 2013 | 2013年每月的第三个星期五上午10:30触发 |
1.2.2 在@Scheduled中使用cron表达式
代码语言:javascript复制@Scheduled注解中的cron属性用于设置cron表达式。
@Service
public class ProductJobs {
……
@Scheduled(cron="0 15 11 * * ?")
public void checkWarningProducts() throws IOException {
……
}
}
2 Spring异步调用
Java中的方法通常都是同步调用的,同步意味着可能发生阻塞。如果被调用方法需要访问网络,则难以保证调用的时间,例如发送Email、SMS短信或者Web服务器。这时,我们应该使用异步(多线程)的方式去调用。在传统Java编程中,异步往往要通过多线程来实现,复杂较高。
Spring提供了@Async注解,可以傻瓜式的实现功能的异步调用。
假设发送一封邮件可能需要一定的时间。
代码语言:javascript复制@Service
public class EmailSender {
public void sendEmail(String to, String content) {
System.out.println("---邮件发送开始---");
try {
Thread.sleep(3000); //虚拟邮件发送时所耗费的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---邮件发送成功---");
}
}
执行该功能时,主程序会一致处在等待之中。
代码语言:javascript复制 public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-beans.xml");
EmailSender emailSender = ctx.getBean(EmailSender.class);
emailSender.sendEmail("zhang3@126.com", "测试邮件");
System.out.println("继续其他任务...");
}
我们可以使用@Async注解,修饰方法,则该方法在调用时会通过另一个线程执行,主程序无需等待。
代码语言:javascript复制@Service
public class EmailSender {
@Async
public void sendEmail(String to, String content) {
……
}
}