一、消息服务概述
1.1 什么是消息服务?
消息服务是用于访问企业消息系统的标准API,消息中间件用于实现消息服务。企业消息系统可以协调应用程序通过网络进行消息交互。JMS即Java消息服务(Java Message Service),是Java平台中面向消息中间件的API,用于在两个应用程序之间或分布式系统中发送消息,进行异步通信。JMS扮演的角色与JDBC很相似,正如JDBC提供了一套用于访问各种关系数据库的公共API,JMS也提供了独立于特定厂商的企业消息系统访问方式。
在Spring Boot中,消息服务是一种用于实现异步通信和解耦应用程序组件的机制。它允许应用程序中的不同部分通过发送和接收消息来进行通信,而不需要直接依赖或了解彼此的存在。消息服务使用消息队列中间件来存储和传递消息。
1.2 消息服务处理过程
消息服务从本质上来说,由3部分构成,包括生产部分、存储部分以及消费部分。生产部分把消息发送出来,消息会进入存储部分,存储部分就是后来的消息队列,在消息队列中对发送过来的消息进行排队存储,之后由消费者按消息顺序对接收的消息进行消费。

生产者生产消息,并将消息发送给消息队列,在消息队列中对消息进行一定的排序,后续由消费者把相应消息接收。基于上述结构,产生了多种第三方消息服务API,称为消息中间件。
1.3 常用消息中间件
在Spring Boot中,常见的消息队列中间件有ActiveMQ、RabbitMQ、Apache Kafka和RocketMQ等。这些中间件可以确保消息的可靠传递,并提供高吞吐量和可扩展性。
1.3.1 ActiveMQ(性能相对较弱)
-
高吞吐量和低延迟:RocketMQ设计目标是实现高吞吐量和低延迟的消息传递。它通过优化存储引擎、消息传递协议和网络通信等方面,实现了每秒百万级别的消息处理能力和毫秒级的消息传递延迟。
-
分布式架构和弹性伸缩:RocketMQ采用分布式架构,可以将消息队列分布在多个Broker节点上,并支持动态扩展和负载均衡。这种设计使得RocketMQ具备很强的横向扩展能力,可以根据业务需求进行灵活的扩容和缩容。
-
可靠性和消息顺序保证:RocketMQ提供了可靠的消息传递机制,确保消息不会丢失和重复传输。它使用主从复制的方式保存消息,同时支持同步刷盘和异步刷盘策略,保证消息的持久化和数据安全。此外,RocketMQ还支持消息的有序传递,保证了相同消息在消费端的顺序性。
-
消息广播和发布订阅模式:RocketMQ支持两种消息模型:消息广播和发布订阅。通过消息广播,同一个消息可以被多个消费者同时消费;通过发布订阅,消费者可以根据自己的需求订阅感兴趣的消息主题,并独立消费。
-
多种消息协议和可插拔性:RocketMQ支持多种消息协议,包括原生协议、HTTP和TCP等,可以与各种应用程序和系统进行集成和交互。同时,RocketMQ提供了丰富的客户端和工具库,具备很高的可插拔性和可扩展性。
ActiveMQ是Apache软件基金会出品的、采用Java语言编写的、完全基于JMS规范的、面向消息的中间件,它为应用程序提供高效、可扩展、稳定、安全的企业级消息通信。ActiveMQ丰富的API和多种集群构建模式使得它成为业界“老牌”的消息中间件,广泛地应用于中小型企业中。
主要特性:
-
多种传输协议支持:ActiveMQ支持多种传输协议,包括TCP、UDP、XMPP、STOMP和HTTP等。这使得在不同的应用场景中可以选择不同的传输协议,以达到最优的性能。
-
消息持久化:ActiveMQ支持消息持久化,即可以将消息存储到磁盘上以保证消息不会因为服务故障而丢失。同时也支持内存缓存以提高读写速度。
-
高级管理工具:ActiveMQ提供了一套完整的管理工具,包括Web控制台、命令行工具和JMX接口,使得在运行时可以方便地对队列和主题进行配置和管理。
-
安全性:ActiveMQ提供了多层次的安全措施,包括基于用户、角色或IP地址的授权访问和基于SSL的加密通信等,以保证消息传输过程中的安全性。
-
集群支持:ActiveMQ支持多种集群模式,包括主从复制、点对点复制和网络连接器,使得在不同的集群环境中可以选择不同的模式以支持高可用性和负载均衡。
-
高可靠性:ActiveMQ支持消息确认机制,使得消费者可以在处理完消息后向服务器发送确认消息,从而确保消息不会丢失。
-
Spring集成:ActiveMQ提供了与Spring Framework的深度集成,使得在使用Spring框架时可以方便地使用ActiveMQ。
1.3.2 RabbitMQ
RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP(advanced message queuing protocal高级消息队列协议)实现。AMQP是为应对大规模并发活动而提供统一消息服务的应用层标准高级消息队列协议,专门为面向消息的中间件设计,该协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
RabbitMQ由以下几个主要组成部分构成:
1.3.4 RocketMQ
RocketMQ是阿里巴巴集团开源产品,目前也是Apache软件基金会的顶级项目之一,使用Java开发,具有高吞吐量、高可用、适合大规模分布式系统应用的特点。RocketMQ的设计思路源于Kafka,对消息的可靠传输以及事务性进行了优化。
主要特性:
二、RabbitMQ 消息中间件
消息中间件RabbitMQ是通过接收消息、将消息传送给交换器、交换器再根据路由键把消息传到相应的队列中来运行的。针对不同的服务需求,RabbitMQ提供了多种工作模式,不同的工作模式是通过不同类型的交换器来实现的。
2.1 RabbitMQ的运行机制
2.1.1 Work queues(工作队列模式)
适用于那些较为繁重,并且可以进行拆分处理的业务,这种情况下可以分派给多个消费者轮流处理业务。
在该工作模式中,不需要设置交换器,RabbitMQ会使用内部默认交换器进行消息转换,需要指定唯一的消息队列,并且可以有多个消息消费者。在这种模式下,多个消息消费者通过轮询的方式依次接收消息队列中存储的消息,一旦消息被某个消费者接收,消息队列会将消息移除,接收并处理消息的消费者必须在消费完一条消息后再准备接收下一条消息。
-
Publisher(生产者): 生产者是发送消息的客户端应用程序,它将消息发送到RabbitMQ。生产者可以将消息发送到一个或多个Exchange(交换机),并指定一个Routing Key(路由键)来决定消息的路由策略。
-
Exchange(交换机): 交换机是消息的接收和转发中心,它接收来自生产者的消息,并根据不同的路由规则将消息路由到一个或多个队列。在RabbitMQ中有几种常见的交换机类型,包括Direct、Fanout、Topic和Headers,每种类型都有不同的路由策略。
-
Binding(绑定): 绑定是交换机和队列之间的关联关系。它定义了消息的路由规则,即将交换机上的消息根据特定的条件(例如Routing Key)路由到相应的队列。
-
Queue(队列): 队列是消息的存储和分发单元,它是消息的最终目的地。生产者发送的消息经过交换机的路由后,最终被存储在队列中,等待被消费者接收和处理。
-
Virtual Host(虚拟主机): 虚拟主机是逻辑上的概念,用于在RabbitMQ中实现逻辑上的隔离和资源分配。每个虚拟主机都拥有自己独立的交换机、队列和绑定,不同虚拟主机之间的消息互相隔离,具有独立的权限控制和资源管理。
-
Broker(代理):表示消息队列服务器实体。
-
Connection(连接):网络连接,比如一个TCP连接。
-
Channel(信道):多路复用连接中的一条独立的双向数据流通道。Channel(信道)是建立在连接(Connection)之上的逻辑通道,用于完成消息的传输和处理。一个连接可以包含多个信道,每个信道之间是独立的、并发的通信管道。
-
Consumer(消费者): 消费者是接收和处理消息的客户端应用程序。它从队列中拉取(或订阅)消息,并对接收到的消息进行处理。消费者可以以同步或异步的方式接收消息,并可以根据需要进行消息确认,以确保消息的可靠性传输。

