SpringBoot2-高级教程-四-

78 阅读25分钟

SpringBoot2 高级教程(四)

原文: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 )。

img/340891_2_En_9_Fig1_HTML.jpg

图 9-1

Spring 初始化 zr

从依赖关系中可以看出,您重用了前面章节中的 JPA 和 REST Repositories 代码。代替使用文本消息(一种测试消息传递的常用方法),您使用一个ToDo实例,它被转换为 JSON 格式。为此,您需要手动将下一个依赖项添加到您的pom.xmlbuild.gradle中。

如果您使用的是 Maven,将下面的依赖项添加到您的pom.xml文件中。

<dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
</dependency>

如果您使用的是 Gradle,将下面的依赖项添加到您的build.gradle文件中。

compile("com.fasterxml.jackson.core:jackson-databind")

这个依赖项提供了使用 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

清单 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

清单 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接口。这个接口提供了toMessagefromMessage方法,您可以在其中插入任何适合对象转换的技术。

让我们为生产者和消费者创建一个使用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

清单 9-3 显示了应用使用的ToDoConfig类。我们来分析一下。

  • @Configuration。这是一个已知的注释,它标记了用于配置 SpringApplication 上下文的类。

  • MessageConverter。方法jacksonJmsMessageConverter返回 MessageConverter 接口。这个接口促进了toMessagefromMessage方法的实现,这有助于插入您想要使用的任何序列化/转换。在这种情况下,您通过使用MappingJackson2MessageConverter类实现来使用 JSON 转换器。这个类是 Spring 框架中的默认实现之一。它使用 Jackson 库,这些库使用映射器在 JSON 和对象之间进行转换。因为您使用的是ToDo实例,所以有必要指定一个目标类型(setTargetType),这意味着 JSON 对象被作为文本和一个 type-id 属性名(setTypeIdPropertyName)来处理,该属性名标识了生产者和消费者之间的属性。type-id 属性名必须始终与生产者和消费者相匹配。它可以是你需要的任何值(最好是你能识别的值,因为它用于设置要与 JSON 相互转换的类的名称(包括包));换句话说,com.apress.todo.domain.Todo类必须在生产者和消费者之间共享,以便映射器知道从哪里获取该类。

  • JmsListenerContainerFactoryjmsFactory方法返回JmsListenerContainerFactory。这个 bean 需要ConnectionFactoryDefaultJmsListenerContainerFactoryConfigurer(都是由 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

清单 9-4 显示了为每个消息调用的验证器类,并验证description字段不为空。这个类实现了验证器接口,并实现了supportsvalidate方法。

这是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());
    }
}

如您所见,这个类实现了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

清单 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

运行待办事项应用

接下来,让我们创建一个 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

清单 9-6 显示了使用ToDoProducer实例和目的地(来自todo.jms.destination属性)发送消息的配置类。

要运行这个应用,您可以使用您的 IDE(如果您导入了它)或者您可以使用 Maven 包装器。

./mvnw spring-boot:run

或者是格拉德包装。

./gradlew bootRun

您应该从日志中获得以下文本。

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)

您可以看一看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;
}

远程 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

对于 ActiveMQ 代理,您可以使用更多的属性。去 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.htmlspring.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

如果你使用的是 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 显示了这些不同的交换类型。

img/340891_2_En_9_Fig2_HTML.jpg

图 9-2

AMQP 交易所/绑定/队列

图 9-2 显示了可能的交换类型。因此,主要思想是向一个交换发送一个消息,包括一个路由关键字,然后交换根据它的类型将消息传递给队列(或者如果路由关键字不匹配,它就不传递)。

默认交换自动绑定到每个创建的队列。直接交换通过路由关键字绑定到队列;您可以将这种交换类型视为一对一绑定。话题交流类似于直接交流;唯一的区别是,在它的绑定中,您可以在其路由关键字中添加一个通配符。标题交换类似于主题交换;唯一的区别是绑定是基于消息头的(这是一个非常强大的交换,您可以对它的消息头执行 allany 表达式)。扇出交换机将消息复制到所有绑定队列;你可以把这种交流看作是一种信息广播。

你可以在 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 )。

img/340891_2_En_9_Fig3_HTML.jpg

图 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

