Spring任务调度与异步处理

2022-11-15 13:32:35 浏览数 (2)

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) {

                ……

        }

}

0 人点赞