-
图6.2 RabbitMQ的消息代理过程
1.3.3 Kafka
Apache Kafka是一个分布式流式处理平台,用于构建高可扩展性、高吞吐量的实时数据管道。Kafka以其卓越的性能和可靠性而受到青睐。它采用基于日志的架构,支持分布式部署和水平扩展,并提供了一系列的消息保证机制(如至少一次、精确一次)。Kafka通常用于大规模数据流处理和日志聚合。
主要特性:
-
高吞吐量和低延迟: Kafka的设计目标是实现高吞吐量和低延迟的消息传递。通过分布式的消息存储和消费机制,可以轻松地达到每秒百万级别的消息传输能力。
-
分布式存储和数据冗余: Kafka的数据存储采用分布式的方式,每个消息可以被保存在多个Broker上,从而实现数据的冗余和容错。同时,Kafka支持水平扩展和动态扩容,可以灵活地调整系统的存储和处理能力。
-
稳定性和可靠性: Kafka的数据存储采用写入顺序日志(WAL)的方式,保证每个消息都能够被可靠地保存和复制。同时,Kafka支持消息的确认和重试等机制,确保消息在传输过程中不会丢失或重复。
-
实时流数据处理和批量处理: Kafka支持实时流数据处理和大规模批量数据处理两种模式。实时流数据处理模式下,可以实时处理和分析实时产生的数据流;大规模批量数据处理模式下,可以对历史数据进行离线分析和挖掘。
-
多语言和多协议支持: Kafka支持多种编程语言和协议(如Java、Scala、Python、C++和REST等),可以与各种应用程序和系统进行集成和交互。同时,Kafka还支持多种数据格式和序列化方式,如Avro、JSON和Protobuf等
-
高吞吐量和低延迟:RocketMQ设计目标是实现高吞吐量和低延迟的消息传递。它通过优化存储引擎、消息传递协议和网络通信等方面,实现了每秒百万级别的消息处理能力和毫秒级的消息传递延迟。
-
分布式架构和弹性伸缩:RocketMQ采用分布式架构,可以将消息队列分布在多个Broker节点上,并支持动态扩展和负载均衡。这种设计使得RocketMQ具备很强的横向扩展能力,可以根据业务需求进行灵活的扩容和缩容。
-
可靠性和消息顺序保证:RocketMQ提供了可靠的消息传递机制,确保消息不会丢失和重复传输。它使用主从复制的方式保存消息,同时支持同步刷盘和异步刷盘策略,保证消息的持久化和数据安全。此外,RocketMQ还支持消息的有序传递,保证了相同消息在消费端的顺序性。
-
消息广播和发布订阅模式:RocketMQ支持两种消息模型:消息广播和发布订阅。通过消息广播,同一个消息可以被多个消费者同时消费;通过发布订阅,消费者可以根据自己的需求订阅感兴趣的消息主题,并独立消费。
-
多种消息协议和可插拔性:RocketMQ支持多种消息协议,包括原生协议、HTTP和TCP等,可以与各种应用程序和系统进行集成和交互。同时,RocketMQ提供了丰富的客户端和工具库,具备很高的可插拔性和可扩展性。
-

2.1.2 Publish/Subscribe(发布订阅模式)
适用于进行相同业务功能处理的场合。
在该工作模式中,必须先配置一个“fanout”类型的交换器,不需要指定对应的路由键,交换器会将消息路由到每一个消息队列上,每一个消息队列都可以对相同的消息进行接收和存储,进而由各自消息队列关联的消费者进行消费。
-

2.1.3 Routing(路由模式)
适用于进行不同类型消息分类处理的场合。
在Routing工作模式中,必须先配置一个direct类型的交换器,并指定不同的路由键值(Routing key)将对应的消息从交换器路由到不同的消息队列进行存储,由消费者进行各自消费。
-

2.1.4 Topics(通配符模式)
适用于根据不同需求动态传递处理业务的场合。
在Topics工作模式中,必须先配置一个topic类型的交换器,并指定不同的路由键值(Routing key)将对应的消息从交换器路由到不同的消息队列进行存储,然后由消费者进行各自消费。

6.6 通配符模式
注:
符号 “#” 匹配路由键Routing key的一个或多个词,符号 “*” 匹配路由键Routing key的一个词。“#” 和 “*” 都可以代表任意字符或是空,多个字符需要用 “.” 连接
2.1.5 RPC(远程过程调用模式)
适用于远程服务调用的业务处理场合
RPC模式是一个回环结构,主要针对分布式架构的消息传递业务,客户端Client先发送消息到消息队列,远程服务端Server获取消息,然后再写入另一个消息队列,向原始客户端Client响应消息处理结果。

