SpringBoot2 高级教程(四)

随笔4个月前发布 文中莲花
54 0 0

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

SpringBoot2 高级教程(四)

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

  • 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

SpringBoot2 高级教程(四)

  • 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

SpringBoot2 高级教程(四)

  • 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接口。这个接口提供了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

SpringBoot2 高级教程(四)

  • 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 接口。这个接口促进了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

SpringBoot2 高级教程(四)

  • 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字段不为空。这个类实现了验证器接口,并实现了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());
    }
}

SpringBoot2 高级教程(四)

  • 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

SpringBoot2 高级教程(四)

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

  • 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 显示了这些不同的交换类型。

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

图 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

SpringBoot2 高级教程(四)

  • 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 容器要拾取的类。

  • 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

SpringBoot2 高级教程(四)

  • 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实例。

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

SpringBoot2 高级教程(四)

  • 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

SpringBoot2 高级教程(四)

  • 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 控制台。

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

图 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

SpringBoot2 高级教程(四)

  • 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注释中的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

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

SpringBoot2 高级教程(四)

图 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

SpringBoot2 高级教程(四)

  • 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

SpringBoot2 高级教程(四)

  • 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

SpringBoot2 高级教程(四)

  • 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

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

  • 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

SpringBoot2 高级教程(四)

  • 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

SpringBoot2 高级教程(四)

  • 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

SpringBoot2 高级教程(四)

  • 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>&nbsp" + json.description + "</span><br/>";
                $("#output").append(result);
            });

        });

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

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

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

图 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

SpringBoot2 高级教程(四)

  • 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
        }
      }
    }
    
    

    SpringBoot2 高级教程(四)

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

  • 1
  • 2

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

management.endpoints.web.exposure.include=*

  • 1
  • 2

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

/执行器

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

SpringBoot2 高级教程(四)

图 10-2

http://localhost:8080/actuator

/致动器/条件

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

SpringBoot2 高级教程(四)

图 10-3

http://localhost:8080/actuator/conditions

/执行器/bean

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

SpringBoot2 高级教程(四)

图 10-4

http://localhost:8080/actuator/beans

/执行器/配置

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

SpringBoot2 高级教程(四)

图 10-5

http://localhost:8080/actuator/configprops

/执行器/线程转储

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

SpringBoot2 高级教程(四)

图 10-6

http://localhost:8080/actuator/threaddump

/执行器/环境

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

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

图 10-8

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

/执行器/信息

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

/执行器/记录器

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

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

图 10-10

http://localhost:8080/actuator/metrics

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

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

图 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=truespring-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..."}

SpringBoot2 高级教程(四)

  • 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 的东西。

SpringBoot2 高级教程(四)

图 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来保护您的致动器端点;这是通过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();
      }

}

  • 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+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

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

图 10-14

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

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

SpringBoot2 高级教程(四)

图 10-15

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

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

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

    SpringBoot2 高级教程(四)

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

    SpringBoot2 高级教程(四)

  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.

    SpringBoot2 高级教程(四)

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

    SpringBoot2 高级教程(四)

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

    SpringBoot2 高级教程(四)

如你所见,这和浏览网页是一样的。您可以尝试使用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.

    SpringBoot2 高级教程(四)

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

    SpringBoot2 高级教程(四)

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

    SpringBoot2 高级教程(四)

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

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

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

SpringBoot2 高级教程(四)

  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>
    
    

    • 1
    • 2
    • 3
    • 4
    • 5

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

    compile('org.springframework.boot:spring-boot-starter-amqp')
    
    

    • 1
    • 2
  2. You guessed right. We are adding AMQP dependencies. Re-run the app and take a look at the /actuator/health endpoint .

    SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

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

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

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

SpringBoot2 高级教程(四)

  • 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 显示了标记为@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

  • 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的响应,您应该会得到以下内容。

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

您可以通过使用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

SpringBoot2 高级教程(四)

  • 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接口)。该接口有三种默认方法: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

SpringBoot2 高级教程(四)

  • 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

SpringBoot2 高级教程(四)

  • 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 文件,没有缩进的制表符。你需要创建两个文件夹: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']

  • 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 采用缺省值,您显示了以下目录结构。

SpringBoot2 高级教程(四)

运行待办事项应用指标

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

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

$ docker-compose up

  • 1
  • 2

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

SpringBoot2 高级教程(四)

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

接下来,我们来配置 Grafana。

SpringBoot2 高级教程(四)

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

可以用admin/admin作为凭证。

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

    SpringBoot2 高级教程(四)

  2. Click the Add Data Source icon.

    SpringBoot2 高级教程(四)

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

    • 名称:todo-app

    • 类型:Prometheus

    • URL: http://prometheus:9090

    • 刮擦间隔:3s

  4. Click the Save button.

    SpringBoot2 高级教程(四)

    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.

    SpringBoot2 高级教程(四)

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

    SpringBoot2 高级教程(四)

  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.

    SpringBoot2 高级教程(四)

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

    SpringBoot2 高级教程(四)

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

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

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

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

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

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

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

SpringBoot2 高级教程(四)

花点时间回顾每一张图。所有数据都来自于/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。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...