原文:Pro Spring Boot 2
协议:CC BY-NC-SA 4.0
九、Spring Boot 的通信
这一章是关于信息传递的。它通过示例解释了如何使用 ActiveMQ 实现 JMS (Java 消息服务),使用 RabbitMQ 实现 AMQP(高级消息队列协议),使用 Redis 实现发布/订阅,使用 WebSockets 实现 STOMP(简单或面向流文本的消息协议)和 Spring Boot。
什么是消息传递?
消息传递是一种在一个或多个实体之间进行通信的方式,它无处不在。
自从计算机发明以来,各种形式的计算机信息就一直存在。它被定义为硬件和/或软件组件或应用之间的一种通信方法。总是有一个发送者和一个或多个接收者。消息传递可以是同步和异步的、发布/订阅和对等的、RPC 的、基于企业的、消息代理、ESB(企业服务总线)、MOM(面向消息的中间件)等等。
消息传递支持必须松散耦合的分布式通信,这意味着无论发送方如何发布消息或发布什么消息,接收方都会在不通知发送方的情况下使用消息。
当然,关于消息传递,我们可以说很多——从旧的技巧和技术到新的协议和消息传递模式,但本章的目的是用例子来说明 Spring Boot 是如何进行消息传递的。
记住这一点,让我们开始使用现有的一些技术和消息代理来创建示例。
与 Spring Boot 的 JMS
让我们从使用 JMS 开始。这是一项老技术,仍被有遗留应用的公司使用。JMS 是由 Sun Microsystems 创建的,它提供了一种同步和异步发送消息的方法;它定义了需要由消息代理实现的接口,比如 WebLogic、IBM MQ、ActiveMQ、HornetQ 等等。
JMS 是一种只支持 Java 的技术,因此有人试图创建消息桥来将 JMS 与其他编程语言结合起来;尽管如此,混合不同的技术还是很困难或者非常昂贵。我知道您认为这是不正确的,因为您可以使用 Spring integration、Google Protobuffers、Apache Thrift 和其他技术来集成 JMS,但是这仍然需要大量的工作,因为您需要了解和维护所有这些技术的代码。
带有 JMS 的待办事项应用
让我们从使用 JMS 和 Spring Boot 创建 ToDo 应用开始。想法是将 ToDo 发送到 JMS 代理,并接收和保存它们。
Spring Boot 团队有几个可用的 JMS 初学者 poms 在这种情况下,您使用 ActiveMQ,它是 Apache 基金会的一个开源异步消息传递代理( http://activemq.apache.org
)。其中一个主要优势是,您可以使用内存中的代理或远程代理。(如果喜欢可以下载安装;本节中的代码使用内存中的代理,但是我将告诉您如何配置远程代理。
可以打开自己喜欢的浏览器,指向已知的 Spring Initializr(https://start.spring.io
);将下列值添加到下列字段中。
-
组:
com.apress.todo
-
神器:
todo-jms
-
名称:
todo-jms
-
包名:
com.apress.todo
-
依赖关系:
JMS (ActiveMQ), Web, Lombok, JPA, REST Repositories, H2, MySQL
您可以选择 Maven 或 Gradle 作为项目类型。然后你可以点击生成项目按钮来下载一个 ZIP 文件。将其解压缩并在您最喜欢的 IDE 中导入项目(参见图 9-1 )。
图 9-1
Spring 初始化 zr
从依赖关系中可以看出,您重用了前面章节中的 JPA 和 REST Repositories 代码。代替使用文本消息(一种测试消息传递的常用方法),您使用一个ToDo
实例,它被转换为 JSON 格式。为此,您需要手动将下一个依赖项添加到您的pom.xml
或build.gradle
中。
如果您使用的是 Maven,将下面的依赖项添加到您的pom.xml
文件中。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
如果您使用的是 Gradle,将下面的依赖项添加到您的build.gradle
文件中。
compile("com.fasterxml.jackson.core:jackson-databind")
- 1
- 2
这个依赖项提供了使用 JSON 序列化ToDo
实体所需的所有 Jackson jars。
在接下来的部分中,我将向您展示重要的文件,以及 JMS 是如何在 ToDo 应用中使用的。该示例使用简单的点对点模式,其中有一个生产者、一个队列和一个消费者。稍后我将展示如何配置它来使用一个带有一个生产者、一个主题和多个消费者的发布者-订阅者模式。
ToDo 生产者
让我们从介绍向 ActiveMQ 代理发送 ToDo 的生产者开始。这个生产者可以在自己的项目上;可以脱离 app 但是出于演示的目的,在 ToDo 应用中,您将生成器放在相同的代码库中。
创建ToDoProducer
类。这个类将一个 ToDo 发送到一个 JMS 队列中(参见清单 9-1 )。
package com.apress.todo.jms;
import com.apress.todo.domain.ToDo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
@Component
public class ToDoProducer {
private static final Logger log = LoggerFactory.getLogger(ToDoProducer.class);
private JmsTemplate jmsTemplate;
public ToDoProducer(JmsTemplate jmsTemplate){
this.jmsTemplate = jmsTemplate;
}
public void sendTo(String destination, ToDo toDo) {
this.jmsTemplate.convertAndSend(destination, toDo);
log.info("Producer> Message Sent");
}
}
Listing 9-1com.apress.todo.jms.ToDoProducer.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
清单 9-1 显示了生产者类。这个类使用@Component
进行标记,因此它在 Spring 应用上下文中被注册为一个 Spring bean。使用了JmsTemplate
类,它非常类似于其他的*Template
类,这些类包装了所有正在使用的技术的样板文件。通过类构造函数注入JmsTemplate
实例,并使用convertAndSend
方法发送消息。您正在发送一个 ToDo 对象(JSON 字符串)。该模板具有将其序列化并发送到 ActiveMQ 队列的机制。
ToDo 消费者
接下来,让我们创建消费者类,它监听来自 ActiveMQ 队列的任何传入消息(参见清单 9-2 )。
package com.apress.todo.jms;
import com.apress.todo.domain.ToDo;
import com.apress.todo.repository.ToDoRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.validation.Valid;
@Component
public class ToDoConsumer {
private Logger log = LoggerFactory.getLogger(ToDoConsumer.class);
private ToDoRepository repository;
public ToDoConsumer(ToDoRepository repository){
this.repository = repository;
}
@JmsListener(destination = "${todo.jms.destination}",containerFactory = "jmsFactory")
public void processToDo(@Valid ToDo todo){
log.info("Consumer> " + todo);
log.info("ToDo created> " + this.repository.save(todo));
}
}
Listing 9-2com.apress.todo.jms.ToDoConsumer.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
清单 9-2 显示了消费者。在这个类中,您使用的是ToDoRepository
,它在这里监听来自 ActiveMQ 队列的任何消息。确保您使用的是使该方法处理来自队列的任何传入消息的@JmsListener
注释;在这种情况下,一个有效的 ToDo(@Valid
注释可以用来验证域模型的任何字段)。@JmsListener
注释有两个属性。destination
属性强调要连接的队列/主题的名称(目的地属性评估todo.jms.destination
属性,该属性将在下一节中创建/使用)。属性是作为配置的一部分创建的。
配置待办事项应用
现在,是时候配置 ToDo 应用来发送和接收 ToDo 了。清单 9-1 和清单 9-2 分别显示了生产者和消费者类。在这两个类中都使用了一个ToDo
实例,这意味着有必要进行序列化。大多数使用序列化的 Java 框架要求您的类从java.io.Serializable
开始实现。将这些类转换成字节是一种简单的方法,但是这种方法已经争论了很多年,因为实现Serializable
降低了在发布使用后修改类实现的灵活性。
Spring 框架提供了另一种不需要从Serializable
开始实现序列化的方法——通过一个MessageConverter
接口。这个接口提供了toMessage
和fromMessage
方法,您可以在其中插入任何适合对象转换的技术。
让我们为生产者和消费者创建一个使用ToDo
实例的配置(参见清单 9-3 )。
package com.apress.todo.config;
import com.apress.todo.error.ToDoErrorHandler;
import com.apress.todo.validator.ToDoValidator;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.JmsListenerConfigurer;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerEndpointRegistrar;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import javax.jms.ConnectionFactory;
@Configuration
public class ToDoConfig {
@Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_class_");
return converter;
}
@Bean
public JmsListenerContainerFactory<?> jmsFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setErrorHandler(new ToDoErrorHandler());
configurer.configure(factory, connectionFactory);
return factory;
}
@Configuration
static class MethodListenerConfig implements JmsListenerConfigurer{
@Override
public void configureJmsListeners (JmsListenerEndpointRegistrar jmsListenerEndpointRegistrar){
jmsListenerEndpointRegistrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
}
@Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory () {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(new ToDoValidator());
return factory;
}
}
}
Listing 9-3com.apress.todo.config.ToDoConfig.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
清单 9-3 显示了应用使用的ToDoConfig
类。我们来分析一下。
-
@Configuration
。这是一个已知的注释,它标记了用于配置 SpringApplication 上下文的类。 -
MessageConverter
。方法jacksonJmsMessageConverter
返回 MessageConverter 接口。这个接口促进了toMessage
和fromMessage
方法的实现,这有助于插入您想要使用的任何序列化/转换。在这种情况下,您通过使用MappingJackson2MessageConverter
类实现来使用 JSON 转换器。这个类是 Spring 框架中的默认实现之一。它使用 Jackson 库,这些库使用映射器在 JSON 和对象之间进行转换。因为您使用的是ToDo
实例,所以有必要指定一个目标类型(setTargetType
),这意味着 JSON 对象被作为文本和一个 type-id 属性名(setTypeIdPropertyName
)来处理,该属性名标识了生产者和消费者之间的属性。type-id 属性名必须始终与生产者和消费者相匹配。它可以是你需要的任何值(最好是你能识别的值,因为它用于设置要与 JSON 相互转换的类的名称(包括包));换句话说,com.apress.todo.domain.Todo
类必须在生产者和消费者之间共享,以便映射器知道从哪里获取该类。 -
JmsListenerContainerFactory
。jmsFactory
方法返回JmsListenerContainerFactory
。这个 bean 需要ConnectionFactory
和DefaultJmsListenerContainerFactoryConfigurer
(都是由 Spring 注入的),它创建了DefaultJmsListenerContainerFactory
,后者设置了一个错误处理程序。通过设置containerFactory
属性,这个 bean 被用在@JmsListener
注释中。 -
JmsListenerConfigurer
。在本课中,您将创建一个静态配置。MethodListenerConfig
类实现了JmsListenerConfigurer
接口。该接口要求您注册一个具有验证器配置的 bean(ToDoValidator
类);在这种情况下,DefaultMessageHandlerMethodFactory
比恩。
如果还不想验证,可以从jmsFactory
bean 声明中移除MethodListenerConfig
类和setErrorHandler
调用;但是如果你想试验验证,那么你需要创建ToDoValidator
类(参见清单 9-4 )。
package com.apress.todo.validator;
import com.apress.todo.domain.ToDo;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
public class ToDoValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(ToDo.class);
}
@Override
public void validate(Object target, Errors errors) {
ToDo toDo = (ToDo)target;
if (toDo == null) {
errors.reject(null, "ToDo cannot be null");
}else {
if (toDo.getDescription() == null || toDo.getDescription().isEmpty())
errors.rejectValue("description",null,"description cannot be null or empty");
}
}
}
Listing 9-4com.apress.todo.validator.ToDoValidator.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
清单 9-4 显示了为每个消息调用的验证器类,并验证description
字段不为空。这个类实现了验证器接口,并实现了supports
和validate
方法。
这是ToDoErrorHandler
代码。
package com.apress.todo.error;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ErrorHandler;
public class ToDoErrorHandler implements ErrorHandler {
private static Logger log = LoggerFactory.getLogger(ToDoErrorHandler.class);
@Override
public void handleError(Throwable t) {
log.warn("ToDo error...");
log.error(t.getCause().getMessage());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
如您所见,这个类实现了ErrorHandler
接口。
现在,让我们创建保存todo.jms.destination
属性的ToDoProperties
类,该属性指示要连接到哪个队列/主题(参见清单 9-5 )。
package com.apress.todo.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "todo.jms")
public class ToDoProperties {
private String destination;
}
Listing 9-5com.apress.todo.config.ToDoProperties.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
清单 9-5 显示了ToDoProperties
类。还记得在清单 9-2(ToDoConsumer
类)中,processToDo
方法被标注了@JmsListener
注释,这暴露了destination
属性。该属性通过评估您在该类中定义的SpEL(Spring Expression Language)${todo.jms.destination}
表达式来获取其值。
您可以在application.properties
文件中设置该属性。
# JPA
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create-drop
# ToDo JMS
todo.jms.destination=toDoDestination
src/main/resources/application.properties
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
运行待办事项应用
接下来,让我们创建一个 config 类,它使用生产者向队列发送消息(参见清单 9-6 )。
package com.apress.todo.config;
import com.apress.todo.domain.ToDo;
import com.apress.todo.jms.ToDoProducer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ToDoSender {
@Bean
public CommandLineRunner sendToDos(@Value("${todo.jms.destination}") String destination, ToDoProducer producer){
return args -> {
producer.sendTo(destination,new ToDo("workout tomorrow morning!"));
};
}
}
Listing 9-6com.apress.todo.config.ToDoSender.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
清单 9-6 显示了使用ToDoProducer
实例和目的地(来自todo.jms.destination
属性)发送消息的配置类。
要运行这个应用,您可以使用您的 IDE(如果您导入了它)或者您可以使用 Maven 包装器。
./mvnw spring-boot:run
- 1
- 2
或者是格拉德包装。
./gradlew bootRun
- 1
- 2
您应该从日志中获得以下文本。
Producer> Message Sent
Consumer> ToDo(id=null, description=workout tomorrow morning!, created=null, modified=null, completed=false)
ToDo created> ToDo(id=8a808087645bd67001645bd6785b0000, description=workout tomorrow morning!, created=2018-07-02T10:32:19.546, modified=2018-07-02T10:32:19.547, completed=false)
- 1
- 2
- 3
- 4
您可以看一看http://localhost:8080/toDos
并查看创建的 ToDo。
使用 JMS 发布/订阅
如果你想使用发布/订阅模式,你想让多个消费者接收一条消息(通过使用主题订阅),我将解释你需要在你的应用中做什么。
因为我们使用 Spring Boot,这使得配置发布/订阅模式更容易。如果您使用默认监听器(一个@JmsListener(destination)
默认监听器容器),那么您可以使用application.properties
文件中的spring.jms.pub-sub-domain=true
属性。
但是,如果您使用自定义侦听器容器,那么您可以通过编程方式设置它。
@Bean
public DefaultMessageListenerContainer jmsListenerContainerFactory() {
DefaultMessageListenerContainer dmlc = new DefaultMessageListenerContainer();
dmlc.setPubSubDomain(true);
// Other configuration here ...
return dmlc;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
远程 ActiveMQ
ToDo 应用正在使用内存代理(spring.activemq.in-memory=true
)。这对于演示或测试来说可能是好的,但实际上,您使用的是远程 ActiveMQ 服务器。如果您需要一个远程服务器,将下面的键添加到您的application.properties
文件中(相应地修改它)。
spring.activemq.broker-url=tcp://my-awesome-server.com:61616
spring.activemq.user=admin
spring.activemq.password=admin
src/main/resources/application.properties
- 1
- 2
- 3
- 4
- 5
- 6
对于 ActiveMQ 代理,您可以使用更多的属性。去 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
找spring.activemq.*
键。
Spring Boot 的兔子
自从 Sun、Oracle 和 IBM 以及微软和 MSMQ 等公司首次尝试 JMS 以来,所使用的协议都是专有的。JMS 定义了一个接口 API,但是试图混合技术或编程语言是一件麻烦的事情。多亏了摩根大通的一个团队,AMQP(高级消息队列协议)诞生了。它是面向 MOM 的开放标准应用层。换句话说,AMQP 是一个有线级协议,这意味着您可以使用任何技术或编程语言来实现这个协议。
消息传递代理相互竞争,以证明它们是健壮的、可靠的和可伸缩的,但最重要的问题是它们有多快。我与许多经纪人合作过,到目前为止,最容易使用和扩展,也是最快的是 RabbitMQ,它实现了 AMQP 协议。
描述 RabbitMQ 的每个部分和所有相关概念需要一整本书,但是我将基于本节的例子来解释其中的一些概念。
安装 RabbitMQ
在我说 RabbitMQ 之前,先安装一下。如果你使用的是 Mac OS X/Linux,你可以使用brew
命令。
$ brew upgrade
$ brew install rabbitmq
- 1
- 2
- 3
如果你使用的是 UNIX 或者 Windows 系统,你可以去 RabbitMQ 网站使用安装程序( www.rabbitmq.com/download.html
)。RabbitMQ 是用 Erlang 编写的,所以它的主要依赖是在您的系统中安装 Erlang 运行时。现在,所有的 RabbitMQ 安装程序都附带了所有的 Erlang 依赖项。确保可执行文件在您的PATH
变量中(对于 Windows 和 Linux,取决于您使用的操作系统)。如果你正在使用brew
,你不需要担心设置PATH
变量。
RabbitMQ/AMQP:交换、绑定和队列
AMQP 定义了三个概念,这三个概念与 JMS 世界略有不同,但是非常容易理解。AMQP 定义了交换,这是发送消息的实体。每个交换机接收一条消息,并将其路由到零个或更多的队列。这种路由涉及一种基于交换类型和规则的算法,称为绑定。
AMPQ 协议定义了五种交换类型:直接、扇出、主题、和报头。图 9-2 显示了这些不同的交换类型。
图 9-2
AMQP 交易所/绑定/队列
图 9-2 显示了可能的交换类型。因此,主要思想是向一个交换发送一个消息,包括一个路由关键字,然后交换根据它的类型将消息传递给队列(或者如果路由关键字不匹配,它就不传递)。
默认交换自动绑定到每个创建的队列。直接交换通过路由关键字绑定到队列;您可以将这种交换类型视为一对一绑定。话题交流类似于直接交流;唯一的区别是,在它的绑定中,您可以在其路由关键字中添加一个通配符。标题交换类似于主题交换;唯一的区别是绑定是基于消息头的(这是一个非常强大的交换,您可以对它的消息头执行 all 和 any 表达式)。扇出交换机将消息复制到所有绑定队列;你可以把这种交流看作是一种信息广播。
你可以在 www.rabbitmq.com/tutorials/amqp-concepts.html
获得更多关于这些话题的信息。
本节中的示例使用默认的交换类型,这意味着路由关键字等于队列的名称。每次创建队列时,RabbitMQ 都会使用队列的名称创建一个从默认交换(实际名称是一个空字符串)到队列的绑定。
任何有 rabbitmq 的应用
让我们重新使用 ToDo 应用,并添加一条 AMQP 消息。与之前的应用一样,您可以使用 ToDo 实例。您发送和接收一个 JSON 消息,并将其转换为 object。
先打开你最喜欢的浏览器,指向已知的 Spring Initializr。将以下值添加到字段中。
-
组:
com.apress.todo
-
神器:
todo-rabbitmq
-
名称:
todo-rabbitmq
-
包名:
com.apress.todo
-
依赖关系:
RabbitMQ, Web, Lombok, JPA, REST Repositories, H2, MySQL
您可以选择 Maven 或 Gradle 作为项目类型。然后,您可以按下 Generate Project 按钮,这将下载一个 ZIP 文件。将其解压缩,并在您喜欢的 IDE 中导入项目(参见图 9-3 )。
图 9-3
spring initializehttps://start.spring.io
您可以从前面的章节中复制/粘贴 JPA/REST 项目的代码。
ToDo 生产者
让我们从创建一个向 Exchange 发送消息的生产者类开始(默认 Exchange-direct)(参见清单 9-7 )。
package com.apress.todo.rmq;
import com.apress.todo.domain.ToDo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
@Component
public class ToDoProducer {
private static final Logger log = LoggerFactory.getLogger(ToDoProducer.class);
private RabbitTemplate template;
public ToDoProducer(RabbitTemplate template){
this.template = template;
}
public void sendTo(String queue, ToDo toDo){
this.template.convertAndSend(queue,toDo);
log.info("Producer> Message Sent");
}
}
Listing 9-7com.apress.todo.rmq.ToDoProducer.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
清单 9-7 显示了ToDoProducer.java
类。让我们检查一下。
-
@Component
。这个注释标记了 Spring 容器要拾取的类。 -
RabbitTemplate
。RabbitTemplate
是一个助手类,它简化了对 RabbitMQ 的同步/异步访问,以便发送和/或接收消息。这与你之前看到的JmsTemplate
非常相似。 -
sendTo(routingKey,message)
。该方法将路由关键字和消息作为参数。在这种情况下,路由关键字是队列的名称。这个方法使用rabbitTemplate
实例来调用接受路由键和消息的convertAndSend
方法。请记住,消息被发送到交换(默认交换),交换将消息路由到正确的队列。这个路由关键字恰好是队列的名称。还要记住,默认情况下,RabbitMQ 总是将默认交换(直接交换)绑定到队列,路由关键字是队列的名称。
ToDo 消费者
接下来,是时候创建监听指定队列的消费者类了(参见清单 9-8 )。
package com.apress.todo.rmq;
import com.apress.todo.domain.ToDo;
import com.apress.todo.repository.ToDoRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class ToDoConsumer {
private Logger log = LoggerFactory.getLogger(ToDoConsumer.class);
private ToDoRepository repository;
public ToDoConsumer(ToDoRepository repository){
this.repository = repository;
}
@RabbitListener(queues = "${todo.amqp.queue}")
public void processToDo(ToDo todo){
log.info("Consumer> " + todo);
log.info("ToDo created> " + this.repository.save(todo));
}
}
Listing 9-8com.apress.todo.rmq.ToDoConsumer.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
清单 9-8 显示了ToDoConsumer.java
类。让我们检查一下。
-
@Component
。你已经知道这个注释了。它标记了 Spring 容器要拾取的类。 -
@RabbitListener
。该注释标记了为任何传入消息创建处理程序的方法(因为您也可以在类中使用该注释),这意味着它创建了一个连接到 RabbitMQ 队列的侦听器,并将该消息传递给该方法。在幕后,监听器通过使用正确的消息转换器(一个org.springframework.amqp.support.converter.MessageConverter
接口的实现)尽最大努力将消息转换成适当的类型。这个接口属于spring-amqp
项目);在这种情况下,它从 JSON 转换成一个ToDo
实例。
从ToDoProducer
和ToDoConsumer
可以看出,代码非常简单。如果您只使用 RabbitMQ Java 客户端( www.rabbitmq.com/java-client.html
)来创建它,至少您需要更多的代码行来创建连接、通道和消息并发送消息,或者如果您正在编写一个消费者,那么您需要打开一个连接、创建一个通道、创建一个基本消费者,并进入一个循环来处理每个传入的消息。这对简单的生产者或消费者来说是很多的。这就是为什么 Spring AMQP 团队创造了这种简单的方法,用几行代码完成一项繁重的任务。
配置待办事项应用
接下来让我们配置应用。请记住,您发送的是 ToDo 实例,所以实际上,这与我们使用 JMS 时的配置是一样的。我们需要设置转换器和监听器容器(参见清单 9-9 )。
package com.apress.todo.config;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ToDoConfig {
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
return factory;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(new Jackson2JsonMessageConverter());
return template;
}
@Bean
public Queue queueCreation(@Value("${todo.amqp.queue}") String queue){
return new Queue(queue,true,false,false);
}
}
Listing 9-9com.apress.todo.config.ToDoConfig.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
清单 9-9 向您展示了配置。它有几个 bean 定义;让我们检查一下。
-
SimpleRabbitListenerContainerFactory
。当使用@RabbitListener
注释进行自定义设置时,该工厂是必需的,因为您正在使用ToDo
实例;有必要设置消息转换器。 -
Jackson2JsonMessageConverter
。该转换器用于生产(带RabbitTemplate
)和消耗(@RabbitListener
);它使用 Jackson 库进行映射和转换。 -
RabbitTemplate
。这是一个可以发送和接收消息的助手类。在这种情况下,有必要使用 Jackson 转换器对其进行定制以生成 JSON 对象。 -
Queue
。您可以手动创建队列,但在这种情况下,您是以编程方式创建的。如果队列是持久的或排他的,那么您可以传递队列的名称,并自动删除。
请记住,在 AMQP 协议中,您需要一个绑定到队列的交换,所以这个特定的示例在运行时创建了一个名为spring-boot
的队列,默认情况下,所有队列都绑定到一个默认的交换。这就是为什么你没有提供任何关于交换的信息。因此,当生产者发送消息时,它首先被发送到默认交换,然后被路由到队列(spring-boot
)。
运行待办事项应用
让我们创建发送 ToDo 消息的 sender 类(参见清单 9-10 )。
package com.apress.todo.config;
import com.apress.todo.domain.ToDo;
import com.apress.todo.rmq.ToDoProducer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ToDoSender {
@Bean
public CommandLineRunner sendToDos(@Value("${todo.amqp.queue}") String destination, ToDoProducer producer){
return args -> {
producer.sendTo(destination,new ToDo("workout tomorrow morning!"));
};
}
}
Listing 9-10com.apress.todo.config.ToDoSender.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
将以下键(声明发送/消费队列)添加到您的application.properties
文件中。
todo.amqp.queue=spring-boot
- 1
- 2
在运行您的示例之前,请确保您的 RabbitMQ 服务器已经启动并正在运行。您可以通过打开终端并执行以下命令来启动它。
$ rabbitmq-server
- 1
- 2
使用来宾/来宾凭证访问http://localhost:15672/
,确保您可以访问 RabbitMQ web 控制台。如果您在访问 web 控制台时遇到问题,请确保通过运行以下命令启用了管理插件。
$ rabbitmq-plugins list
- 1
- 2
如果整个列表中的复选框未被选中,则管理插件尚未启用(通常在全新安装时发生)。要启用这个插件,您可以执行以下命令。
$ rabbitmq-plugins enable rabbitmq_management --online
- 1
- 2
现在,你可以再试一次。然后,您应该会看到一个类似于图 9-4 的 web 控制台。
图 9-4
rabbitmq web 控制台管理
图 9-4 显示了 RabbitMQ web 控制台。现在,您可以使用 IDE 像往常一样运行项目了。如果您使用的是 Maven,请执行
$ ./mvnw spring-boot:run
- 1
- 2
如果您使用的是 Gradle,请执行
$./gradlew bootRun
- 1
- 2
在您执行这个命令之后,您应该得到类似于下面的输出。
Producer> Message Sent
Consumer> ToDo(id=null, description=workout tomorrow morning!, created=null, modified=null, completed=false)
ToDo created> ToDo(id=8a808087645bd67001645bd6785b0000, description=workout tomorrow morning!, created=2018-07-02T10:32:19.546, modified=2018-07-02T10:32:19.547, completed=false)
- 1
- 2
- 3
- 4
如果您查看 RabbitMQ web 控制台的 Queues 选项卡,您应该已经定义了spring-boot
队列(参见图 9-5 )。
图 9-5
rabbitmq web 控制台队列选项卡
图 9-5 显示了 RabbitMQ web 控制台的 Queues 选项卡。你发的信息马上就送到了。如果你想多玩一点,看看一部分吞吐量,可以修改清单 9-11 所示的ToDoSender
类,但是不要忘记停止你的 app。
package com.apress.todo.config;
import com.apress.todo.domain.ToDo;
import com.apress.todo.rmq.ToDoProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.text.SimpleDateFormat;
import java.util.Date;
@EnableScheduling
@Configuration
public class ToDoSender {
@Autowired
private ToDoProducer producer;
@Value("${todo.amqp.queue}")
private String destination;
private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = 500L)
private void sendToDos(){
producer.sendTo(destination,new ToDo("Thinking on Spring Boot at " + dateFormat.format(new Date())));
}
}
Listing 9-11Version 2 of com.apress.todo.config.ToDoSender.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
清单 9-11 显示了ToDoSender
类的修改版本。让我们检查这个新版本。
-
@EnableScheduling
。这个注释告诉(通过自动配置)Spring 容器需要创建org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor
类。它根据@Scheduled
注释中的fixedRate
、fixedDelay
或cron
表达式,注册所有用@Scheduled
注释的方法,供org.springframework.scheduling.TaskScheduler
接口实现调用。 -
@Scheduled(fixedDelay = 500L)
。这个注释告诉TaskScheduler
接口实现以 500 毫秒的固定延迟执行sendToDos
方法。这意味着每半秒钟就向队列发送一条消息。
你已经知道的应用的另一部分。因此,如果您再次执行该项目,您应该会看到无休止的消息。在运行时,查看 RabbitMQ 控制台并查看输出。你可以放一个for
循环来在半秒钟内发送更多的消息。
远程兔子 MQ
如果您想要访问一个远程 RabbitMQ,您可以将以下属性添加到application.properties
文件中。
spring.rabbitmq.host=mydomain.com
spring.rabbitmq.username=rabbituser
spring.rabbitmq.password=thisissecured
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/production
- 1
- 2
- 3
- 4
- 5
- 6
你可以在 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
的 Spring Boot 参考中读到 RabbitMQ 的所有属性。
现在你知道在 Spring Boot 上使用 RabbitMQ 有多简单了。如果您想了解 RabbitMQ 和 Spring AMQP 技术的更多信息,您可以在主项目网站 http://projects.spring.io/spring-amqp/
获得更多信息。
您可以通过按 Ctrl+C 来停止 RabbitMQ,这是您启动代理的地方。关于如何使用 RabbitMQ,有更多的选择,比如创建一个集群或具有高可用性。您可以在 www.rabbitmq.com
了解更多相关信息。
使用 Spring Boot 重定向消息
现在轮到雷迪斯了。Redis(远程字典服务器)是一个 NoSQL 键值存储数据库。它是用 C 语言编写的,尽管它的内核很小,但它非常可靠、可伸缩、功能强大、速度超快。它的主要功能是存储数据结构,如列表、散列、字符串、集合和排序集合。一个主要的特性是提供一个发布/订阅消息系统,这就是为什么您将使用 Redis 作为消息代理的原因。
正在安装 Redis
安装 Redis 非常简单。如果您使用的是 Mac OS X/Linux,您可以使用brew
并执行以下命令。
$ brew update && brew install redis
- 1
- 2
如果您使用的是不同版本的 UNIX 或 Windows,您可以访问 Redis 网站,在 http://redis.io/download
下载 Redis 安装程序。或者,如果您想根据您的系统来编译它,也可以通过下载源代码来完成。
任何带有 Redis 的应用
使用 Redis 进行发布/订阅消息传递非常简单,与其他技术非常相似。您使用 Redis 的发布/订阅消息模式发送和接收 ToDo。
我们先打开你最喜欢的浏览器,指向 Spring Initializr。将以下值添加到字段中。
-
组:
com.apress.todo
-
神器:
todo-redis
-
名称:
todo-redis
-
包名:
com.apress.todo
-
依赖关系:
Redis, Web, Lombok, JPA, REST Repositories, H2, MySQL
您可以选择 Maven 或 Gradle 作为项目类型。然后,您可以按下 Generate Project 按钮,这将下载一个 ZIP 文件。将其解压缩,并在您喜欢的 IDE 中导入项目(参见图 9-6 )。
图 9-6
Spring 初始化 zr
您使用前面章节中的 ToDo 域和 repo。
ToDo 生产者
让我们创建将 Todo 实例发送到特定主题的 Producer 类(参见清单 9-12 )。
package com.apress.todo.redis;
import com.apress.todo.domain.ToDo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class ToDoProducer {
private static final Logger log = LoggerFactory.getLogger(ToDoProducer.class);
private RedisTemplate redisTemplate;
public ToDoProducer(RedisTemplate redisTemplate){
this.redisTemplate = redisTemplate;
}
public void sendTo(String topic, ToDo toDo){
log.info("Producer> ToDo sent");
this.redisTemplate.convertAndSend(topic, toDo);
}
}
Listing 9-12com.apress.todo.redis.ToDoProducer.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
清单 9-12 显示了生产者类。它与以前的技术非常相似。它使用了一个*Template
模式类;在这种情况下,发送ToDo
实例到特定主题的RedisTemplate
。
ToDo 消费者
接下来,创建订阅主题的消费者(参见清单 9-13 )。
package com.apress.todo.redis;
import com.apress.todo.domain.ToDo;
import com.apress.todo.repository.ToDoRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class ToDoConsumer {
private static final Logger log = LoggerFactory.getLogger(ToDoConsumer.class);
private ToDoRepository repository;
public ToDoConsumer(ToDoRepository repository){
this.repository = repository;
}
public void handleMessage(ToDo toDo) {
log.info("Consumer> " + toDo);
log.info("ToDo created> " + this.repository.save(toDo));
}
}
Listing 9-13com.apress.todo.redis.ToDoConsumer.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
清单 9-13 显示了订阅任何传入ToDo
消息主题的消费者。重要的是要知道,必须有一个handleMessage
方法名来使用监听器(这是创建MessageListenerAdapter
时的一个约束)。
配置待办事项应用
接下来,让我们为 ToDo 应用创建配置(参见清单 9-14 )。
package com.apress.todo.config;
import com.apress.todo.domain.ToDo;
import com.apress.todo.redis.ToDoConsumer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
@Configuration
public class ToDoConfig {
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter toDoListenerAdapter, @Value("${todo.redis.topic}") String topic) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(toDoListenerAdapter, new PatternTopic(topic));
return container;
}
@Bean
MessageListenerAdapter toDoListenerAdapter(ToDoConsumer consumer) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(consumer);
messageListenerAdapter.setSerializer(new Jackson2JsonRedisSerializer<>(ToDo.class));
return messageListenerAdapter;
}
@Bean
RedisTemplate<String, ToDo> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<String,ToDo> redisTemplate = new RedisTemplate<String,ToDo>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(ToDo.class));
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
Listing 9-14com.apress.todo.config.ToDoConfig.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
清单 9-14 显示了 ToDo 应用所需的配置。这个类声明了下面的 Spring beans。
-
RedisMessageListenerContainer
。这个类负责连接到 Redis 主题。 -
MessageListenerAdapter
。这个适配器接受一个 POJO (Plain Old Java Object)类来处理消息。作为一个要求,方法必须被命名为handleMessage
;这个方法接收来自主题的消息作为一个ToDo
实例,这就是为什么它也需要一个序列化器。 -
Jackson2JsonRedisSerializer
。这个序列化程序从/到ToDo
实例进行转换。 -
RedisTemplate
。这个类实现了Template
模式,与其他消息传递技术非常相似。这个类需要一个序列化器来处理 JSON 和来往于ToDo
实例。
使用 JSON 格式并在 ToDo 实例之间进行正确的转换需要这种定制;但是您可以避免一切,使用缺省配置,该配置需要一个可序列化的对象(比如一个字符串)来发送,并使用StringRedisTemplate
来代替。
在application.properties
文件中,添加以下内容。
# JPA
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create-drop
# ToDo Redis
todo.redis.topic=todos
- 1
- 2
- 3
- 4
- 5
- 6
- 7
运行待办事项应用
在运行 ToDo 应用之前,请确保您已经启动并运行了 Redis 服务器。要启动它,请在终端中执行以下命令。
$ redis-server
89887:C 11 Feb 20:17:55.320 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
89887:M 11 Feb 20:17:55.321 * Increased maximum number of open files to 10032 (it was originally set to 256).
_._
_.-``__ “-._
_.-`` `. `_. “-._ Redis 4.0.10 64 bit
.-`` .-```. ```java/ _.,_ “-._
( ' , .-` | `, ) Standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 89887
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
89887:M 11 Feb 20:17:55.323 # Server started, Redis version 3.0.7
89887:M 11 Feb 20:17:55.323 * The server is now ready to accept connections on port 6379
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
这个输出表明 Redis 已经准备好,正在监听端口 6379。您可以打开一个新的终端窗口并执行以下命令。
$ redis-cli
- 1
- 2
这是一个连接到 Redis 服务器的 shell 客户端。您可以通过执行以下命令订阅“todos
”主题。
127.0.0.1:6379> SUBSCRIBE todos
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "todos"
3) (integer) 1
- 1
- 2
- 3
- 4
- 5
- 6
现在,您可以像往常一样运行项目(通过在您的 ide 中运行或者使用 Maven 或 Gradle)。如果您使用的是 Maven,请执行
$ ./mvnw spring-boot:run
- 1
- 2
执行该命令后,您的日志中应该会有类似于以下输出的内容。
...
Producer> Message Sent
Consumer> ToDo(id=null, description=workout tomorrow morning!, created=null, modified=null, completed=false)
ToDo created> ToDo(id=8a808087645bd67001645bd6785b0000, description=workout tomorrow morning!, created=2018-07-02T10:32:19.546, modified=2018-07-02T10:32:19.547, completed=false)
...
- 1
- 2
- 3
- 4
- 5
- 6
如果您看一看 Redis shell,您应该会看到如下所示的内容。
1) "message"
2) "todos"
3) "{"id":null,"description":"workout tomorrow morning!","created":null,"modified":null,"completed":false}"
- 1
- 2
- 3
- 4
当然,你可以在浏览器的http://localhost:8080/toDos
查看新的待办事项。
干得好!您已经使用 Redis 创建了一个 Spring Bot 消息应用。您可以通过按 Ctrl+C 关闭 Redis。
远程重定向
如果想要远程访问 Redis,需要向application.properties
文件添加以下属性。
spring.redis.database=0
spring.redis.host=localhost
spring.redis.password=mysecurepassword
spring.redis.port=6379
- 1
- 2
- 3
- 4
- 5
你可以在 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
的《Spring Boot 参考》中读到关于 Redis 的所有性质。
您看到您需要使用 Redis 作为消息传递代理,但是如果您想了解更多关于 Spring 的键值存储,您可以在 http://projects.spring.io/spring-data-redis/
查看 Spring Data Redis 项目。
带 Spring Boot 的 WebSockets
关于 WebSockets 的主题应该放在 web 一章中,这似乎是合乎逻辑的,但我认为 WebSockets 与消息传递更相关,这就是为什么这一节在本章中的原因。
WebSockets 是一种新的通信方式,取代了客户机/服务器 web 技术。它允许客户端和服务器之间长期保持单个 TCP 套接字连接。它也被称为推技术,这是服务器可以向 web 发送数据,而无需客户端进行长时间轮询来请求新的更改。
本节向您展示了一个示例,其中您通过 REST 端点(Producer
)发送消息,并使用网页和 JavaScript 库接收消息(Consumer
)。
带有 WebSockets 的 ToDo 应用
创建使用 JPA REST 存储库的 ToDo 应用。每次有新的待办事项时,它都会被发布到网页上。从网页到 ToDo 应用的连接使用使用 STOMP 协议的 WebSockets。
我们先打开你最喜欢的浏览器,指向 Spring Initializr。将以下值添加到字段中。
-
组:
com.apress.todo
-
神器:
todo-websocket
-
名称:
todo-websocket
-
包名:
com.apress.todo
-
依赖关系:
Websocket, Web, Lombok, JPA, REST Repositories, H2, MySQL
您可以选择 Maven 或 Gradle 作为项目类型。然后,您可以按下 Generate Project 按钮,这将下载一个 ZIP 文件。将其解压缩,并在您喜欢的 IDE 中导入项目(参见图 9-7 )。
图 9-7
Spring 初始化 zr
您可以重用和复制/粘贴ToDo
和ToDoRepository
类。您还需要添加以下依赖项;如果您使用的是 Maven,将以下内容添加到pom.xml
文件中。
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.3</version>
</dependency>
<!-- jQuery -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.1.1</version>
</dependency>
<!-- Bootstrap -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.5</version>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
如果您使用的是 Gradle,将以下依赖项添加到build.gradle
文件中。
compile('org.webjars:sockjs-client:1.1.2')
compile('org.webjars:stomp-websocket:2.3.3')
compile('org.webjars:jquery:3.1.1')
compile('org.webjars:bootstrap:3.3.5')
- 1
- 2
- 3
- 4
- 5
这些依赖项创建了您需要连接到消息传递代理的 web 客户端。WebJars 是将外部资源作为包包含进来的一种非常方便的方式,而不用担心一个接一个地下载。
ToDo 生产者
当使用 HTTP POST 方法发布新的 ToDo 时,生成器向主题发送 STOMP 消息。要做到这一点,有必要捕捉当域类被持久化到数据库时 Spring Data REST 发出的事件。
Spring Data REST 框架有几个事件,允许在持久化操作之前、期间和之后进行控制。创建一个监听after-create
事件的ToDoEventHandler
类(参见清单 9-15 )。
package com.apress.todo.event;
import com.apress.todo.config.ToDoProperties;
import com.apress.todo.domain.ToDo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.rest.core.annotation.HandleAfterCreate;
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;
@Component
@RepositoryEventHandler(ToDo.class)
public class ToDoEventHandler {
private Logger log = LoggerFactory.getLogger(ToDoEventHandler.class);
private SimpMessagingTemplate simpMessagingTemplate;
private ToDoProperties toDoProperties;
public ToDoEventHandler(SimpMessagingTemplate simpMessagingTemplate,ToDoProperties toDoProperties){
this.simpMessagingTemplate = simpMessagingTemplate;
this.toDoProperties = toDoProperties;
}
@HandleAfterCreate
public void handleToDoSave(ToDo toDo){
this.simpMessagingTemplate.convertAndSend(this.toDoProperties.getBroker() + "/new",toDo);
log.info(">> Sending Message to WS: ws://todo/new - " + toDo);
}
}
Listing 9-15com.apress.todo.event.ToDoEventHandler.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
清单 9-15 向您展示了接收after-create
事件的事件处理程序。我们来分析一下。
-
@RepositoryEventHandler
。这个注释告诉BeanPostProcessor
这个类需要检查处理程序方法。 -
SimpMessagingTemplate
。这个类是Template
模式的另一个实现,用于使用 STOMP 协议发送消息。它的行为方式与前面章节中的其他*模板类相同。 -
ToDoProperties
。此类是自定义属性处理程序。它描述了 WebSockets 的代理(todo.ws.broker
)、端点(todo.ws.endpoint
)和应用端点。 -
@HandleAfterCreate
。该注释标记了获取域类保存到数据库后发生的任何事件的方法。如您所见,它使用了保存到数据库中的ToDo
实例。在这个方法中,您使用SimpMessagingTemplate
向/todo/new
端点发送一个ToDo
实例。该端点的任何订阅者都会获得 JSON 格式的 ToDo(STOMP)。
接下来,让我们创建保存端点信息的ToDoProperties
类(参见清单 9-16 )。
package com.apress.todo.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "todo.ws")
public class ToDoProperties {
private String app = "/todo-api-ws";
private String broker = "/todo";
private String endpoint = "/stomp";
}
Listing 9-16com.apress.todo.config.ToDoProperties.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
ToDoProperties
类是一个助手,用来保存关于代理(/stomp
和 web 客户端连接到哪里的信息(主题- /todo/new
)。
配置待办事项应用
这一次,ToDo 应用创建了一个消息传递代理,它接受 WebSocket 通信并使用 STOMP 协议进行消息交换。
创建配置类(参见清单 9-17 )。
package com.apress.todo.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
@EnableConfigurationProperties(ToDoProperties.class)
public class ToDoConfig implements WebSocketMessageBrokerConfigurer {
private ToDoProperties props;
public ToDoConfig(ToDoProperties props){
this.props = props;
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint(props.getEndpoint()).setAllowedOrigins("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker(props.getBroker());
config.setApplicationDestinationPrefixes(props.getApp());
}
}
Listing 9-17com.apress.todo.config.ToDoConfig.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
清单 9-17 显示了ToDoConfig
类。让我们检查一下。
-
@Configuration
。您知道这将类标记为 Spring 容器的配置。 -
@EnableWebSocketMessageBroker
。该注释使用自动配置来创建所有必要的构件,以便使用一个非常高级的消息传递子协议通过 WebSockets 实现代理支持的消息传递。如果您需要定制端点,您需要覆盖来自WebSocketMessageBrokerConfigurer
接口的方法。 -
WebSocketMessageBrokerConfigurer
。它重写方法以自定义协议和端点。 -
registerStompEndpoints(StompEndpointRegistry registry)
。此方法注册 STOMP 协议;在这种情况下,它注册了/stomp
端点,并使用 JavaScript 库 SockJS (https://github.com/sockjs
)。 -
configureMessageBroker(MessageBrokerRegistry config)
。此方法配置消息代理选项。在这种情况下,它启用了/todo
端点中的代理。这意味着想要使用 WebSockets 代理的客户端需要使用/todo
来连接。
接下来,让我们向application.properties
文件添加信息。
# JPA
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create-drop
# Rest Repositories
spring.data.rest.base-path=/api
# WebSocket
todo.ws.endpoint=/stomp
todo.ws.broker=/todo
todo.ws.app=/todo-api-ws
src/main/resources/application.properties
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
因为客户端是一个 HTML 页面并且是默认的 index.html,application.properties
文件声明了一个新的 REST base-path
端点(/api
);这意味着 REST 存储库位于/api/*
端点,而不是应用的根目录。
所有 Web 客户端
web 客户机连接到消息传递代理,subscribe(使用 STOMP 协议)接收发布的任何新 ToDo。这个客户端可以是处理 WebSockets 并知道 STOMP 协议的任何类型。
让我们创建一个连接到代理的简单 index.html 页面(参见清单 9-18 )。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ToDo WebSockets</title>
<link rel="stylesheet" href="/webjars/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="/webjars/bootstrap/3.3.5/css/bootstrap-theme.min.css">
</head>
<body>
<div class="container theme-showcase" role="main">
<div class="jumbotron">
<h1>What ToDo?</h1>
<p>An easy way to find out what your are going to do NEXT!</p>
</div>
<div class="page-header">
<h1>Everybody ToDo's</h1>
</div>
<div class="row">
<div class="col-sm-12">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">ToDo:</h3>
</div>
<div class="panel-body">
<div id="output">
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/webjars/jquery/3.1.1/jquery.min.js"></script>
<script src="/webjars/sockjs-client/1.1.2/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/2.3.3/stomp.min.js"></script>
<script>
$(function(){
var stompClient = null;
var socket = new SockJS('http://localhost:8080/stomp');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/todo/new', function (data) {
console.log('>>>>> ' + data);
var json = JSON.parse(data.body);
var result = "<span><strong>[" + json.created + "]</strong> " + json.description + "</span><br/>";
$("#output").append(result);
});
});
});
</script>
</body>
</html>
Listing 9-18src/main/resources/static/index.html
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
清单 9-18 显示了使用SockJS
类连接到/stomp
端点的客户端 index.html。它订阅了/todo/new
主题,并一直等到 get a new ToDo 被添加到列表中。对 JavaScript 库和 CSS 的引用是 WebJars 类资源。
运行待办事项应用
现在,您已经准备好启动您的待办事项应用。您可以像往常一样运行应用,既可以使用 IDE,也可以在命令行中运行。如果您使用的是 Maven,请执行
$ ./mvnw spring-boot:run
- 1
- 2
如果您使用的是 Gradle,请执行
$ ./gradlew bootRun
- 1
- 2
打开浏览器,进入http://localhost:8080
。您应该会看到一个空的待办事项框。接下来,打开终端并执行以下命令。
$ curl -XPOST -d '{"description":"Learn to play Guitar"}' -H "Content-Type: application/json" http://localhost:8080/api/toDos
$ curl -XPOST -d '{"description":"read Spring Boot Messaging book from Apress"}' -H "Content-Type: application/json" http://localhost:8080/api/toDos
- 1
- 2
- 3
喜欢的话可以再加。在您的浏览器中,您会看到待办事项(参见图 9-8 )。
图 9-8
SockJS 和 Stomp 消息:待办事项列表
图 9-8 显示了通过 WebSockets 发布消息的结果。现在,想象一下需要实时通知的新应用的可能性(例如创建实时聊天室、为您的客户即时更新股票,或者更新您的网站而无需预览或重启)。有了 Spring Boot 和 WebSockets,你就被覆盖了。
注意
所有的代码都可以从网站上获得。也可以在 https://github.com/felipeg48/pro-spring-boot-2nd
获取最新。
摘要
本章讨论了用于消息传递的所有技术,包括 JMS 和 Artemis。还讨论了如何通过在application.properties
文件中提供服务器名称和端口来连接到远程服务器。
您了解了 AMQP 和 RabbitMQ,以及如何使用 Spring Boot 收发信息。您还了解了 Redis 以及如何使用它的发布/订阅消息传递。最后,您了解了 WebSockets 以及用 Spring Boot 实现它是多么容易。
如果你对消息传递感兴趣,我写了 Spring Boot 消息传递 (Apress,2017) ( www.apress.com/us/book/9781484212257
),其中详细讨论了它,并揭示了更多的消息传递模式,从简单的应用事件到使用 Spring Cloud Stream 及其传输抽象的云解决方案。
下一章将讨论 Spring Boot 执行器以及如何监控您的 Spring Boot 应用。
十、Spring Boot 执行器
本章讨论了 Spring Boot 执行器模块,并解释了如何使用其所有功能来监控您的 Spring Boot 应用。
每个开发人员在开发期间和之后的一个共同任务是开始检查日志。开发人员检查业务逻辑是否如预期的那样工作,或者检查服务的处理时间,等等。即使他们应该有他们的单元、集成和回归测试,他们也不能避免外部故障,包括网络(连接、速度等。)、磁盘(空间、权限等。),还有更多。
当您部署到生产环境时,这一点甚至更加重要。你必须关注你的应用,有时还要关注整个系统。当您开始依赖非功能性需求时,例如检查不同应用健康状况的监控系统,或者当您的应用达到某个阈值时发出警报,或者更糟的是,当您的应用崩溃时,您需要尽快采取行动。
开发人员依赖许多第三方技术来完成他们的工作,我并不是说这不好,但这意味着所有的重担都由 DevOps 团队承担。他们必须监控每一个应用和整个系统。
Spring Boot 执行器
Spring Boot 包括一个致动器模块,它将生产就绪的非功能性要求引入到您的应用中。Spring Boot 执行器模块提供了开箱即用的监控、指标和审计功能。
使执行器模块更有吸引力的是,您可以通过不同的技术公开数据,比如 HTTP(端点)和 JMX。Spring Boot 致动器指标监控可以使用 Micrometer 框架( http://micrometer.io/
)来完成,它允许您编写一次指标代码,并在任何厂商中立的引擎中使用,如 Prometheus、Atlas、CloudWatch、Datadog 等等。
带执行器的待办事项应用
让我们开始使用 ToDo 应用中的 Spring Boot 执行器模块来看看执行器是如何工作的。您可以从头开始,也可以跟随下一部分来了解您需要做什么。如果您是从零开始,那么您可以转到 Spring Initializr ( https://start.spring.io
)并将以下值添加到字段中。
-
组:
com.apress.todo
-
神器:
todo-actuator
-
名称:
todo-actuator
-
包名:
com.apress.todo
-
依赖关系:
Web, Lombok, JPA, REST Repositories, Actuator, H2, MySQL
您可以选择 Maven 或 Gradle 作为项目类型。然后,您可以按下 Generate Project 按钮,这将下载一个 ZIP 文件。将其解压缩,并在您喜欢的 IDE 中导入项目(参见图 10-1 )。
图 10-1
Spring 初始化 zr
目前没有来自其他项目的任何内容;唯一的新依赖是执行器模块。您可以复制/重用ToDo
域类和ToDoRepository
接口(参见清单 10-1 和 10-2 )。
package com.apress.todo.repository;
import com.apress.todo.domain.ToDo;
import org.springframework.data.repository.CrudRepository;
public interface ToDoRepository extends CrudRepository<ToDo,String> { }
Listing 10-2com.apress.todo.repository.ToDoRepository.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
package com.apress.todo.domain;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Entity
@Data
public class ToDo {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
@NotNull
@NotBlank
private String description;
@Column(insertable = true, updatable = false)
private LocalDateTime created;
private LocalDateTime modified;
private boolean completed;
public ToDo(){}
public ToDo(String description){
this.description = description;
}
@PrePersist
void onCreate() {
this.setCreated(LocalDateTime.now());
this.setModified(LocalDateTime.now());
}
@PreUpdate
void onUpdate() {
this.setModified(LocalDateTime.now())
;
}
}
Listing 10-1com.apress.todo.domain.ToDo.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
在运行 ToDo 应用之前,看看你的pom.xml
(如果你使用 Maven)或build.gradle
(如果你使用 Gradle)中是否有spring-boot-starter-actuator
依赖项。
您可以运行 ToDo 应用,需要注意的重要事情是日志输出。你应该有类似的东西。
INFO 41925 --- [main] s... : Mapped "{[/actuator/health],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" ...
INFO 41925 --- [main] s... : Mapped "{[/actuator/info],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" ...
INFO 41925 --- [main] s... : Mapped "{[/actuator],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" ...
- 1
- 2
- 3
- 4
默认情况下,执行器模块公开了您可以访问的三个端点。
-
/actuator/health
。此端点提供基本的应用健康信息。如果从浏览器或命令行进行访问,您会得到以下响应:{ "status": "UP" }
- 1
- 2
- 3
- 4
-
/actuator/info
。此端点显示任意应用信息。如果您访问这个端点,您将得到一个空响应;但是如果您将以下内容添加到您的application.properties
文件中:spring.application.name=todo-actuator info.application-name=${spring.application.name} info.developer.name=Awesome Developer info.developer.email=awesome@example.com
- 1
- 2
- 3
- 4
- 5
- 6
您会得到以下内容:
{ "application-name": "todo-actuator", "developer": { "name": "Awesome Developer", "email": "awesome@example.com" } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
-
/actuator
。该端点是所有执行器端点的前缀。如果您通过浏览器或命令行访问该端点,您会看到:{ "_links": { "self": { "href": "http://localhost:8080/actuator", "templated": false }, "health": { "href": "http://localhost:8080/actuator/health", "templated": false }, "info": { "href": "http://localhost:8080/actuator/info", "templated": false } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
默认情况下,除了/actuator/shutdown
端点之外,所有端点(还有更多)都是启用的;但是为什么只暴露两个端点(健康和信息)?实际上,所有这些都是通过 JMX 暴露的,这是因为其中一些包含敏感信息;所以,知道通过网络暴露什么信息是很重要的。
如果您想在 web 上公开它们,有两个属性:management.endpoints.web.exposure.include
和management.endpoints.web.exposure.exclude
。您可以用逗号将它们分开列出,也可以使用*
将它们全部列出。
这同样适用于通过具有属性的 JMX 公开端点。
management.endpoints.jmx.exposure.include
和management.endpoints.jmx.exposure.exclude
。请记住,默认情况下,所有端点都通过 JMX 公开。
正如我之前提到的,您不仅有公开端点的方法,还有启用它们的方法。可以用下面的语义:management.endpoint.<ENDPOINT-NAME>.enabled
。所以,如果你想启用/actuator/shutdown
(默认情况下是禁用的),你需要在application.properties
中这样做。
management.endpoint.shutdown.enabled=true
- 1
- 2
您可以将以下属性添加到您的application.properties
文件中,以公开所有的 web actuator 端点。
management.endpoints.web.exposure.include=*
- 1
- 2
如果你看一下输出,你会得到更多的执行器端点,比如/actuator/beans
、/actuator/conditions
等等。让我们更详细地回顾一下其中的一些。
/执行器
/actuator
端点是所有端点的前缀,但是如果您访问它,它会为所有其他端点提供一个基于超媒体的发现页面。所以,如果你去http://localhost:8080/actuator
,你应该看到类似于图 10-2 的东西。
图 10-2
http://localhost:8080/actuator
/致动器/条件
此端点显示自动配置报告。它给你两组:positiveMatches
和negativeMatches
。请记住,Spring Boot 的主要特性是它通过查看类路径和依赖项来自动配置您的应用。这与您添加到pom.xml
文件中的起始 POM 和额外的依赖项有很大关系。如果你去http://localhost:8080/actuator/conditions
,你应该会看到类似于图 10-3 的东西。
图 10-3
http://localhost:8080/actuator/conditions
/执行器/bean
该端点显示应用中使用的所有 Spring beans。请记住,尽管您添加了几行代码来创建一个简单的 web 应用,但在幕后,Spring 开始创建运行您的应用所需的所有 beans。如果你去http://localhost:8080/actuator/beans
,你应该会看到类似于图 10-4 的东西。
图 10-4
http://localhost:8080/actuator/beans
/执行器/配置
这个端点列出了由@ConfigurationProperties
bean 定义的所有配置属性,这是我在前面的章节中向您展示过的。记住,你可以添加你自己的配置属性前缀,它们可以在application.properties
或 YAML 文件中定义和访问。图 10-5 显示了该端点的一个例子。
图 10-5
http://localhost:8080/actuator/configprops
/执行器/线程转储
该端点执行应用的线程转储。它显示了所有正在运行的线程以及运行您的应用的 JVM 的堆栈跟踪。前往http://localhost:8080/actuator/threaddump
终点(见图 10-6 )。
图 10-6
http://localhost:8080/actuator/threaddump
/执行器/环境
这个端点公开了 Spring 的ConfigurableEnvironment
接口的所有属性。这将显示所有活动的概要文件和系统环境变量以及所有应用属性,包括 Spring Boot 属性。转到http://localhost:8080/actuator/env
(见图 10-7 )。
图 10-7
http://localhost:8080/actuator/env
/执行器/健康
此端点显示应用的健康状况。默认情况下,它向您显示整体系统健康状况。
{
"status": "UP"
}
- 1
- 2
- 3
- 4
如果您想查看关于其他系统的更多信息,您需要在application.properties
文件中使用以下属性。
management.endpoint.health.show-details=always
- 1
- 2
修改application.properties
并重新运行 ToDo 应用。如果您有一个数据库应用(我们有),您会看到数据库状态,默认情况下,您还会看到来自您系统的diskSpace
。如果你正在运行你的 app,你可以去http://localhost:8080/actuator/health
(见图 10-8 )。
图 10-8
http://localhost:8080/actuator/health
-详细信息
/执行器/信息
此端点显示公共应用信息。这意味着您需要将这些信息添加到application.properties
中。如果您有多个 Spring Boot 应用,建议您添加它。
/执行器/记录器
此端点显示应用中可用的所有记录器。图 10-9 显示了特定包装的水平。
图 10-9
http://localhost:8080/actuator/loggers
/执行器/记录器/{name}
通过这个端点,您可以查找特定的包及其日志级别。因此,如果您配置了logging.
level.com
.apress.todo=DEBUG
,并且您到达了http://localhost:8080/actuator/loggers/com.apress.todo
端点,您将得到以下结果。
{
"configuredLevel": DEBUG,
"effectiveLevel": "DEBUG"
}
- 1
- 2
- 3
- 4
- 5
/执行器/指标
这个端点显示当前应用的指标信息,在这里您可以确定它使用了多少内存、有多少内存可用、应用的正常运行时间、使用的堆的大小、使用的线程数量等等(参见图 10-10 和图 10-11 )。
图 10-10
http://localhost:8080/actuator/metrics
您可以通过在端点末尾添加名称来访问每个指标;所以如果你想了解更多关于jvm.memory.max
的信息,你需要到达http://localhost:8080/actuator/metrics/jvm.memory.max
(见图 10-11 )。
图 10-11
http://localhost:8080//actuator/metrics/jvm.memory.max
如果你看一下图 10-11 ,在可用标签部分,你可以通过追加tag=KEY:VALUE
获得更多信息。您可以使用http://localhost:8080/actuator/metrics/jvm.memory.max?tag=area:heap
并获得关于堆的信息。
/执行器/映射
这个端点显示了应用中声明的所有@RequestMapping
路径的所有列表。如果您想更多地了解声明了哪些映射,这非常有用。如果您的应用正在运行,您可以转到http://localhost:8080/actuator/mappings
端点(参见图 10-12 )。
图 10-12
http://localhost:8080/actuator/mappings
/致动器/关闭
默认情况下,此端点未启用。它允许应用正常关闭。这个端点是敏感的,这意味着它可以安全地使用,也应该如此。如果您的应用正在运行,您现在可以停止它。如果您想要启用/actuator/shutdown
端点,您需要向application.properties
添加以下内容。
management.endpoint.shutdown.enabled=true
- 1
- 2
保护这个端点是明智的。您需要将spring-boot-starter-security
依赖项添加到您的pom.xml
(如果您使用的是 Maven)。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
如果您使用的是 Gradle,您可以将下面的依赖项添加到您的build.gradle
中。
complie ('org.springframework.boot: spring-boot-starter-security')
- 1
- 2
请记住,通过添加安全性依赖项,您将默认启用安全性。用户名为user
,密码打印在日志中。此外,您可以通过使用内存、数据库或 LDAP 用户来建立更好的安全性;有关更多信息,请参见 Spring Boot 安全性章节。
现在,让我们添加management.endpoint.shutdown.enabled=true
和spring-boot-starter-security
依赖项并重新运行应用。运行应用后,查看日志并保存打印的密码,以便可以在/actuator/shutdown
端点上使用。
...
Using default security password: 2875411a-e609-4890-9aa0-22f90b4e0a11
...
- 1
- 2
- 3
- 4
现在,如果您打开一个终端,您可以执行以下命令。
$ curl -i -X POST http://localhost:8080/shutdown -u user:2875411a-e609-4890-9aa0-22f90b4e0a11
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Application-Context: application
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 17 Feb 2018 04:22:58 GMT
{"message":"Shutting down, bye..."}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
正如您从这个输出中看到的,您正在使用一个POST
方法来访问/actuator/shutdown
端点,并且您正在传递之前打印的用户和密码。结果是Shutting down, bye..
的消息。当然,你的申请被终止了。同样,知道这个特定的端点必须在任何时候都受到保护是很重要的。
/actuator/httptrace
这个端点显示跟踪信息,通常是最后几个 HTTP 请求。这个端点对于查看所有请求信息和返回的信息以在 HTTP 级别调试应用非常有用。您可以运行您的应用并转到http://localhost:8080/actuator/httptrace
。你应该会看到类似于图 10-13 的东西。
图 10-13
http://localhost:8080/actuator/httptrace
更改端点 ID
您可以配置端点 ID,这会更改名称。假设你不喜欢/actuator/beans
端点,它指的是 Spring beans,那么如果你把这个端点改成/actuator/spring
呢。
您以management.endpoints.web.path-mapping.<endpoint-name>=<new-name>
的形式在application.properties
文件中进行这种更改;例如,management . endpoints . web . path-mapping。豆 = 春。
如果您重新运行您的应用(停止并重新启动以应用更改),您可以使用/actuator/spring
端点来访问/actuator/beans
端点。
致动器 CORS 支架
使用 Spring Boot 执行器模块,您可以配置 CORS(跨源资源共享),这允许您指定哪些跨域被授权使用执行器的端点。通常,这允许应用间连接到您的端点,由于安全原因,只有授权的域能够执行这些端点。
您可以在application.properties
文件中进行配置。
management.endpoints.web.cors.allowed-origins=http://mydomain.com
management.endpoints.web.cors.allowed-methods=GET, POST
- 1
- 2
- 3
如果您的应用正在运行,请停止并重新运行它。
通常在management.endpoints.web.cors.allowed-origins
中,你应该输入一个类似于 http://mydomain.com
或者http://localhost:9090
(不是*
)的域名,这样就可以访问你的端点以避免任何黑客入侵你的网站。这与在任何控制器中使用@CrossOrigin(origins = "http://localhost:9000")
注释非常相似。
更改管理端点路径
默认情况下,Spring Boot 执行器将它在/actuator
中的管理作为根,这意味着执行器的所有端点都可以从/actuator
访问;例如,/actuator/beans
,/actuator/health
等等。在继续之前,请停止您的应用。您可以通过向application.properties
文件添加以下属性来更改其管理上下文路径。
management.endpoints.web.base-path=/monitor
- 1
- 2
如果您重新运行您的应用,您会看到EndpointHandlerMapping
正在通过添加/monitor/<endpoint-name>
上下文路径来映射所有端点。您现在可以通过http://localhost:8080/monitor/httptrace
访问/httptrace
端点。
您还可以使用management.server.*
属性更改服务器地址、添加 SSL、使用特定的 IP 或更改端点的端口。
management.server.servlet.context-path=/admin
management.server.port=8081
management.server.address=127.0.0.1
- 1
- 2
- 3
- 4
该配置的端点为context-path /admin/actuator/<endpoint-name>
。端口是 8081(这意味着您有两个监听端口:8080 用于您的应用,8081 用于您的管理端点)。端点或管理绑定到 127.0.0.1 地址。
如果您想要禁用端点(出于安全原因),您有两个选择,您可以使用management.endpoints.enabled-by-default=false
或者您可以使用management.server.port=-1
属性。
保护端点
您可以通过包含spring-boot-starter-security
和配置WebSecurityConfigurerAdapter
来保护您的致动器端点;这是通过HttpSecurity
和RequestMatcher
配置实现的。
@Configuration
public class ToDoActuatorSecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(EndpointRequest.toAnyEndpoint())
.authorizeRequests()
.anyRequest().hasRole("ENDPOINT_ADMIN")
.and()
.httpBasic();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
为了安全起见,访问端点需要ENDPOINT_ADMIN
角色,知道这一点很重要。
配置端点
默认情况下,您会看到执行器端点缓存响应不接受任何参数的读取操作;所以如果需要改变这种行为,可以使用management.endpoint.<endpoint-name>.cache.time-to-live
属性。作为另一个例子,如果您需要更改/actuator/beans
缓存,您可以将以下内容添加到application.properties
文件中。
management.endpoint.beans.cache.time-to-live=10s
- 1
- 2
实现自定义执行器端点
您可以扩展或创建自定义执行器端点。你需要用@Endpoint
标记你的类,也要用@ReadOperation
、@WriteOperation
或@DeleteOperation
标记你的方法;默认情况下,您的端点通过 JMX 和基于 HTTP 的 web 公开。
您可以更具体地决定是否只想向 JMX 公开您的端点,然后将您的类标记为@JmxEndpoint
。如果你只是在网络上需要它,那么你可以用@WebEndpoint
来标记你的类。
当创建方法时,您可以接受参数,这些参数被转换成ApplicationConversionService
实例所需的正确类型。这些类型使用了application/vnd.spring-boot.actuator.v2+json
和application/json
内容类型。
您可以在任何方法签名中返回任何类型(甚至是void
或Void
)。通常,返回的内容类型因类型而异。如果是org.springframework.core.io.Resource
类型,则返回一个application/octet-stream
内容类型;对于所有其他类型,它返回一个application/vnd.spring-boot.actuator.v2+json, application/json
内容类型。
当在 web 上使用您的定制执行器端点时,这些操作定义了它们自己的 HTTP 方法:@ReadOperation
( Http.GET
)、@WriteOperation
( Http.POST
)和@DeleteOperation
( Http.DELETE
)。
带有自定义执行器端点的 ToDo 应用
让我们创建一个定制端点(/todo-stats
)来显示数据库中 ToDo 的计数以及完成的数量。此外,我们可以创建一个写操作来完成一个待办事项,甚至创建一个删除待办事项的操作。
让我们创建ToDoStatsEndpoint
来保存定制端点的所有逻辑(参见清单 10-3 )。
package com.apress.todo.actuator;
import com.apress.todo.domain.ToDo;
import com.apress.todo.repository.ToDoRepository;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.boot.actuate.endpoint.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Endpoint(id="todo-stats")
public class ToDoStatsEndpoint {
private ToDoRepository toDoRepository;
ToDoStatsEndpoint(ToDoRepository toDoRepository){
this.toDoRepository = toDoRepository;
}
@ReadOperation
public Stats stats() {
return new Stats(this.toDoRepository.count(),this.toDoRepository.countByCompleted(true));
}
@ReadOperation
public ToDo getToDo(@Selector String id) {
return this.toDoRepository.findById(id).orElse(null);
}
@WriteOperation
public Operation completeToDo(@Selector String id) {
ToDo toDo = this.toDoRepository.findById(id).orElse(null);
if(null != toDo){
toDo.setCompleted(true);
this.toDoRepository.save(toDo);
return new Operation("COMPLETED",true);
}
return new Operation("COMPLETED",false);
}
@DeleteOperation
public Operation removeToDo(@Selector String id) {
try {
this.toDoRepository.deleteById(id);
return new Operation("DELETED",true);
}catch(Exception ex){
return new Operation("DELETED",false);
}
}
@AllArgsConstructor
@Data
public class Stats {
private long count;
private long completed;
}
@AllArgsConstructor
@Data
public class Operation{
private String name;
private boolean successful;
}
}
Listing 10-3com.apress.todo.actuator.ToDoStatsEndpoint.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
清单 10-3 显示了执行诸如显示统计数据(ToDo 的总数和已完成的总数)等操作的定制端点。它获取一个 ToDo 对象,移除它,并将其设置为已完成。我们来复习一下。
-
@Endpoint
。将类型标识为提供有关正在运行的应用的信息的执行器终结点。端点可以通过多种技术公开,包括 JMX 和 HTTP。这是ToDoStatsEndpoint
类,它是执行器的端点。 -
@ReadOperation
。将端点上的方法标识为读取操作(看到该类通过 ID 返回 ToDo)。 -
@Selector
。可以在端点方法的参数上使用选择器,以指示该参数选择端点数据的子集。这是一种修改值的方法,在这种情况下,用于将 ToDo 更新为已完成。 -
@WriteOperation
。将端点上的方法标识为写操作。这与 POST 事件非常相似。 -
@DeleteOperation
。将端点上的方法标识为删除操作。
清单 10-3 非常类似于 REST API,但是在这种情况下,所有这些方法都通过 JMX 协议公开。另外,stats
方法使用toDoRepository
来调用countByCompleted
方法。让我们将其添加到ToDoRepository
接口中(参见清单 10-4 )。
package com.apress.todo.repository;
import com.apress.todo.domain.ToDo;
import org.springframework.data.repository.CrudRepository;
public interface ToDoRepository extends CrudRepository<ToDo,String> {
public long countByCompleted(boolean completed);
}
Listing 10-4com.apress.todo.repository.ToDoRepository.java – v2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
清单 10-4 显示了ToDoRepository
接口的版本 2。这个接口现在有了一个新的方法声明,countByCompleted
;请记住,这是一个名为 query 的方法,Spring Data 模块负责创建适当的 SQL 语句来计算已经完成的 ToDo 的数量。
如您所见,这对于创建自定义端点来说非常简单。现在,如果您运行应用,并转到http://localhost:8080/actuator
,您应该会看到列出的todo-stats
端点(参见图 10-14 )。
图 10-14
http://localhost:8080/actuator
– todo-stats 自定义端点
如果你点击第一个todo-stats
链接,你会看到如图 10-15 所示的内容。
图 10-15
http://localhost:8080/actuator/todo-stats
很简单,对吧?但是其他的行动呢。让我们试用一下。为此,我们将 JMX 与 JConsole 一起使用(它随 JDK 安装一起提供)。您可以打开一个终端窗口并执行jconsole
命令。
-
Select from the
com.apress.todo.ToDoActuatorApplication
list and click Connect. -
Right now there is no secured connection, but it’s OK to click the Insecure Connection button.
-
From the main screen, select the MBeans tab. Expand the
org.springframework.boot
package and the Endpoint folder. You see theTodo-stats
. You can expand it and see all the operations. -
Click the stats item to see the MBeans operation stats.
-
You can click the stats button (that actually is the call to the
stats
method), and you will get.
如你所见,这和浏览网页是一样的。您可以尝试使用completeToDo
操作。
-
Click the
completeToDo
operation. On the right, fill out the ID field withebcf1850563c4de3b56813a52a95e930
, which is the Buy Movie Tickets ToDo that is not completed. -
Click
completeToDo
to get the confirmation (anOperation
object). -
If you redo the stats operation, you should now see that two are completed.
如您所见,通过 JConsole 工具使用 JMX 非常容易。现在,您知道了如何为您需要的数据创建自定义端点。
Spring Boot 执行器健康
如今,我们在系统中寻找可见性,这意味着我们需要密切监控它们并对任何事件做出反应。我记得很久以前,监控服务器的方法是简单的 ping 但现在这还不够。我们不仅监控服务器,还监控系统及其洞察力。我们仍然需要查看我们的系统是否启动了,如果没有,我们需要获得关于该问题的更多信息。
Spring Boot 致动器health
端点营救!/actuator/health
端点提供正在运行的应用的状态或健康检查。它提供了一个特殊的属性,management.endpoint.health.show-details
,您可以使用它来显示关于整个系统的更多信息。以下是可能的值。
-
never
。细节从不显示;这是默认值。 -
when-authorized
。仅向授权用户显示详细信息;您可以通过设置management.endpoint.health.roles
属性来配置角色。 -
always
。所有细节都显示给所有用户。
Spring Boot 执行器提供了收集系统所有信息的HealthIndicator
接口;它返回一个包含所有这些信息的Health
实例。执行器运行状况有几个现成的运行状况指示器,这些指示器通过运行状况聚合器自动配置,以确定系统的最终状态。它非常类似于日志级别。你可以开始工作了。别担心。我将用一个例子来说明这一点。
以下是一些自动配置的运行状况指示器。
-
CassandraHealthIndicator
。检查 Cassandra 数据库是否已启动并正在运行。 -
DiskSpaceHealthIndicator
。检查磁盘空间是否不足。 -
RabbitHealthIndicator
。检查兔子服务器是否启动并运行。 -
RedisHealthIndicator
。检查 Redis 服务器是否已启动并正在运行。 -
DataSourceHealthIndicator
。检查来自数据源的数据库连接。 -
MongoHealthIndicator
。检查 MongoDB 是否启动并运行。 -
MailHealthIndicator
。检查邮件服务器是否启动。 -
SolrHealthIndicator
。检查 Solr 服务器是否启动。 -
JmsHealthIndicator
。检查 JMS 代理是否启动并运行。 -
ElasticsearchHealthIndicator
。检查 ElasticSearch 集群是否启动。 -
Neo4jHealthIndicator
。检查 Neo4j 服务器是否已启动并正在运行。 -
InfluxDBHealthIndicator
。检查 InfluxDB 服务器是否已启动。
还有很多。如果依赖项在您的类路径中,所有这些都是自动配置的;换句话说,您不需要担心配置或使用它们。
让我们测试一下健康指标。
-
确保
application.properties
文件中有management.endpoints.web.exposure.include=*
(在 ToDo 应用中)。 -
将
management.endpoint.health.show-details=always
属性添加到application.properties
文件。 -
如果您运行 ToDo 应用,并访问
http://localhost:8080/actuator/health
,您应该会得到以下内容。
H2 数据库DataSourceHealthIndicator
和DiskSpaceHealthIndicator
正在被自动配置。
-
Add the following dependency to your
pom.xml
file (if you are using Maven).<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
- 1
- 2
- 3
- 4
- 5
如果您使用的是 Gradle,将下面的依赖项添加到您的
build.gradle
文件中。compile('org.springframework.boot:spring-boot-starter-amqp')
- 1
- 2
-
You guessed right. We are adding AMQP dependencies. Re-run the app and take a look at the
/actuator/health
endpoint .
因为您添加了spring-boot-starter-amqp
依赖项,所以它是关于 RabbitMQ 代理的,并且您有执行器,RabbitHealthIndicator
被自动配置为到达本地主机(或具有spring.rabbitmq.*
属性设置的特定代理)。如果它是活的,那么它报告它。在这种情况下,您会在日志中看到一些连接失败,在health
端点中,您会看到系统关闭。如果您有一个 RabbitMQ 代理(来自前一章),您可以运行它(用rabbitmq-server
命令)并刷新health
端点。你看,一切都准备好了!
就这样。这就是你如何使用所有开箱即用的健康指标。添加所需的依赖项——这样就完成了!
带有自定义健康指示器的待办事项应用
现在轮到 ToDo 的应用拥有自己的自定义健康指标了。实现一个非常容易。您需要实现HealthIndicator
接口并用Health
实例返回期望的状态。
创建ToDoHealthCheck
来访问一个FileSystem
路径,并检查它是否可用、可读和可写(参见清单 10-5 )。
package com.apress.todo.actuator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.Health;
import org.springframework.stereotype.Component;
import java.io.File;
@Component
public class ToDoHealthCheck implements HealthIndicator {
private String path;
public ToDoHealthCheck(@Value("${todo.path:/tmp}")String path){
this.path = path;
}
@Override
public Health health() {
try {
File file = new File(path);
if(file.exists()){
if(file.canWrite())
return Health.up().build();
return Health.down().build();
}else{
return Health.outOfService().build();
}
}catch(Exception ex) {
return Health.down(ex).build();
}
}
}
Listing 10-5com.apress.todo.actuator.ToDoHealthCheck.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
清单 10-5 显示了标记为@Component
的ToDoHealthCheck
类,它是HealthIndicator
接口的实现。有必要实现health
方法(参见Health
类有一个流畅的 API 来帮助创建健康状态。分析代码,看看 path 变量必须设置成什么(环境中的一个属性,命令行中的一个参数,或者application.properties
);否则,默认使用/tmp
。如果这个路径存在,那么它检查你是否能写它;如果是,它公开UP
状态,如果不是,它报告一个DOWN
状态。如果路径不存在,它报告一个OUT_OF_SERVICE
状态。如果有任何异常,它会显示一个DOWN
状态。
在前面的代码中,需要一个todo.path
属性。让我们创建一个保存该属性信息的ToDoProperties
类。你已经知道了(见清单 10-6 )。
package com.apress.todo.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "todo")
public class ToDoProperties {
private String path;
}
Listing 10-6com.apress.todo.config.ToDoProperties.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
如你所见,这很简单。如果你还记得,要使用一个@ConfigurationProperties
标记的类,就要用@EnableConfigurationProperties
来调用它。让我们创建ToDoConfig
类来支持它(参见清单 10-7 )。
package com.apress.todo.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@EnableConfigurationProperties(ToDoProperties.class)
@Configuration
public class ToDoConfig {
}
Listing 10-7com.apress.todo.config.ToDoConfig.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
这个班没什么特别的。在新属性中添加application.properties
文件。
todo.path=/tmp/todo
- 1
- 2
如果您使用的是 Windows,您可以尝试一些类似
todo.path=C:\tmp\todo
- 1
- 2
查看文档中的正确字符。那么,你都准备好了。如果您重新运行您的 ToDo 应用,并查看/actuator/health
的响应,您应该会得到以下内容。
JSON 响应中有toDoHealthCheck
键;并且与逻辑设置相匹配。接下来,通过创建一个可写的/tmp/todo
目录来解决这个问题。
您可以通过使用application.properties
文件中的以下属性来配置状态/严重性顺序(例如,日志记录级别)。
management.health.status.order=FATAL, DOWN, OUT_OF_SERVICE, UNKNOWN, UP
- 1
- 2
如果使用 HTTP 上的health
端点,每个状态/严重性都有自己的 HTTP 代码或映射可用。
-
向下–503
-
停止服务–503
-
向上–200
-
下降-200
您可以通过以下方式使用自己的代码
management.health.status.http-mapping.FATAL=503
- 1
- 2
此外,您可以使用Health.status("IN_BAD_CONDITION").build();
创建自己的状态,如IN_BAD_CONDITION
使用 Spring Boot 执行器创建自定义健康指示器非常简单!
Spring Boot 执行器指标
如今,每个系统都需要被监控。有必要通过观察每个应用中发生的事情来保持可见性,无论是单独还是整体。Spring Boot 致动器提供基本的度量和集成,并自动配置千分尺( http://micrometer.io
)。
Micrometer 为许多流行的监控系统提供了一个简单的仪表客户端界面;换句话说,您可以编写一次监控代码,并使用任何其他第三方系统,如 Prometheus、网飞 Atlas、CloudWatch、Datadog、Graphite、Ganglia、JMX、InfluxDB/Telegraf、New Relic、StatsD、SignalFX 和 WaveFront(以及更多即将推出的系统)。
记住 Spring Boot 执行器有/actuator/metrics
。如果您运行 ToDo 应用,您将获得基本指标;您在前面的章节中已经了解了这一点。我没有向您展示的是如何使用千分尺创建您的自定义指标。这个想法是编写一次代码,并使用任何其他第三方监测工具。Spring Boot 致动器和测微计将这些指标暴露给所选的监控工具。
让我们直接跳到实现测微计代码,并使用 Prometheus 和 Grafana 来看看它有多容易使用。
带测微计的 ToDo 应用:普罗米修斯和格拉夫纳
让我们实现测微计代码,并使用普罗米修斯和格拉夫纳。
到目前为止,我们已经看到,一旦 Spring Data REST 模块看到所有扩展了Repository<T,ID>
接口的接口,它就会代表我们创建 web MVC 控制器(REST 端点)。想象一下,我们需要拦截这些 web 请求,并开始创建一个聚合;这个指标告诉我们一个特定的 REST 端点和 HTTP 方法被请求了多少次。这有助于我们确定哪个端点适合微服务。这个拦截器还获取所有请求,包括/actuator
请求。
为此,Spring MVC 提供了一个我们可以使用的HandlerInterceptor
接口。它有三个默认方法,但我们只需要其中一个。让我们从创建ToDoMetricInterceptor
类开始(参见清单 10-8 )。
package com.apress.todo.interceptor;
import io.micrometer.core.instrument.MeterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ToDoMetricInterceptor implements HandlerInterceptor {
private static Logger log = LoggerFactory.getLogger(ToDoMetricInterceptor.class);
private MeterRegistry registry;
private String URI, pathKey, METHOD;
public ToDoMetricInterceptor(MeterRegistry registry) {
this.registry = registry;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
URI = request.getRequestURI();
METHOD = request.getMethod();
if (!URI.contains("prometheus")){
log.info(" >> PATH: {}",URI);
log.info(" >> METHOD: {}", METHOD);
pathKey = "api_".concat(METHOD.toLowerCase()).concat(URI.replaceAll("/","_").toLowerCase());
this.registry.counter(pathKey).increment();
}
}
}
Listing 10-8com.apress.todo.interceptor.ToDoMetricInterceptor.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
清单 10-8 显示了ToDoMetricInterceptor
类(它实现了HandlerInterceptor
接口)。该接口有三种默认方法:preHandle
、postHandle
和afterCompletion
。这个类只实现了afterCompletion
方法。这个方法有HttpServletRequest
,有助于发现请求了哪个端点和 HTTP 方法。
您的类使用的是MeterRegistry
实例,它是 Micrometer 框架的一部分。实现从请求实例中获取路径和方法,并使用counter
方法来递增。pathKey
很简单;如果有对/toDos
端点的GET
请求,则pathKey
为api_get_todos
。如果对/toDos
端点有一个POST
请求,那么pathKey
就是api_post_todos
,依此类推。因此,如果有几个对/toDos
的请求,registry
递增(使用那个pathKey
)并聚合到现有值。
接下来,让我们确保ToDoMetricInterceptor
正在被 Spring MVC 拾取和配置。打开ToDoConfig
类并添加一个MappedInterceptor
bean(参见清单 10-9 的版本 2)。
package com.apress.todo.config;
import com.apress.todo.interceptor.ToDoMetricInterceptor;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.MappedInterceptor;
@EnableConfigurationProperties(ToDoProperties.class)
@Configuration
public class ToDoConfig {
@Bean
public MappedInterceptor metricInterceptor(MeterRegistry registry) {
return new MappedInterceptor(new String[]{"/**"},
new ToDoMetricInterceptor(registry));
}
}
Listing 10-9com.apress.todo.config.ToDoConfig.java v2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
清单 10-9 显示了新的ToDoConfig
类,它有MappedInterceptor
。它通过使用"/**"
匹配器为每个请求使用ToDoMetricInterceptor
。
接下来,让我们添加两个将数据导出到 JMX 和普罗米修斯的依赖项。如果您有 Maven,您可以向pom.xml
文件添加以下依赖项。
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-jmx</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如果您使用的是 Gradle,您可以将以下依赖项添加到build.gradle
。
compile('io.micrometer:micrometer-registry-jmx')
compile('io.micrometer:micrometer-registry-prometheus')
- 1
- 2
- 3
Spring Boot 执行器自动配置和注册每个千分尺注册表,在这种情况下,JMX 和普罗米修斯。对于 Prometheus,致动器配置/actuator/prometheus
端点。
先决条件:使用 Docker
在使用指标测试 ToDo 应用之前,有必要安装 Docker(尝试安装最新版本)。
-
从
https://docs.docker.com/install/
安装 Docker CE(社区版) -
安装码头由组成
我为什么选择 Docker?嗯,这是一种安装我们需要的东西的简单方法。我们将在接下来的章节中再次用到它。Docker Compose 通过使用 Docker 允许我们使用 DNS 名称的内部网络来方便安装 Prometheus 和 Grafana。
坞站-组合. yml
这是用来启动普罗米修斯和格拉夫纳的docker-compose.yml
文件。
version: '3.1'
networks:
micrometer:
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus/:/etc/prometheus/
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
ports:
- 9090:9090
networks:
- micrometer
restart: always
grafana:
image: grafana/grafana
user: "104"
depends_on:
- prometheus
volumes:
- ./grafana/:/etc/grafana/
ports:
- 3000:3000
networks:
- micrometer
restart: always
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
您可以用任何编辑器创建这个文件。记住,这是一个 YML 文件,没有缩进的制表符。你需要创建两个文件夹:prometheus
和grafana
。在每个文件夹中,都有一个文件。
在prometheus
文件夹中,有一个prometheus.yml
文件,内容如下。
global:
scrape_interval: 5s
evaluation_interval: 5s
scrape_configs:
- job_name: 'todo-app'
metrics_path: '/actuator/prometheus'
scrape_interval: 5s
static_configs:
- targets: ['host.docker.internal:8080']
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
关于这个文件最重要的是metrics_path
和 targets 键。当发现micrometer-registry-prometheus
时,Spring Boot 执行器自动配置/actuator/prometheus
端点。该值是metrics_path
所必需的。另一个非常重要的值是targets
键。普罗米修斯每 5 秒刮一次/actuator/prometheus
终点。它需要知道自己的位置(它使用的是host.docker.internal
域名)。这是 Docker 寻找其主机的部分(正在运行的localhost:8080/todo-actuator
应用)。
grafana
文件夹包含一个空的grafana.ini
文件。为了确保 Grafana 采用缺省值,您显示了以下目录结构。
运行待办事项应用指标
现在,是时候开始测试和配置 Grafana 来查看有用的指标了。运行您的待办事项应用。检查日志并确保/actuator/prometheus
端点在那里。
打开一个终端,转到有docker-compose.yml
文件的地方,执行下面的命令。
$ docker-compose up
- 1
- 2
这个命令行启动 docker-compose 引擎,它下载图像并运行它们。让我们通过打开浏览器并点击http://localhost:9090/targets
来确保 Prometheus 应用正常工作。
这意味着prometheus.yml
配置被成功采用。换句话说,普罗米修斯是在刮http://localhost:8080/actuator/prometheus
端点。
接下来,我们来配置 Grafana。
- 打开另一个浏览器标签,点击
http://localhost:3000
。
可以用admin/admin
作为凭证。
-
You can press the Skip button the following screen.
-
Click the Add Data Source icon.
-
填写所有必填字段。重要的是
-
名称:
todo-app
-
类型:
Prometheus
-
URL:
http://prometheus:9090
-
刮擦间隔:
3s
-
-
Click the Save button.
URL 值
http://prometheus:9090
指的是 docker-
compose 服务,是 docker-compose 提供的内部 DNS,不需要做本地主机。您可以保留默认的其他值,并点击保存&测试。如果一切按预期运行,您会在页面底部看到一个绿色横幅,上面写着,数据源正在运行。 -
You can go home by going back or pointing the browser to
http://localhost:3000
. You can click the New Dashboard button on the homepage. -
You can click the Graph icon, a panel appears. Click Panel Title and then click Edit.
-
Configure two queries, the
api_get_total
andapi_post_todos_total
, which were generated as metrics by the Micrometer and Spring Boot Actuator for Prometheus engine. -
Perform requests to the
/toDos
(several times) and post to the/toDos
endpoints. You see something like the next figure.
恭喜你!您已经使用 Micrometer、Prometheus 和 Grafana 创建了自定义指标。
Spring Boot 和格拉夫纳的一般统计
我为 Grafana 找到了一个非常有用的配置,它允许你利用 Spring Boot 执行器提供的每一个指标。这个配置可以导入到 Grafana 中。
从 https://grafana.com/dashboards/6756
下载此配置。文件名为spring-boot-statistics_rev2.json
。你接下来需要它。
在 Grafana 主页的左上角(http://localhost:3000
,点击 Grafana 图标,打开侧边栏。点击+
符号并选择导入。
设置默认值,但是在 Prometheus 字段中,选择 todo-app(您之前配置的数据源)。
点击导入—然后瞧!您拥有一个包含所有 Spring Boot 执行器指标和监控的完整仪表板!
花点时间回顾每一张图。所有数据都来自于/actuator/prometheus
端点。
您可以通过在另一个终端窗口中执行以下命令来关闭 docker-compose。
$ docker-compose down
- 1
- 2
注意
你可以在 Apress 网站或者 GitHub 的 https://github.com/Apress/pro-spring-boot-2
或者我的个人知识库 https://github.com/felipeg48/pro-spring-boot-2nd
找到这部分的解决方法。
摘要
本章讨论了 Spring Boot 执行器,包括它的端点和它的可定制性。使用执行器模块,您可以监控您的 Spring Boot 应用,从使用/health
端点到使用/httptrace
进行更细粒度的调试。
您了解了可以使用千分尺并插入任何第三方工具来使用 Spring Boot 执行器指标。
在下一章中,您将进一步了解如何使用 Spring Integration 和 Spring Cloud Stream。