2.3RabbitMQ在Spring Boot中的整合实现
使用Spring Boot整合消息中间件RabbitMQ实现消息服务,主要围绕3个部分的工作展开:配置消息中间件、消息生产者发送消息、消息消费者接收消息。
-
添加相关依赖:
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit-test</artifactId> <scope>test</scope> </dependency> -
在Application.properties配置文件中配置RabbitMQ服务器所在的IP地址、账号及密码。
-
# 配置RabbitMQ消息中间件连接配置 spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest2.3.1 基于API的方式及测试的搭建步骤
基于API的方式主要讲的是使用 Spring 框架提供的API管理类AmapAdmin 定制消息发送组件,并进行消息发送。这种定制消息发送组件的方式与在 RabbitMQ 可视化界面上通过对应面板进行组件操作的实现基本一样,都是通过管理员的身份,预先手动声明交换器、队列、路由健等,然后进行消息队列组装,来供应用程序调用,从而实现消息服务。
① 使用AmqpAdmin定制消息发送组件。
-
在测试类中先引入AmqpAdmin管理类定制Publish/Subscribe工作模式所需的消息组件
-
@Resource private AmqpAdmin amqpAdmin; @Test public void amqpAdmin() { amqpAdmin.declareExchange(new FanoutExchange("fanout_exchange")); amqpAdmin.declareQueue(new Queue("fanout_queue_email")); amqpAdmin.declareQueue(new Queue("fanout_queue_sms")); amqpAdmin.declareBinding(new Binding("fanout_queue_email", Binding.DestinationType.QUEUE,"fanout_exchange","",null)); amqpAdmin.declareBinding(new Binding("fanout_queue_sms", Binding.DestinationType.QUEUE,"fanout_exchange","",null)); } -
执行上述单元测试方法amqpAdmin(),验证RabbitMQ消息组件的定制效果,通过RabbitMQ可视化管理页面的Exchanges面板查看效果。
-
在RabbitMQ可视化管理页面的Exchanges面板中新出现了一个名称为fanout_exchange的交换器,且其类型是设置的fanout类型。单击fanout_exchange交换器进入查看。

-
切换到Queues面板页面,查看定制生成的消息队列信息

消息发送者发送消息
-
创建一个实体类User
@Data public class User implements Serializable { private Integer id; private String username; }
-
在项目测试类中使用Spring框架提供的RabbitTemplate模板类实现消息发送。
@Autowired private RabbitTemplate rabbitTemplate; @Test public void psubPublisher() { User user=new User(); user.setId(1); user.setUsername("石头"); rabbitTemplate.convertAndSend("fanout_exchange","",user); }
-
执行上述测试方法,执行成功,查看RabbitMQ可视化管理页面Queues面板信息,两个消息队列中各自拥有一条待接收的消息。
-
执行上述测试方法,执行成功,查看RabbitMQ可视化管理页面Queues面板信息,两个消息队列中各自拥有一条待接收的消息。

-

执行上述测试方法,执行成功,查看RabbitMQ可视化管理页面Queues面板信息,两个消息队列中各自拥有一条待接收的消息
-
单击某个队列的详情页面,查看具体的信息

-
定制其他类型的消息转换器,使用JSON的格式显示。
创建一个RabbitMQ消息配置类RabbitMQConfig,并在该配置类中通过@Bean注解自定义了一个Jackson2JsonMessageConverter类型的消息转换器组件,该组件的返回值必须为MessageConverter类型
@Configuration
public class RabbitMQConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
再次执行测试类中psubPublisher()代码,在网页RabbitMQ中查看显示效果
③ 消息消费者接收消息
-
消息消费者接收消息,创建一个针对RabbitMQ消息中间件进行消息接收和处理的业务类RabbitMQService,在该类中编写如下方法
@Service public class RabbitMQService { @RabbitListener(queues = "fanout_queue_email") public void psubConsumerEmail(Message message) { byte[] body = message.getBody(); String s = new String(body); System.out.println("邮件业务接收到消息: "+s); } @RabbitListener(queues = "fanout_queue_sms") public void psubConsumerSms(Message message) { byte[] body = message.getBody(); String s = new String(body); System.out.println("短信业务接收到消息: "+s); } } -
启动项目来监听并接收消息队列中的消息。程序启动成功后,立即查看控制台打印结果