清单 9-7 显示了ToDoProducer.java类。让我们检查一下。

  • @Component。这个注释标记了 Spring 容器要拾取的类。

  • RabbitTemplateRabbitTemplate是一个助手类,它简化了对 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

清单 9-8 显示了ToDoConsumer.java类。让我们检查一下。

  • @Component。你已经知道这个注释了。它标记了 Spring 容器要拾取的类。

  • @RabbitListener。该注释标记了为任何传入消息创建处理程序的方法(因为您也可以在类中使用该注释),这意味着它创建了一个连接到 RabbitMQ 队列的侦听器,并将该消息传递给该方法。在幕后,监听器通过使用正确的消息转换器(一个org.springframework.amqp.support.converter.MessageConverter接口的实现)尽最大努力将消息转换成适当的类型。这个接口属于spring-amqp项目);在这种情况下,它从 JSON 转换成一个ToDo实例。

ToDoProducerToDoConsumer可以看出,代码非常简单。如果您只使用 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

清单 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

将以下键(声明发送/消费队列)添加到您的application.properties文件中。

todo.amqp.queue=spring-boot

在运行您的示例之前,请确保您的 RabbitMQ 服务器已经启动并正在运行。您可以通过打开终端并执行以下命令来启动它。

$ rabbitmq-server

使用来宾/来宾凭证访问http://localhost:15672/,确保您可以访问 RabbitMQ web 控制台。如果您在访问 web 控制台时遇到问题,请确保通过运行以下命令启用了管理插件。

$ rabbitmq-plugins list

如果整个列表中的复选框未被选中,则管理插件尚未启用(通常在全新安装时发生)。要启用这个插件,您可以执行以下命令。

$ rabbitmq-plugins enable rabbitmq_management --online

现在,你可以再试一次。然后,您应该会看到一个类似于图 9-4 的 web 控制台。

img/340891_2_En_9_Fig4_HTML.jpg

图 9-4

rabbitmq web 控制台管理

图 9-4 显示了 RabbitMQ web 控制台。现在,您可以使用 IDE 像往常一样运行项目了。如果您使用的是 Maven,请执行

$ ./mvnw spring-boot:run

如果您使用的是 Gradle,请执行

$./gradlew bootRun

在您执行这个命令之后,您应该得到类似于下面的输出。

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)

如果您查看 RabbitMQ web 控制台的 Queues 选项卡,您应该已经定义了spring-boot队列(参见图 9-5 )。

img/340891_2_En_9_Fig5_HTML.jpg

图 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

清单 9-11 显示了ToDoSender类的修改版本。让我们检查这个新版本。

  • @EnableScheduling。这个注释告诉(通过自动配置)Spring 容器需要创建org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor类。它根据@Scheduled注释中的fixedRatefixedDelaycron表达式,注册所有用@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

你可以在 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

如果您使用的是不同版本的 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 )。

img/340891_2_En_9_Fig6_HTML.jpg

图 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

清单 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

清单 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

清单 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

运行待办事项应用

在运行 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

这个输出表明 Redis 已经准备好,正在监听端口 6379。您可以打开一个新的终端窗口并执行以下命令。

$ redis-cli

这是一个连接到 Redis 服务器的 shell 客户端。您可以通过执行以下命令订阅“todos”主题。

127.0.0.1:6379> SUBSCRIBE todos
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "todos"
3) (integer) 1

现在,您可以像往常一样运行项目(通过在您的 ide 中运行或者使用 Maven 或 Gradle)。如果您使用的是 Maven,请执行

$ ./mvnw spring-boot:run

执行该命令后,您的日志中应该会有类似于以下输出的内容。

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

如果您看一看 Redis shell,您应该会看到如下所示的内容。

1) "message"
2) "todos"
3) "{\"id\":null,\"description\":\"workout tomorrow morning!\",\"created\":null,\"modified\":null,\"completed\":false}"

当然,你可以在浏览器的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

你可以在 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 )。

img/340891_2_En_9_Fig7_HTML.jpg

图 9-7

Spring 初始化 zr

您可以重用和复制/粘贴ToDoToDoRepository类。您还需要添加以下依赖项;如果您使用的是 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>

如果您使用的是 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')

这些依赖项创建了您需要连接到消息传递代理的 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