2.3.2 基于配置类的方式
基于配置类的方式主要讲的是使用Spring Boot框架提供的@Configuration注解配置类定制消息发送组件,并进行消息发送。
使用@Bean注解定制了三种类型的 Bean 组件,这三种组件分别表示1个交换器、2个消息队列、2个消息队列分别与交换器的绑定。这种基于配置类方式定制的消息组件内容和基于 API方式定制的消息组件内容完全一样,只不过是实现方式的不同而已。
-
打开RabbitMQ消息配置类RabbitMQConfig,定义消息转换器、fanout类型的交换器、不同名称的消息队列以及将不同名称的消息队列与交换器绑定。
@Bean public MessageConverter messageConverter(){ return new Jackson2JsonMessageConverter(); } //1. 定义fanout类型的交换器 @Bean public Exchange fanout_exchange(){ return ExchangeBuilder.fanoutExchange("fanout_exchange").build(); } //2. 定义两个不同名称的消息队列 @Bean public Queue fanout_queue_email(){ return new Queue("fanout_queue_email"); } @Bean public Queue fanout_queue_sms(){ return new Queue("fanout_queue_sms"); } //3. 将两个不同名称的消息队列与交换器进行绑定 @Bean public Binding bindingEmail(){ return BindingBuilder.bind(fanout_queue_email()).to(fanout_exchange()).with("").noargs(); } @Bean public Binding bindingSms(){ return BindingBuilder.bind(fanout_queue_sms()).to(fanout_exchange()).with("").noargs(); } -
按照消息服务整合实现步骤,完成消息组件的定制后,还需要编写消息发送者和消息消费者,而在基于API的方式中已经实现了消息发送者和消息消费者,并且基于配置类方式定制的消息组件名称和之前测试用的消息发送和消息消费组件名称都是一致的,所以这里可以直接重复使用。
-
打开业务类RabbitMQService,使用@RabbitListener注解及其相关属性定制消息发送组件,在该类中编写处理邮件以及短信任务的方法
-
重新运行消息发送者测试方法 psubPublisher0,消息消费者可以自动监听消费消息队列中存在的消息,效果与基于API的方式测试效果一样。
2.3.3 基于注解的方式及测试
@RabbitListener(bindings = @QueueBinding(value = @Queue("fanout_queue_email"), exchange = @Exchange(value = "fanout_exchange",type = "fanout"))) public void psubConsumerEmailAno(User user) { System.out.println("邮件业务接收到消息2: "+user); } @RabbitListener(bindings =@QueueBinding(value = @Queue("fanout_queue_sms"),exchange = @Exchange(value = "fanout_exchange",type = "fanout"))) public void psubConsumerSmsAno(User user) { System.out.println("短信业务接收到消息2: "+user); }
-
重启测试方法
psubPublisher(),消息消费者可以自动监听并消费消息队列中存在的消息了,效果也与基于API的方式测试效果一样。
使用@RabbitListener注解及其相关属性定制消息组件消费者,改用与发送消息对应的实体类User 作为消息接收参数。在@RabbitListener注解中,使用bindings属性来自动创建并绑定交换器和消息队列组件,在定制交换器时将交换器类型设置为fanout。另外,bindings属性的@QueueBinding注解除了有value、type属性外,还有key属性用于定制路由键routingKey(当前发布订阅模式不需要)。
2.4 案例练习
2.4.1 Routing(路由模式)
① 使用基于注解的方式定制消息组件和消息消费者。
-
打开业务类RabbitMQService,在该类中使用@RabbitListener注解及其相关属性定制Routing路由模式的消息组件,并模拟编写消息消费者接收的方法。
@RabbitListener(bindings =@QueueBinding(value = @Queue("routing_queue_error"),exchange = @Exchange(value = "routing_exchange",type = "direct"), key = "error_routing_key")) public void routingConsumerError(String message) { System.out.println("接收到error级别日志消息: "+message); } @RabbitListener(bindings =@QueueBinding(value = @Queue("routing_queue_all"),exchange = @Exchange(value = "routing_exchange",type = "direct"), key = {"error_routing_key","info_routing_key","warning_routing_key"})) public void routingConsumerAll(String message) { System.out.println("接收到info、error、warning等级别日志消息: "+message); }消息发送者发送消息
-
在测试类中使用RabbitTemplate模板类实现Routing路由模式下的消息发送。
@Test public void routingPublisher() { rabbitTemplate.convertAndSend("routing_exchange", "error_routing_key", "routing send error message"); } -
将测试方法routingPublisher()中进行消息发送的参数进行修改,调整发送info级别的日志信息(注意同时修改info_routing_key路由键),再次启动该测试方法,查看控制台执行效果,如图

-
通过RabbitMQ可视化管理页面查看自动定制的Routing路由模式的消息组件,如图所示

2.4.2 Topics(通配符模式)
① 使用基于注解的方式定制消息组件和消息消费者
-
打开业务类RabbitMQService,在该类中使用@RabbitListener注解及其相关属性定制Topics通配符模式的消息组件,并模拟编写消息消费者接收的方法。
@RabbitListener(bindings =@QueueBinding(value = @Queue("topic_queue_email"),exchange = @Exchange(value = "topic_exchange",type = "topic"), key = "info.#.email.#")) public void topicConsumerEmail(String message) { System.out.println("接收到邮件订阅需求处理消息: "+message); } @RabbitListener(bindings =@QueueBinding(value = @Queue("topic_queue_sms"),exchange = @Exchange(value = "topic_exchange",type = "topic"), key = "info.#.sms.#")) public void topicConsumerSms(String message) { System.out.println("接收到短信订阅需求处理消息: "+message); }② 消息发送者发送消息
-
在项目测试类Chapter08ApplicationTests中使用RabbitTemplate模板类实现Routing路由模式下的消息发送。
-
@Test public void topicPublisher() { rabbitTemplate.convertAndSend("topic_exchange","info.email", "topics send email message"); rabbitTemplate.convertAndSend("topic_exchange","info.sms", "topics send sms message"); rabbitTemplate.convertAndSend("topic_exchange", "info.email.sms", "topics send email and sms message"); }③效果测试
-
执行测试方法topicPublisher(),先进行邮件订阅用户的消息发送

-
执行测试方法topicPublisher(),再进行短信订阅用户的消息发送
-
同时进行邮件和短信订阅用户的消息发送方法
-

-
通过RabbitMQ可视化管理页面查看自动定制的Topics通配符模式的消息组件,使用基于注解的方式自动生成了Topics通配符模式下的消息组件,并进行了自动绑定。

Spring Boot 任务管理
一、任务管理简介
Spring Boot 任务管理是指使用Spring Boot框架来管理和调度应用程序中的任务。任务可以是定时任务、异步任务或周期性任务,通过任务管理可以实现任务的调度、执行和监控。通过使用Spring Boot任务管理,可以更好地组织和管理应用程序中的任务,提高应用程序的效率、可靠性和可维护性。
以下是一些常见的任务管理:
-
异步任务:某些任务可能需要在后台执行,以避免阻塞主线程。通过任务管理,可以将这些任务提交给线程池或异步执行器,从而提高应用程序的响应性能。
-
定时任务:某些任务需要按照特定的时间间隔或时间点执行,例如数据备份、报表生成等。通过任务管理,可以方便地配置和管理这些定时任务,确保它们按照预定的时间执行。
-
周期性任务:某些任务需要按照一定的周期或规律执行,例如定期清理过期数据、定期更新缓存等。通过任务管理,可以方便地配置和管理这些周期性任务,确保它们按照预定的规则执行。
-
监控和管理:任务管理还可以提供任务的监控和管理功能,包括任务的执行状态、执行结果、执行日志等信息。这些信息可以帮助开发人员快速定位和解决任务执行过程中的问题。
二、异步任务
异步任务是指在程序执行过程中,将某些耗时的操作交给其他线程或进程来处理,以提高程序的响应性能和并发能力。异步任务通常与主线程(或主进程)并行执行,不会阻塞主线程的执行。
在传统的同步任务中,程序会按照顺序依次执行每个操作,如果某个操作需要等待某个耗时的任务完成,那么整个程序会被阻塞,直到该任务完成才能继续执行后续操作。这种方式可能导致程序响应缓慢,无法同时处理多个请求或任务。
而异步任务则可以将耗时的操作委托给其他线程或进程来处理,主线程可以继续执行后续操作,不需要等待耗时任务的完成。当耗时任务完成后,可以通过回调、轮询或事件通知等方式来获取任务的执行结果。
异步任务常见的应用场景包括:
-
网络请求:在进行网络请求时,可以将请求发送到其他线程或进程中处理,主线程可以继续响应其他请求或处理其他任务,提高系统的并发能力和吞吐量。
-
文件操作:对于大文件的读写、复制、压缩等操作,可以将这些操作交给其他线程或进程来处理,主线程可以继续执行其他操作,提高程序的响应速度。
-
数据库查询:对于复杂的数据库查询操作,可以将查询任务交给其他线程或进程来执行,主线程可以继续处理其他请求或任务,提高系统的并发性能。
-
图像处理:对于图像处理、特征提取等耗时的操作,可以将这些操作交给其他线程或进程来处理,主线程可以继续进行其他计算或显示操作,提高程序的响应性能。
2.1 无返回值异步任务调用
① Spring Boot 项目创建
创建一个Spring Boot项目,选择Web模块中的Web依赖。
② 编写异步调用方法
创建一个业务实现类MyAsyncService,在该类中模拟编写用户短信验证码发送的方法。
@Service
public class MyAsyncService {
@Async
public void sendSMS() throws Exception {
System.out.println("调用短信验证码业务方法...");
Long startTime = System.currentTimeMillis();
Thread.sleep(5000);
Long endTime = System.currentTimeMillis();
System.out.println("短信业务执行完成耗时:" + (endTime - startTime));
}
}
③ 开启基于注解的异步任务支持
@EnableAsync
@SpringBootApplication
public class Chapter09Application {
public static void main(String[] args) {
SpringApplication.run(Chapter09Application.class, args);
}
}
④ 编写控制层业务调用方法
创建一个异步方法调用的实现类MyAsyncController,在该类中模拟编写用户短信验证码发送的处理方法
@Autowired
private MyAsyncService myService;
@GetMapping("/sendSMS")
public String sendSMS() throws Exception {
Long startTime = System.currentTimeMillis();
myService.sendSMS();
Long endTime = System.currentTimeMillis();
System.out.println("主流程耗时: "+(endTime-startTime));
return "success";
}
⑤ 异步任务效果测试
启动主程序启动类,浏览器上访问http://localhost:8080/sendSMS
注意:上述异步方法是没有返回值的,这样主流程在执行异步方法时不会阻塞,而是继续向下执行主流程程序,直接向页面响应结果,而调用的异步方法会作为一个子线程单独执行,直到异步方法执行完成。
2.2 有返回值异步任务调用
① 编写异步调用方法
在MyAsyncService业务处理类中,添加两个模拟有返回值的异步任务业务处理方法
//ProcessA
@Async
public Future<Integer> processA() throws Exception {
System.out.println("开始分析并统计业务A数据...");
Long startTime = System.currentTimeMillis();
Thread.sleep(4000);
int count=123456;
Long endTime = System.currentTimeMillis();
System.out.println("业务A数据统计耗时:" + (endTime - startTime));
return new AsyncResult<Integer>(count);
}
//ProcessB
@Async
public Future<Integer> processB() throws Exception {
System.out.println("开始分析并统计业务B数据...");
Long startTime = System.currentTimeMillis();
Thread.sleep(5000);
int count=654321;
Long endTime = System.currentTimeMillis();
System.out.println("业务B数据统计耗时:" + (endTime - startTime));
return new AsyncResult<Integer>(count);
}
② 编写控制层业务调用方法
在MyAsyncController业务处理类中,编写业务数据分析统计的请求处理方法
@GetMapping("/statistics")
public String statistics() throws Exception {
Long startTime = System.currentTimeMillis();
Future<Integer> futureA = myService.processA();
Future<Integer> futureB = myService.processB();
int total = futureA.get() + futureB.get();
System.out.println("异步任务数据统计汇总结果: "+total);
Long endTime = System.currentTimeMillis();
System.out.println("主流程耗时: "+(endTime-startTime));
return "success";
}
③ 异步任务效果测试
启动主程序启动类,在浏览器上访问http://localhost:8080/statistics
注意:上述异步方法是有返回值的,这样主流程在执行异步方法时会有短暂阻塞,需要等待并获取异步方法的返回结果,而调用的两个异步方法会作为两个子线程并行执行,直到异步方法执行完成并返回结果,这样主流程会在最后一个异步方法返回结果后跳出阻塞状态。
三、定时任务
定时任务是一种很常见的应用场景,可以设置每隔一段时间或在精准到秒的准确时间去自动执行方法。定时任务最早使用Java自带的java.util.Timer类和TimerTask类来实现,java.util.Timer类用来执行任务,接收一个TimerTask参数;或者使用Spring提供的注解@Schedule,如果定时任务执行时间较短,并且比较单一,可以使用这个注解来实现;还可以使用开源的第三方框架Quartz,可以根据需要对定时任务进行多实例部署,该方式多用于分布式场景下的定时任务。
使用注解实现定时任务的方式,简单、方便,主要使用@EnableScheduling注解和@Scheduled注解。而Quartz则提供了丰富的任务调度功能,可以定制周期性运行的任务调度方案。
3.1 定时任务介绍
3.1.1 @EnableScheduling
@EnableScheduling 是一个在 Spring Boot 应用程序中启用定时任务的注解。通过在主类上添加该注解,可以启用 Spring 的任务调度功能,允许使用 @Scheduled 注解来定义定时任务。
3.1.2 @Scheduled
@Scheduled注解是用于标记一个方法作为定时任务的执行体。该注解可以用在方法上,指示方法应按照指定的时间表执行。
通过@Scheduled注解,可以设置任务的触发时间、执行频率等属性。具体的属性包括:
-
fixedDelay:表示任务执行结束后的延迟时间(以毫秒为单位),下一次任务将在当前任务完成后再次启动。 -
fixedRate:表示任务开始执行的间隔时间(以毫秒为单位),无论前一个任务是否完成。 -
initialDelay:表示首次任务执行的延迟时间(以毫秒为单位)。 -
cron:使用 Cron 表达式来定义更复杂的任务触发时间规则。
Cron 表达式是一种用于表示定时任务执行时间的格式化字符串。它由6个或7个部分组成,每个部分代表任务执行的不同时间维度,包括分钟、小时、日、月、周、年等。以下是一个典型的 Cron 表达式:
https://cron.ciding.cc/
Copy Code
* * * * * *
| | | | | |
| | | | | +--- 年 (可选字段)
| | | | +------- 星期 (0-7,0和7都表示周日)
| | | +------------ 月份 (1-12)
| | +----------------- 日 (1-31)
| +---------------------- 小时 (0-23)
+--------------------------- 分钟 (0-59)
-
n1-n2:表示一个范围内的数值,例如1-5表示1到5。
例如,以下 Cron 表达式表示每天早上6点执行任务:0 6 * * 。而这个 Cron 表达式表示每月最后一天早上7点执行任务:0 7 L *。
需要注意的是,在不同的应用场景中,Cron 表达式的使用可能还会存在一些差异,具体规则需要根据实际情况进行调整。
在Cron表达式中,星期的数字对应如下:
-
1:星期天(或者可以使用0来表示)
-
2:星期一
-
3:星期二
-
4:星期三
-
5:星期四
-
6:星期五
-
7:星期六
3.2 定时任务实现
① 编写定时任务业务处理方法
新建一个定时任务管理的业务处理类ScheduledTaskService,并在该类中编写对应的定时任务处理方法。使用@Scheduled注解声明了三个定时任务方法,这三个方法定制的执行规则基本相同,都是每隔1分钟重复执行一次定时任务,在使用fixedDelay属性的方法scheduledTaskAfterSleep()中,使用Thread.sleep(10000)模拟该定时任务处理耗时为10秒钟。
private static final SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private Integer count1 = 1;
private Integer count2 = 1;
private Integer count3 = 1;
@Scheduled(fixedRate = 60000)
public void scheduledTaskImmediately() {
System.out.println(String.format("fixedRate第%s次执行,当前时间为:%s",
count1++, dateFormat.format(new Date())));
}
@Scheduled(fixedDelay = 60000)
public void scheduledTaskAfterSleep() throws InterruptedException {
System.out.println(String.format("fixedDelay第%s次执行,当前时间为:%s",
count2++, dateFormat.format(new Date())));
Thread.sleep(10000);
}
@Scheduled(cron = "0 * * * * *")
public void scheduledTaskCron(){
System.out.println(String.format("cron第%s次执行,当前时间为:%s",
count3++, dateFormat.format(new Date())));
}
② 开启基于注解的定时任务支持
@EnableScheduling
@EnableAsync
@SpringBootApplication
public class Chapter09Application {
public static void main(String[] args) {
SpringApplication.run(Chapter09Application.class, args);
}
}
③ 定时任务效果测试
结论:配置@Scheduled注解的fixedRate和fixedDelay属性的定时方法会立即执行一次,配置cron属性的定时方法会在整数分钟时间点首次执行;接着,配置fixedRate和cron属性的方法会每隔1分钟重复执行一次定时任务,而配置fixedDelay属性的方法是在上一次方法执行完成后再相隔1分钟重复执行一次定时任务。
四、邮件任务
4.1 发送纯文本邮件
① 添加邮件服务的依赖启动器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
注: 当添加上述依赖后,Spring Boot自动配置的邮件服务会生效,在邮件发送任务时,可以直接使用Spring框架提供的JavaMailSender接口或者它的实现类JavaMailSenderImpl邮件发送。
② 添加邮件服务配置
# 发件人邮服务器相关配置
spring.mail.host=smtp.qq.com
spring.mail.port=587
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
mail.debug=true
# 配置个人QQ账户和密码(密码是加密后的授权码)
spring.mail.username=2127269781@qq.com(这个修改为你的qq邮箱)
spring.mail.password=ijdjokspcbnzfbfa(这个修改为你的邮箱授权码)
spring.mail.default-encoding=UTF-8
# 邮件服务超时时间配置
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=3000
spring.mail.properties.mail.smtp.writetimeout=5000
邮箱授权码获取方式:
①点击设置→再点击账号
②下滑找到POP3服务,点击开启服务,若显示服务已开启,点击继续获取授权码,短信验证后即可获得授权码