清单 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

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

清单 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

因为客户端是一个 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>&nbsp" + json.description + "</span><br/>";
                $("#output").append(result);
            });

        });

    });
</script>
</body>
</html>

Listing 9-18src/main/resources/static/index.html

清单 9-18 显示了使用SockJS类连接到/stomp端点的客户端 index.html。它订阅了/todo/new主题,并一直等到 get a new ToDo 被添加到列表中。对 JavaScript 库和 CSS 的引用是 WebJars 类资源。

运行待办事项应用

现在,您已经准备好启动您的待办事项应用。您可以像往常一样运行应用,既可以使用 IDE,也可以在命令行中运行。如果您使用的是 Maven,请执行

$ ./mvnw spring-boot:run

如果您使用的是 Gradle,请执行

$ ./gradlew bootRun

打开浏览器,进入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

喜欢的话可以再加。在您的浏览器中,您会看到待办事项(参见图 9-8 )。

img/340891_2_En_9_Fig8_HTML.jpg

图 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 )。

img/340891_2_En_10_Fig1_HTML.jpg

图 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

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

在运行 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]}"  ...

默认情况下,执行器模块公开了您可以访问的三个端点。

  • /actuator/health。此端点提供基本的应用健康信息。如果从浏览器或命令行进行访问,您会得到以下响应:

    {
        "status": "UP"
    }
    
    
  • /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
    
    

    您会得到以下内容:

    {
        "application-name": "todo-actuator",
        "developer": {
            "name": "Awesome Developer",
            "email": "awesome@example.com"
        }
    }
    
    
  • /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
        }
      }
    }
    
    

默认情况下,除了/actuator/shutdown端点之外,所有端点(还有更多)都是启用的;但是为什么只暴露两个端点(健康和信息)?实际上,所有这些都是通过 JMX 暴露的,这是因为其中一些包含敏感信息;所以,知道通过网络暴露什么信息是很重要的。

如果您想在 web 上公开它们,有两个属性:management.endpoints.web.exposure.includemanagement.endpoints.web.exposure.exclude。您可以用逗号将它们分开列出,也可以使用*将它们全部列出。

这同样适用于通过具有属性的 JMX 公开端点。

management.endpoints.jmx.exposure.includemanagement.endpoints.jmx.exposure.exclude。请记住,默认情况下,所有端点都通过 JMX 公开。

正如我之前提到的,您不仅有公开端点的方法,还有启用它们的方法。可以用下面的语义:management.endpoint.<ENDPOINT-NAME>.enabled。所以,如果你想启用/actuator/shutdown(默认情况下是禁用的),你需要在application.properties中这样做。

management.endpoint.shutdown.enabled=true

您可以将以下属性添加到您的application.properties文件中,以公开所有的 web actuator 端点。

management.endpoints.web.exposure.include=*

如果你看一下输出,你会得到更多的执行器端点,比如/actuator/beans/actuator/conditions等等。让我们更详细地回顾一下其中的一些。

/执行器

/actuator端点是所有端点的前缀,但是如果您访问它,它会为所有其他端点提供一个基于超媒体的发现页面。所以,如果你去http://localhost:8080/actuator,你应该看到类似于图 10-2 的东西。

img/340891_2_En_10_Fig2_HTML.jpg

图 10-2

http://localhost:8080/actuator

/致动器/条件

此端点显示自动配置报告。它给你两组:positiveMatchesnegativeMatches。请记住,Spring Boot 的主要特性是它通过查看类路径和依赖项来自动配置您的应用。这与您添加到pom.xml文件中的起始 POM 和额外的依赖项有很大关系。如果你去http://localhost:8080/actuator/conditions,你应该会看到类似于图 10-3 的东西。

img/340891_2_En_10_Fig3_HTML.jpg

图 10-3

http://localhost:8080/actuator/conditions

/执行器/bean

该端点显示应用中使用的所有 Spring beans。请记住,尽管您添加了几行代码来创建一个简单的 web 应用,但在幕后,Spring 开始创建运行您的应用所需的所有 beans。如果你去http://localhost:8080/actuator/beans,你应该会看到类似于图 10-4 的东西。

img/340891_2_En_10_Fig4_HTML.jpg

图 10-4

http://localhost:8080/actuator/beans