③ 定制邮件发送服务
新建一个邮件发送任务管理的业务处理类SendEmailService,编写了一个发送纯文本邮件的sendSimpleEmail()方法,在该方法中通过SimpleMailMessage类定制了邮件信息的发件人地址(From)、收件人地址(To)、邮件标题(Subject)和邮件内容(Text),最后使用JavaMailSenderImpl的send()方法实现纯文本邮件发送。
@Service
public class SendEmailService {
@Autowired
private JavaMailSenderImpl mailSender;
@Value("${spring.mail.username}")
private String from;
public void sendSimpleEmail(String to,String subject,String text){
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(to);
message.setSubject(subject);
message.setText(text);
try { mailSender.send(message);
System.out.println("纯文本邮件发送成功");
} catch (MailException e) {
System.out.println("纯文本邮件发送失败 "+e.getMessage());
e.printStackTrace();
}
}
}
④ 纯文本邮件发送效果测试
先定制了纯文本邮件发送方法所需要的参数(示例中定制了给自己邮箱发送邮件),然后调用业务方法实现了纯文本邮件发送。
@SpringBootTest
class Demo10ApplicationTests {
@Autowired
private SendEmailService sendEmailService;
@Test
public void sendSimpleMailTest() {
String to="707333732@qq.com";//你想要发送到的邮箱地址
String subject="【纯文本邮件】标题";
String text="Spring Boot纯文本邮件发送内容测试.....";
sendEmailService.sendSimpleEmail(to,subject,text);
}
}
直接启动单元测试方法sendSimpleMailTest()