/执行器/配置

这个端点列出了由@ConfigurationPropertiesbean 定义的所有配置属性,这是我在前面的章节中向您展示过的。记住,你可以添加你自己的配置属性前缀,它们可以在application.properties或 YAML 文件中定义和访问。图 10-5 显示了该端点的一个例子。

img/340891_2_En_10_Fig5_HTML.jpg

图 10-5

http://localhost:8080/actuator/configprops

/执行器/线程转储

该端点执行应用的线程转储。它显示了所有正在运行的线程以及运行您的应用的 JVM 的堆栈跟踪。前往http://localhost:8080/actuator/threaddump终点(见图 10-6 )。

img/340891_2_En_10_Fig6_HTML.jpg

图 10-6

http://localhost:8080/actuator/threaddump

/执行器/环境

这个端点公开了 Spring 的ConfigurableEnvironment接口的所有属性。这将显示所有活动的概要文件和系统环境变量以及所有应用属性,包括 Spring Boot 属性。转到http://localhost:8080/actuator/env(见图 10-7 )。

img/340891_2_En_10_Fig7_HTML.jpg

图 10-7

http://localhost:8080/actuator/env

/执行器/健康

此端点显示应用的健康状况。默认情况下,它向您显示整体系统健康状况。

{
   "status": "UP"
}

如果您想查看关于其他系统的更多信息,您需要在application.properties文件中使用以下属性。

management.endpoint.health.show-details=always

修改application.properties并重新运行 ToDo 应用。如果您有一个数据库应用(我们有),您会看到数据库状态,默认情况下,您还会看到来自您系统的diskSpace。如果你正在运行你的 app,你可以去http://localhost:8080/actuator/health(见图 10-8 )。

img/340891_2_En_10_Fig8_HTML.jpg

图 10-8

http://localhost:8080/actuator/health -详细信息

/执行器/信息

此端点显示公共应用信息。这意味着您需要将这些信息添加到application.properties中。如果您有多个 Spring Boot 应用,建议您添加它。

/执行器/记录器

此端点显示应用中可用的所有记录器。图 10-9 显示了特定包装的水平。

img/340891_2_En_10_Fig9_HTML.jpg

图 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"
}

/执行器/指标

这个端点显示当前应用的指标信息,在这里您可以确定它使用了多少内存、有多少内存可用、应用的正常运行时间、使用的堆的大小、使用的线程数量等等(参见图 10-10 和图 10-11 )。

img/340891_2_En_10_Fig10_HTML.jpg

图 10-10

http://localhost:8080/actuator/metrics

您可以通过在端点末尾添加名称来访问每个指标;所以如果你想了解更多关于jvm.memory.max的信息,你需要到达http://localhost:8080/actuator/metrics/jvm.memory.max(见图 10-11 )。

img/340891_2_En_10_Fig11_HTML.jpg

图 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 )。

img/340891_2_En_10_Fig12_HTML.jpg

图 10-12

http://localhost:8080/actuator/mappings

/致动器/关闭

默认情况下,此端点未启用。它允许应用正常关闭。这个端点是敏感的,这意味着它可以安全地使用,也应该如此。如果您的应用正在运行,您现在可以停止它。如果您想要启用/actuator/shutdown端点,您需要向application.properties添加以下内容。

management.endpoint.shutdown.enabled=true

保护这个端点是明智的。您需要将spring-boot-starter-security依赖项添加到您的pom.xml(如果您使用的是 Maven)。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

如果您使用的是 Gradle,您可以将下面的依赖项添加到您的build.gradle中。

complie ('org.springframework.boot: spring-boot-starter-security')

请记住,通过添加安全性依赖项,您将默认启用安全性。用户名为user,密码打印在日志中。此外,您可以通过使用内存、数据库或 LDAP 用户来建立更好的安全性;有关更多信息,请参见 Spring Boot 安全性章节。

现在,让我们添加management.endpoint.shutdown.enabled=truespring-boot-starter-security依赖项并重新运行应用。运行应用后,查看日志并保存打印的密码,以便可以在/actuator/shutdown端点上使用。

...
Using default security password: 2875411a-e609-4890-9aa0-22f90b4e0a11
...

现在,如果您打开一个终端,您可以执行以下命令。

$ 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..."}

正如您从这个输出中看到的,您正在使用一个POST方法来访问/actuator/shutdown端点,并且您正在传递之前打印的用户和密码。结果是Shutting down, bye..的消息。当然,你的申请被终止了。同样,知道这个特定的端点必须在任何时候都受到保护是很重要的。

/actuator/httptrace

这个端点显示跟踪信息,通常是最后几个 HTTP 请求。这个端点对于查看所有请求信息和返回的信息以在 HTTP 级别调试应用非常有用。您可以运行您的应用并转到http://localhost:8080/actuator/httptrace。你应该会看到类似于图 10-13 的东西。

img/340891_2_En_10_Fig13_HTML.jpg

图 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

如果您的应用正在运行,请停止并重新运行它。

通常在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

如果您重新运行您的应用,您会看到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

该配置的端点为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来保护您的致动器端点;这是通过HttpSecurityRequestMatcher配置实现的。

@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();
      }

}

为了安全起见,访问端点需要ENDPOINT_ADMIN角色,知道这一点很重要。

配置端点

默认情况下,您会看到执行器端点缓存响应不接受任何参数的读取操作;所以如果需要改变这种行为,可以使用management.endpoint.<endpoint-name>.cache.time-to-live属性。作为另一个例子,如果您需要更改/actuator/beans缓存,您可以将以下内容添加到application.properties文件中。

management.endpoint.beans.cache.time-to-live=10s

实现自定义执行器端点

您可以扩展或创建自定义执行器端点。你需要用@Endpoint标记你的类,也要用@ReadOperation@WriteOperation@DeleteOperation标记你的方法;默认情况下,您的端点通过 JMX 和基于 HTTP 的 web 公开。

您可以更具体地决定是否只想向 JMX 公开您的端点,然后将您的类标记为@JmxEndpoint。如果你只是在网络上需要它,那么你可以用@WebEndpoint来标记你的类。

当创建方法时,您可以接受参数,这些参数被转换成ApplicationConversionService实例所需的正确类型。这些类型使用了application/vnd.spring-boot.actuator.v2+jsonapplication/json内容类型。

您可以在任何方法签名中返回任何类型(甚至是voidVoid)。通常,返回的内容类型因类型而异。如果是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

清单 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

清单 10-4 显示了ToDoRepository接口的版本 2。这个接口现在有了一个新的方法声明,countByCompleted;请记住,这是一个名为 query 的方法,Spring Data 模块负责创建适当的 SQL 语句来计算已经完成的 ToDo 的数量。

如您所见,这对于创建自定义端点来说非常简单。现在,如果您运行应用,并转到http://localhost:8080/actuator,您应该会看到列出的todo-stats端点(参见图 10-14 )。

img/340891_2_En_10_Fig14_HTML.jpg

图 10-14

http://localhost:8080/actuator - todo-stats 自定义端点

如果你点击第一个todo-stats链接,你会看到如图 10-15 所示的内容。

img/340891_2_En_10_Fig15_HTML.jpg

图 10-15

http://localhost:8080/actuator/todo-stats

很简单,对吧?但是其他的行动呢。让我们试用一下。为此,我们将 JMX 与 JConsole 一起使用(它随 JDK 安装一起提供)。您可以打开一个终端窗口并执行jconsole命令。

  1. Select from the com.apress.todo.ToDoActuatorApplication list and click Connect.

    img/340891_2_En_10_Fig16_HTML.jpg

  2. Right now there is no secured connection, but it’s OK to click the Insecure Connection button.

    img/340891_2_En_10_Fig17_HTML.jpg

  3. From the main screen, select the MBeans tab. Expand the org.springframework.boot package and the Endpoint folder. You see the Todo-stats. You can expand it and see all the operations.

    img/340891_2_En_10_Fig18_HTML.jpg

  4. Click the stats item to see the MBeans operation stats.

    img/340891_2_En_10_Fig19_HTML.jpg

  5. You can click the stats button (that actually is the call to the stats method), and you will get.

    img/340891_2_En_10_Fig20_HTML.jpg