4.2 发送带附件和图片的邮件
① 定制邮件发送服务
打开之前创建的邮件发送任务的业务处理类SendEmailService,在该类中编写一个发送带附件和图片邮件的业务方法sendComplexEmail() ,该方法需要接收的参数除了基本的发送信息外,还包括静态资源唯一标识、静态资源路径和附件路径。
public void sendComplexEmail(String to,String subject,String text,String filePath,String rscId,
String rscPath){
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text, true);
FileSystemResource res = new FileSystemResource(new File(rscPath));
helper.addInline(rscId, res);
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);
mailSender.send(message);
System.out.println("复杂邮件发送成功");
} catch (MessagingException e) {
System.out.println("复杂邮件发送失败 "+e.getMessage());
e.printStackTrace();
}
}
② 邮件发送效果测试
在项目测试类中添加一个方法调用带附件和图片的复杂邮件发送的方法实现邮件发送效果测试,根据前面定义的复杂邮件发送业务方法定制了各种参数。其中,在定义邮件内容时使用了Html标签编辑邮件内容,并内嵌了一个标识为rscId的图片,并为邮件指定了携带的附件路径。在邮件发送之前,务必保证指定路径下存放有对应的静态资源和附件文件。
@Test
public void sendComplexEmailTest() {
String to="707333732@qq.com";
String subject="【复杂邮件】标题";
StringBuilder text = new StringBuilder();
text.append("<html><head></head>");
text.append("<body><h1>祝大家身体健康!</h1>");
String rscId = "img001";
text.append("<img src='cid:" +rscId+"'/></body>");
text.append("</html>");
String rscPath="F:\\email\\newyear.jpg";
String filePath="F:\\email\\_祝福语.txt";
sendEmailService.sendComplexEmail(to,subject,text.toString(),
filePath,rscId,rscPath);}

4.3 发送模板邮件
① 添加Thymeleaf模板引擎依赖启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
② 定制模板邮件,添加发送用户注册验证码的模板页面(sign_up.html)
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>用户验证码</title>
</head>
<body>
<div><span th:text="${username}">XXX</span> 先生/女士,您好:</div>
<P style="text-indent: 2em">您的新用户验证码为
<span th:text="${code}" style="color: cornflowerblue">123456</span>,请妥善保管。
</P>
</body>
</html>
③ 定制邮件发送服务
在业务处理类SendEmailService中编写一个发送Html模板邮件的业务方法,sendTemplateEmail()方法主要用于处理Html内容(包括Thymeleaf邮件模板)的邮件发送,在定制Html模板邮件信息时,使用了MimeMessageHelper类对邮件信息进行封装处理。
public void sendTemplateEmail(String to, String subject, String content) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
mailSender.send(message);
System.out.println("模板邮件发送成功");
} catch (MessagingException e)
{System.out.println("模板邮件发送失败 "+e.getMessage());
e.printStackTrace();}
}
④ 模板邮件发送效果测试
在项目测试类中添加一个方法调用前面编写的Html模板邮件发送方法测试邮件发送效果,先使用@Autowired注解引入了Thymeleaf提供的模板引擎解析器TemplateEngine,然后定制了模板邮件发送所需的参数。
@Autowired
private TemplateEngine templateEngine;
@Test
public void sendTemplateEmailTest() {
String to="707333732@qq.com";
String subject="【模板邮件】标题";
Context context = new Context();
context.setVariable("username", "石头");
context.setVariable("code", "456123");
String emailContent = templateEngine.process("sign_up", context);
sendEmailService.sendTemplateEmail(to,subject,emailContent);
}

五、Quartz
Quartz框架是一个开源项目,完全用Java开发,它是一种功能丰富、开放源码的作业调度框架。
可以创建简单或复杂的日程,安排执行几十、几百、甚至是上万的作业数,作业被定义为标准的Java组件,可以编程让它们执行几乎任何东西。 Quartz框架具有强大的调度功能,包括许多企业级功能,如JTA事务和集群支持;还提供了调度运行环境的持久化机制,可以保存并恢复调度现场,即使系统因故障关闭,任务调度现场数据并不会丢失。
Quartz的工作过程可以这样理解:给它一个触发条件的定义,它负责到了时间点,触发执行相应的组件。简单来讲,它就是一个定时器,帮用户设置在某一有规律的时间点做想做的事。
以火车票购票为例,当用户下单购票后,后台就会插入一条待支付的任务(job),若用户未及时支付一般等待30分钟,超过30分钟后就会执行这个job,判断用户是否支付,未支付就会取消此次订单;当用户完成支付后,后台会再插入一条待消费的任务,job触发日期为火车票上的出发日期,超过这个时间就会执行这个job,判断是否使用等。
5.1 Quartz框架-核心要素
首先要有一个定时执行的任务(Job),描述具体的业务逻辑,如定时发送邮件任务、重启机器的任务、优惠券到期发送短信提醒的任务等; 然后是需要一个触发任务去执行的触发器(Trigger),触发器最基本的功能是指定Job的执行时间、执行间隔、运行次数等; 最后通过调度器(Scheduler)将Job和Trigger结合起来,即指定Trigger去执行指定的Job。Scheduler是整个框架的心脏和灵魂。
5.2 Spring Boot整合Quartz
<!--Spring Boot集成quartz-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
5.2.1 任务Job
Job是Quartz框架的任务,接口比较简单,是由完成业务逻辑的任务组件实现的接口,其中只有一个方法:void execute(JobExecutionContext context):,开发者通过该接口定义任务,在这个方法中编写业务逻辑。该方法的参数是JobExecutionContext,包含了Quartz运行时的环境以及Job本身的详细数据信息。
开发者实现该接口定义任务,在这个方法中编写业务逻辑,打印当前的执行时间。
/**
* 工作类,实现Job接口
*/
public class PrintJob implements Job {
//执行的工作代码,编写具体的业务逻辑
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//打印当前的执行时间,格式为:2000-01-01 00:00:00
Date date = new Date();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("当前的时间为:" + sf.format(date));
System.out.println("Hello PrintJob!");
}
}
JobDetail:就是用来绑定Job,为Job实例提供任务相关属性,如:Job名字、描述、关联监听器等信息。JobDetail定义的是任务数据,而真正的执行逻辑是在Job中。 JobDetail实例可以通过JobExecutionContext或JobBuilder类创建。 JobDataMap:实现了JDK的Map接口,可以以Key-Value的形式存储数据,在Job实例执行时,可以使用其中的数据。JobDetail、Trigger都可以使用JobDataMap来设置一些参数或信息。
Job运行时的信息保存在JobDataMap实例中,在需要向Job传值的时候就可以通过JobDataMap传入。
5.2.2 触发器(Trigger)
Trigger是Quartz的触发器,会通知Scheduler(调度器)何时去执行对应Job。Trigger描述了触发Job执行的时间规则,当准备调度一个 job 时,创建一个 Trigger 的实例,然后设置调度相关的属性即可。 一个Trigger只能绑定一个Job,但一个Job可以被多个Trigger绑定。Trigger也有相关联的 JobDataMap,用于给 Job 传递一些触发相关的参数。 Quartz有很多类型的触发器,所有类型的Trigger都有TriggerKey属性,表示Trigger的身份,还有很多其它的公共属性,在构建Trigger的时候可以通过TriggerBuilder设置,如
-
startTime属性表示Trigger第一次触发的时间;
-
endTime属性表示Trigger失效的时间点。
常用的触发器有SimpleTrigger和CronTrigger这两个子类:
SimpleTrigger可以实现在具体的时间点执行一次的任务或者在具体的时间点执行且以指定的间隔重复执行若干次的任务。如有一个Trigger,可以设置在2025年11月1日的上午12点准时触发,或者在这个时间点触发且每隔10秒触发一次,重复5次。
SimpleTrigger的属性主要包括:开始时间、结束时间、重复次数以及重复的间隔。重复次数,可以是0、正整数,以及常量SimpleTrigger.REPEAT_INDEFINITELY。重复的间隔,必须是0,或者long型的正数,表示毫秒。
SimpleTrigger trigger1 = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1") //表示触发器的一些属性名和组名
.startAt(myStartTime) // myStartTime开始触发的时间
.simpleSchedule()
.withSchedule(SimpleScheduleBuilder.withIntervalInSeconds(5))
.build();
-
withIdentity表示触发器的一些属性名和组名,并由此形成标识。
-
startAt表示启动时间。
-
withSchedule表示以某种触发器触发。
CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案。
CronTrigger可以通过cron表达式定义出各种复杂的调度方案。使用CronTrigger,可以指定好时间表,CronTrigger 支持日历相关的重复时间间隔(如每月第一个周一执行),而不是简单的周期时间间隔。如每天早晨9点执行、每周五上午12点执行等。
CronTrigger实例使用TriggerBuilder(用于触发器的主要属性)和CronScheduleBuilder(对于CronTrigger特定的属性)构建。
Trigger trigger3 = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 9-17 * * ?"))
.forJob("myJob", "group1")
.build();
Copy Code
* * * * * *
| | | | | |
| | | | | +--- 年 (可选字段)
| | | | +------- 星期 (0-7,0和7都表示周日)
| | | +------------ 月份 (1-12)
| | +----------------- 日 (1-31)
| +---------------------- 小时 (0-23)
+--------------------------- 分钟 (0-59)
5.2.3 调度器(Scheduler)
Scheduler 是Quartz的调度器,是Quartz框架的心脏,用来管理触发器和Job,并保证Job能被触发执行。
Trigger和JobDetail可以注册到Scheduler中,一个Scheduler可以拥有多个Trigger组和多个JobDetail组。
注册不同对象时,对象在Scheduler中拥有各自的组名及名称,组名及名称是Scheduler查找定位容器中某一对象的依据,组名和名称组成了对象的全名,同一类型对象的全名不能相同。如果不显示指定所属的组,Schedule将被放入默认组中,默认组组名为Scheduler.DEFAULT_GROUP。
Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。常用的有:
-
Date scheduleJob(JobDetail jobDetail, Trigger trigger):绑定jobDetail 和 trigger,将它们注册进 Scheduler 中,返回值是最近一次任务执行的开始时间。
-
void start():启动Scheduler
-
void standby():暂停Scheduler
-
void shutdown():完全关闭Scheduler,不能再重启
Scheduler接口有两个实现类,分别为标准默认调度器-StdScheduler和远程调度器-RemoteScheduler,Scheduler实例的创建主要通过工厂模式,最常用的是
StdSchedulerFactory工厂类,主要有两种创建Scheduler的方式。//方式 一: SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); //方式二: Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
5.2.4 案例练习
Spring Boot整合Quartz框架,设置每周五上午10:00发送欢迎语。
①创建工程,引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
② 创建任务类
在项目中创建包jobs,并创建任务类HelloJob。
/**
* 定时任务类,描述需要定时执行的具体业务逻辑
*/
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//获取JobDetail对象 存放了任务相关的信息
JobDetail jobDetail = context.getJobDetail();
//获取JobDataMap对象
JobDataMap jobDataMap = jobDetail.getJobDataMap();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E");
System.out.print(jobDataMap.get("depart") + "-----" + jobDataMap.get("username") +
"现在时间是:" + sf.format(new Date()) + ",愉快的周末马上开始啦!-----------");
System.out.println(Thread.currentThread().getName());
}
}
③ 创建调度类
创建包quartz,在包中创建调度类TriggerRunner。
public class TriggerRunner {
public static void main(String[] args) throws SchedulerException {
// JobDetail:用来绑定Job,并且在job执行的时候携带一些执行的信息
//创建一个JobDetail实例,将该实例与HelloJob Class绑定
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("myJob","group1") //指定Job的名字
.usingJobData("username","张三") //存储用户名
.build();
//存储数据
JobDataMap jobDataMap=jobDetail.getJobDataMap();
jobDataMap.put("depart","销售部");
//Trigger:用来触发job去执行的,包括定义了什么时候去执行,第一次执行,是否会一直重复地执行下去,执行几次等
//创建一个Trigger实例,定义该job立即执行,并且每隔2秒钟重复执行一次,重复三次
Trigger trigger = TriggerBuilder.newTrigger()
// .withIdentity("myTrigger1","group1")
// .startNow()
// .withSchedule(SimpleScheduleBuilder.simpleSchedule()
// .withIntervalInSeconds(2).withRepeatCount(3)) //使用SimpleSchedule,每2秒调度一次,执行三次
// .build();
.withIdentity("myTrigger2","group1")
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 12 ? * FRI"))
.build();
// .withSchedule(CronScheduleBuilder.cronSchedule("0 0 9 ? * MON"))
//创建scheduler实例:scheduler区别于trigger和jobDetail,是通过factory模式创建的
//创建一个ScheduleFactory
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
//创建一个调度器Scheduler
Scheduler scheduler = schedulerFactory.getScheduler();
//注入jobDetail和trigger到调度器中
scheduler.scheduleJob(jobDetail,trigger);
//开始调度
scheduler.start();
// scheduler.shutdown();
// scheduler.start();
}
}
④启动TriggerRunner
调整计算机时间,运行TriggerRunner启动类

87

被折叠的 条评论
为什么被折叠?