如你所见,这和浏览网页是一样的。您可以尝试使用completeToDo操作。

  1. Click the completeToDo operation. On the right, fill out the ID field with ebcf1850563c4de3b56813a52a95e930, which is the Buy Movie Tickets ToDo that is not completed.

    img/340891_2_En_10_Fig21_HTML.jpg

  2. Click completeToDo to get the confirmation (an Operation object).

    img/340891_2_En_10_Fig22_HTML.jpg

  3. If you redo the stats operation, you should now see that two are completed.

    img/340891_2_En_10_Fig23_HTML.jpg

如您所见,通过 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 服务器是否已启动。

还有很多。如果依赖项在您的类路径中,所有这些都是自动配置的;换句话说,您不需要担心配置或使用它们。

让我们测试一下健康指标。

img/340891_2_En_10_Fig24_HTML.jpg

  1. 确保application.properties文件中有management.endpoints.web.exposure.include=*(在 ToDo 应用中)。

  2. management.endpoint.health.show-details=always属性添加到application.properties文件。

  3. 如果您运行 ToDo 应用,并访问http://localhost:8080/actuator/health,您应该会得到以下内容。

H2 数据库DataSourceHealthIndicatorDiskSpaceHealthIndicator正在被自动配置。

  1. 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>
    
    

    如果您使用的是 Gradle,将下面的依赖项添加到您的build.gradle文件中。

    compile('org.springframework.boot:spring-boot-starter-amqp')
    
    
  2. You guessed right. We are adding AMQP dependencies. Re-run the app and take a look at the /actuator/health endpoint .

    img/340891_2_En_10_Fig25_HTML.jpg

因为您添加了spring-boot-starter-amqp依赖项,所以它是关于 RabbitMQ 代理的,并且您有执行器,RabbitHealthIndicator被自动配置为到达本地主机(或具有spring.rabbitmq.*属性设置的特定代理)。如果它是活的,那么它报告它。在这种情况下,您会在日志中看到一些连接失败,在health端点中,您会看到系统关闭。如果您有一个 RabbitMQ 代理(来自前一章),您可以运行它(用rabbitmq-server命令)并刷新health端点。你看,一切都准备好了!

img/340891_2_En_10_Fig26_HTML.jpg

就这样。这就是你如何使用所有开箱即用的健康指标。添加所需的依赖项——这样就完成了!

带有自定义健康指示器的待办事项应用

现在轮到 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

清单 10-5 显示了标记为@ComponentToDoHealthCheck类,它是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

如你所见,这很简单。如果你还记得,要使用一个@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

这个班没什么特别的。在新属性中添加application.properties文件。

todo.path=/tmp/todo

如果您使用的是 Windows,您可以尝试一些类似

todo.path=C:\\tmp\\todo

查看文档中的正确字符。那么,你都准备好了。如果您重新运行您的 ToDo 应用,并查看/actuator/health的响应,您应该会得到以下内容。

img/340891_2_En_10_Fig27_HTML.jpg

JSON 响应中有toDoHealthCheck键;并且与逻辑设置相匹配。接下来,通过创建一个可写的/tmp/todo目录来解决这个问题。

img/340891_2_En_10_Fig28_HTML.jpg

您可以通过使用application.properties文件中的以下属性来配置状态/严重性顺序(例如,日志记录级别)。

management.health.status.order=FATAL, DOWN, OUT_OF_SERVICE, UNKNOWN, UP

如果使用 HTTP 上的health端点,每个状态/严重性都有自己的 HTTP 代码或映射可用。

  • 向下–503

  • 停止服务–503

  • 向上–200

  • 下降-200

您可以通过以下方式使用自己的代码

management.health.status.http-mapping.FATAL=503

此外,您可以使用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

清单 10-8 显示了ToDoMetricInterceptor类(它实现了HandlerInterceptor接口)。该接口有三种默认方法:preHandlepostHandleafterCompletion。这个类只实现了afterCompletion方法。这个方法有HttpServletRequest,有助于发现请求了哪个端点和 HTTP 方法。

您的类使用的是MeterRegistry实例,它是 Micrometer 框架的一部分。实现从请求实例中获取路径和方法,并使用counter方法来递增。pathKey很简单;如果有对/toDos端点的GET请求,则pathKeyapi_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

清单 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>

如果您使用的是 Gradle,您可以将以下依赖项添加到build.gradle

compile('io.micrometer:micrometer-registry-jmx')
compile('io.micrometer:micrometer-registry-prometheus')

Spring Boot 执行器自动配置和注册每个千分尺注册表,在这种情况下,JMX 和普罗米修斯。对于 Prometheus,致动器配置/actuator/prometheus端点。

先决条件:使用 Docker

在使用指标测试 ToDo 应用之前,有必要安装 Docker(尝试安装最新版本)。

我为什么选择 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

您可以用任何编辑器创建这个文件。记住,这是一个 YML 文件,没有缩进的制表符。你需要创建两个文件夹:prometheusgrafana。在每个文件夹中,都有一个文件。

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']

关于这个文件最重要的是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 采用缺省值,您显示了以下目录结构。

img/340891_2_En_10_Fig29_HTML.jpg

运行待办事项应用指标

现在,是时候开始测试和配置 Grafana 来查看有用的指标了。运行您的待办事项应用。检查日志并确保/actuator/prometheus端点在那里。

打开一个终端,转到有docker-compose.yml文件的地方,执行下面的命令。

$ docker-compose up

这个命令行启动 docker-compose 引擎,它下载图像并运行它们。让我们通过打开浏览器并点击http://localhost:9090/targets来确保 Prometheus 应用正常工作。

img/340891_2_En_10_Fig30_HTML.jpg

这意味着prometheus.yml配置被成功采用。换句话说,普罗米修斯是在刮http://localhost:8080/actuator/prometheus端点。

接下来,我们来配置 Grafana。

img/340891_2_En_10_Fig31_HTML.jpg

  1. 打开另一个浏览器标签,点击http://localhost:3000

可以用admin/admin作为凭证。

  1. You can press the Skip button the following screen.

    img/340891_2_En_10_Fig32_HTML.jpg

  2. Click the Add Data Source icon.

    img/340891_2_En_10_Fig33_HTML.jpg

  3. 填写所有必填字段。重要的是

    • 名称:todo-app

    • 类型:Prometheus

    • URL: http://prometheus:9090

    • 刮擦间隔:3s

  4. Click the Save button.

    img/340891_2_En_10_Fig34_HTML.jpg

    URL 值http://prometheus:9090指的是 docker - compose 服务,是 docker-compose 提供的内部 DNS,不需要做本地主机。您可以保留默认的其他值,并点击保存&测试。如果一切按预期运行,您会在页面底部看到一个绿色横幅,上面写着,数据源正在运行

  5. 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.

    img/340891_2_En_10_Fig35_HTML.jpg

  6. You can click the Graph icon, a panel appears. Click Panel Title and then click Edit.

    img/340891_2_En_10_Fig36_HTML.jpg

  7. Configure two queries, the api_get_total and api_post_todos_total, which were generated as metrics by the Micrometer and Spring Boot Actuator for Prometheus engine.

    img/340891_2_En_10_Fig37_HTML.jpg

  8. Perform requests to the /toDos (several times) and post to the /toDos endpoints. You see something like the next figure.

    img/340891_2_En_10_Fig38_HTML.jpg

恭喜你!您已经使用 Micrometer、Prometheus 和 Grafana 创建了自定义指标。

Spring Boot 和格拉夫纳的一般统计

我为 Grafana 找到了一个非常有用的配置,它允许你利用 Spring Boot 执行器提供的每一个指标。这个配置可以导入到 Grafana 中。

https://grafana.com/dashboards/6756 下载此配置。文件名为spring-boot-statistics_rev2.json。你接下来需要它。

在 Grafana 主页的左上角(http://localhost:3000,点击 Grafana 图标,打开侧边栏。点击+符号并选择导入。

img/340891_2_En_10_Fig39_HTML.jpg

设置默认值,但是在 Prometheus 字段中,选择 todo-app(您之前配置的数据源)。

img/340891_2_En_10_Fig40_HTML.jpg

点击导入—然后!您拥有一个包含所有 Spring Boot 执行器指标和监控的完整仪表板!

img/340891_2_En_10_Fig41_HTML.jpg

花点时间回顾每一张图。所有数据都来自于/actuator/prometheus端点。

您可以通过在另一个终端窗口中执行以下命令来关闭 docker-compose。

$ docker-compose down

注意

你可以在 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。