【Hoxton.SR1版本】Spring Cloud Bus消息总线

目录

一、简介

二、消息总线实现配置动态刷新

三、架构图

四、指定刷新范围

五、总结 


一、简介

上一篇文章已经实现了Spring Cloud Config分布式配置中心的功能,我们已经可以通过Config Server获取Gitee远程仓库配置文件中的内容,并且实现了手动通过actuator微服务监控对外暴露的/refresh接口实现了在不重启应用的情况下手动刷新配置文件信息。如果微服务单元很多的情况下,手动刷新难免比较麻烦,其实Spring Cloud官方提供了一种 "一处刷新,处处生效"的组件,那就是Spring Cloud Bus消息总线组件,Bus消息代理中间件可以将消息路由到一个或多个目的地,接下来就来看看如何使用Bus实现全局刷新或者指定范围刷新。

Spring Cloud Bus官方文档:https://cloud.spring.io/spring-cloud-bus/2.2.x/reference/html/

  • 什么是总线?

在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中的所有的微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。

  • 基本原理?

Config Client实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一个Topic的服务就能得到通知,然后去更新自身的配置。

  • Spring Cloud Bus能干嘛?

Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改,事件推送等,也可以当做微服务间的通信通道。

二、消息总线实现配置动态刷新

Bus消息总线刷新涉及到配置中心服务端和客户端的修改,下面先对Config Server做一些小调整:

【a】pom.xml中添加bus-amqp依赖

<!--添加消息总线RabbitMQ支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

【b】application.yml中加入MQ配置、以及对外暴露的访问端点

server:
  port: 3344
spring:
  application:
    name:  springcloud-config-server  #注册进Eureka服务器的微服务名
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/weixiaohuai/spring-cloud-config.git #Gitee上面的git仓库名字
          #Gitee仓库中配置文件所在的目录
          search-paths:
            - spring-cloud-config
          #访问git仓库的用户名(如果Git仓库为公开仓库,可以不填写用户名和密码,如果是私有仓库需要填写)
#          username:
          #访问git仓库的密码
#          password:
      #读取的分支名称
      label: master
  #RabbitMQ相关配置
  rabbitmq:
    port: 5672
    username: guest
    password: guest
    host: localhost
eureka:
  client:
    service-url:
      defaultZone: http://springcloud-eureka7001.com:7001/eureka/,http://springcloud-eureka7002.com:7002/eureka/   #集群版Eureka注册中心
#暴露bus刷新配置的端点
management:
  endpoints:
    web:
      exposure:
        include: bus-refresh

接下来就需要对客户端进行小调整,其实跟服务端的修改基本一致:

【a】pom.xml中添加bus-amqp依赖

 <!--添加消息总线RabbitMQ支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

【b】application.yml中加入MQ配置、以及加入/bus-refresh对外暴露的访问端点 

spring:
  application:
    name: springcloud-config-client
  cloud:
    config:
      #      uri: http://localhost:3344/   #指定Config Server服务配置中心的地址

      #配置环境
      #dev为开发环境配置文件
      #test为测试环境
      #pro为正式环境
      profile: dev
      label: master  #指定配置文件的分支名称
      discovery:
        enabled: true  #开启通过服务访问config-server的功能
        service-id: springcloud-config-server  #指定Config Server服务注册到Eureka中的微服务名称(即config-server的application-name)
  rabbitmq:
    password: guest
    host: localhost
    username: guest
    port: 5672
server:
  port: 3355
eureka:
  client:
    service-url:
      defaultZone: http://springcloud-eureka7001.com:7001/eureka/,http://springcloud-eureka7002.com:7002/eureka/   #集群版Eureka注册中心
#暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: refresh,info,health,bus-refresh

【c】验证Controller接口是否加上@RefreshScope,很关键。

由于我们需要后面需要测试固定服务刷新以及全局刷新的功能,需要涉及到两个Config Client客户端,所以这里我们需要新建一个module【springcloud-config-client3366】 ,此模块跟3355那个客户端除了端口号不一致,其他都一模一样,这里就直接贴出代码:

【a】pom.xml依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud2020</artifactId>
        <groupId>com.wsh.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-config-client3366</artifactId>
    
    <dependencies>
        <!--添加消息总线RabbitMQ支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

【b】bootstrap.yml配置文件

spring:
  application:
    name: springcloud-config-client
  cloud:
    config:
      #配置环境
      #dev为开发环境配置文件
      #test为测试环境
      #pro为正式环境
      profile: dev
      label: master  #指定配置文件的分支名称
      discovery:
        enabled: true  #开启通过服务访问config-server的功能
        service-id: springcloud-config-server  #指定Config Server服务注册到Eureka中的微服务名称(即config-server的application-name)
  rabbitmq:
    password: guest
    host: localhost
    username: guest
    port: 5672
server:
  port: 3366
eureka:
  client:
    service-url:
      defaultZone: http://springcloud-eureka7001.com:7001/eureka/,http://springcloud-eureka7002.com:7002/eureka/   #集群版Eureka注册中心
#暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: refresh,info,health,bus-refresh

【c】主启动类

package com.wsh.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClientServiceApplication3366 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigClientServiceApplication3366.class, args);
    }
}

【d】测试Controller

package com.wsh.springcloud.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
//@RefreshScope注解的作用: 如果刷新了bean,那么下一次访问bean(即执行一个方法)时就会创建一个新实例
@RefreshScope
public class TestController {
    private static Logger logger = LoggerFactory.getLogger(TestController.class);

    @Value("${config.server.info}")
    String message;

    @RequestMapping("/getPropertyFromConfigServer")
    public String getPropertyFromConfigServer() {
        String msg = "hello, i am " + message + ", i'm come from config server";
        logger.info(msg);
        return msg;
    }

}

 以上就是对配置中心服务端以及客户端的全部修改,接下来就需要进行测试,在测试之前,有必要了解一下Spring Cloud Bus提供的两种刷新模式:

  • 1). 利用消息总线触发一个客户端/bus-refresh,而刷新所有客户端的配置;
  • 2). 利用消息总线触发一个服务端Config Server的/bus-refresh端点,而刷新所有客户端的配置;

很显然,模式二的架构更加适合应用在生产环境中,模式一不太合适的原因主要有三点:

  • 打破了微服务的职责单一性,因为微服务本身就是业务模块,它本不应该承担配置刷新的职责;
  • 破坏了微服务各节点的对等性;
  • 有一定的局限性,例如,微服务在迁移的时候,它的网络地址尝尝会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改,不好维护;

下面我们启动Config Server以及两个Config Client,注意观察Config Server服务端的启动日志: 

可以看到,对外暴露了一个/bus-refresh的请求,这个就是用来触发配置刷新的接口。项目启动完成后,我们首先通过Config Server端[端口3344]去访问配置文件:http://localhost:3344/master/applicaiton-dev.yml

可以看到,当前Gitee仓库中的version版本号为4.0,然后我们使用Config Client进行访问:

http://localhost:3355/getPropertyFromConfigServer或者http://localhost:3366/getPropertyFromConfigServer

 

 

接着,我们手动将Gitee仓库中的application-dev.yml文件进行修改:version:5.0

 

 通过Config Server[端口3344]访问:http://localhost:3344/master/applicaiton-dev.yml

服务配置中心立马感知到配置文件发生变化,加载出最新的配置信息,然后我们通过Config Client客户端进行访问:

 http://localhost:3355/getPropertyFromConfigServer或者http://localhost:3366/getPropertyFromConfigServer

 

可以看到,在没有发出/bus-refresh请求的情况下,客户端拿到的配置信息还是旧的,如上图所示。 

下面我们通过postman发送一个Post请求:http://localhost:3344/actuator/bus-refresh

请求完成之后,继续通过Config Client客户端查看配置文件的值:

http://localhost:3355/getPropertyFromConfigServerhttp://localhost:3366/getPropertyFromConfigServer

 

 

可以看到,当Config Server服务端【3344】发送一个 /actuator/bus-refresh请求后,同时监听同一个主题的Config Client【3355和3366】立马收到配置刷新的请求,于是客户端就会去重新拉取最新的配置,如上图所示。

  • 之所以能够实现这种动态刷新,是因为Config Client实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一个Topic的服务就能得到通知,然后去更新自身的配置。下图是RabbitMQ中交换机监听的三个队列:

三、架构图

前面我们使用的是模式二:利用消息总线触发一个服务端Config Server的/bus-refresh端点,而刷新所有客户端的配置。

配置中心服务端动态刷新配置的架构图如下:

当然,也可以使用模式一: 利用消息总线触发一个客户端的/bus-refresh端点,而刷新所有客户端的配置;

有兴趣的小伙伴可以学习笔者之前对旧版本Spring Cloud  Bus的介绍:https://blog.csdn.net/Weixiaohuai/article/details/82852331配置中心客户端动态刷新配置的架构图如下:

总结两点:

【a】Config Client1、Config Client2、Config Client3都从Config Server中获取配置信息,服务配置中心从Gitee远程仓库获取配置信息,这时候我们在各个服务消息者客户端可以成功获取到Gitee中配置文件信息。

【b】假设,我们对Config Client3的配置内容进行了修改,这时候Config Client1、Config Client2、Config Client3中的配置信息其实并没有更新到最新的,还是以前的。只有我们向Config Client3发送/actuator/bus-refresh的post请求之后,因为Config Client3会通过RabbitMQ通知到消息总线上,这样监听消息总线上的Config Client1和Config Client2都能从消息总线上获取到最新的配置信息,从而实现配置信息的动态更新。

四、指定刷新范围

官网对于这部分的文档:https://cloud.spring.io/spring-cloud-bus/2.2.x/reference/html/#addressing-an-instance

上面的例子中,我们通过向服务实例请求Spring Cloud Bus的/bus-refresh接口,从而触发总线上其他服务实例的/refresh。但是有些特殊场景下(比如:灰度发布),我们希望可以刷新微服务中某个具体实例的配置。

Spring Cloud Bus对这种场景也有很好的支持:/bus-refresh接口还提供了destination参数,用来定位具体要刷新的应用程序。比如,我们可以请求/bus-refresh/{服务名字:服务端口号},此时总线上的各应用实例会根据destination属性的值来判断是否为自己的实例名,若符合才进行配置刷新,若不符合就忽略该消息。

destination参数除了可以定位具体的实例之外,还可以用来定位具体的服务。定位服务的原理是通过使用Spring的PathMatecher(路径匹配)来实现,比如:/bus-refresh/springcloud-config-client:**,该请求会触发springcloud-config-client服务的所有实例进行刷新。

destination写法: 注册到Eureka上面的服务名称  +  ":"  + 

  1. 如果需要指定某个实例端口的这个服务,就加上端口号;
  2. 如果需要刷新某个服务的所有实例,那么端口号指定为**即可。

【a】某个实例更新:http://localhost:3344/actuator/bus-refresh/springcloud-config-client:3355,只更新端口为3355的springcloud-config-client服务实例

首先查看Eureka注册中心:

(1)、修改Gitee中application-dev.yml文件中的version版本号为:6.0

(2)、使用Config Client【3355】端浏览器访问:http://localhost:3355/getPropertyFromConfigServer

使用Config Client【3366】端浏览器访问:http://localhost:3366/getPropertyFromConfigServer

 

可见,配置信息还是旧的,下面我们通过postman发送一个post请求只刷新3355这个实例的配置信息:

http://localhost:3344/actuator/bus-refresh/springcloud-config-client:3355

注意!注意!注意!不要使用http://localhost:3344/actuator/bus-refresh?destination=springcloud-config-client:3355,笔者测试了,好像不起效果,使用这个地址3366也同时刷新了,具体原因也不是很确定是不是跟SpringCloud的版本有关系。

然后使用Config Client【3355】端浏览器访问: 

 使用Config Client【3366】端浏览器访问:http://localhost:3366/getPropertyFromConfigServer

 可见,【3366】端的配置还是旧的,只刷新了springcloud-config-client服务的3355实例配置,3366实例并没有刷新。

 

【b】某个服务的全部实例更新:http://localhost:3344/actuator/bus-refresh/springcloud-config-client:**这样会更新springcloud-config-client服务的所有实例

 (1)、修改Gitee仓库中的application.yml文件version为:7.0

我们使用postman发送一个post请求去刷新springcloud-config-client服务的所有实例的配置信息:http://localhost:3344/actuator/bus-refresh/springcloud-config-client:**

 (二)、然后使用Config Client【3355】端浏览器访问:http://localhost:3355/getPropertyFromConfigServer

然后使用Config Client【3366】端浏览器访问:http://localhost:3366/getPropertyFromConfigServer

 

五、总结 

本篇文章主要介绍如何使用Spring Cloud Bus消息总线实现配置动态刷新,结合示例详细说明了其用法和指定范围刷新和全局范围刷新等等。以上相关项目的代码我已经放在Gitee上,有需要的小伙伴可以去拉取进行学习:https://gitee.com/weixiaohuai/springcloud_Hoxton,由于笔者水平有限,如有不对之处,还请小伙伴们指正,相互学习,一起进步。

已标记关键词 清除标记
pom.xml 配置 ``` <dependencies> <!--微服务注册--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--网关配置--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-zuul --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <!--限流--> <!-- https://mvnrepository.com/artifact/com.marcosbarbero.cloud/spring-cloud-zuul-ratelimit --> <dependency> <groupId>com.marcosbarbero.cloud</groupId> <artifactId>spring-cloud-zuul-ratelimit</artifactId> <version>2.3.0.RELEASE</version> </dependency> <!--限流redis数据库记录数据--> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--OAuth--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--自定义config配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> <!-- 被zipkin服务追踪的启动依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <!--配置rabbitmq--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency> </dependencies> ``` application.yml ``` server: port: 6020 spring: application: name: Gateway-Zuul #对曝光的微服务的名称 #配置redis redis: host: 127.0.0.1 port: 6379 password: admin jedis: pool: max-active: 100 max-idle: 8 min-idle: 4 max-wait: 10000 timeout: 3000 #rabbitmq rabbitmq: host: 127.0.0.1 port: 5672 username: admin password: admin #zipkin在rabbitmq消息队列zipkin zipkin: sender: type: rabbit rabbitmq: queue: zipkin #被追踪的可能性,默认是0.1 表示百分之10 sleuth: sampler: probability: 1.0 eureka: client: service-url: defaultZone: http://Eureka7001.com:7001/eureka/ registry-fetch-interval-seconds: 5 # 默认为30秒 表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒 instance: instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}} #修改之后的ip prefer-ip-address: true #访问路径显示IP地址 zuul: host: connect-timeout-millis: 15000 #HTTP连接超时要比Hystrix的大 socket-timeout-millis: 60000 #socket超时 ignored-services: "*" # 不允许用微服务名访问了,如果禁用所有的,可以使用 "*" routes: OAuth-server: /auth/** sensitive-headers: #允许传递敏感信息 #限流配置 ratelimit: enabled: true repository: REDIS behind-proxy: true add-response-headers: false default-policy-list: #optional - will apply unless specific policy exists - limit: 20 #optional - request number limit per refresh interval window quota: 1 #optional - request time limit per refresh interval window (in seconds) refresh-interval: 1 #default value (in seconds) type: #optional # - user - origin - url - httpmethod security: oauth2: client: #令牌端点 access-token-uri: http://localhost:${server.port}/auth/oauth/token #授权端点 user-authorization-uri: http://localhost:${server.port}/auth/oauth/authorize #OAuth2客户端ID client-id: test #OAuth2客户端密钥 client-secret: test authorization: check-token-access: http://localhost:${server.port}/auth/oauth/check_token resource: jwt: key-value: jkdfjkdf ribbon: ReadTimeout: 10000 ConnectTimeout: 10000 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000 circuitBreaker: enabled: true requestVolumeThreshold: 10 sleepWindowInMilliseconds: 10000 errorThresholdPercentage: 60 ``` 报错信息: ``` 2020-02-09 17:04:44.448 ERROR [Gateway-Zuul,bb484313c41a709a,0245f4527d3b0826,true] 20368 --- [ask-scheduler-1] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessagingException: Failed to invoke method; nested exception is java.lang.UnsupportedOperationException: This converter does not support this method at org.springframework.integration.endpoint.MethodInvokingMessageSource.doReceive(MethodInvokingMessageSource.java:115) at org.springframework.integration.endpoint.AbstractMessageSource.receive(AbstractMessageSource.java:167) at org.springframework.integration.endpoint.SourcePollingChannelAdapter.receiveMessage(SourcePollingChannelAdapter.java:250) at org.springframework.integration.endpoint.AbstractPollingEndpoint.doPoll(AbstractPollingEndpoint.java:359) at org.springframework.integration.endpoint.AbstractPollingEndpoint.pollForMessage(AbstractPollingEndpoint.java:328) at org.springframework.integration.endpoint.AbstractPollingEndpoint.lambda$null$1(AbstractPollingEndpoint.java:275) at org.springframework.integration.util.ErrorHandlingTaskExecutor.lambda$execute$0(ErrorHandlingTaskExecutor.java:57) at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50) at org.springframework.integration.util.ErrorHandlingTaskExecutor.execute(ErrorHandlingTaskExecutor.java:55) at org.springframework.integration.endpoint.AbstractPollingEndpoint.lambda$createPoller$2(AbstractPollingEndpoint.java:272) at org.springframework.cloud.sleuth.instrument.async.TraceRunnable.run(TraceRunnable.java:67) at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.UnsupportedOperationException: This converter does not support this method at org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter.toMessage(DefaultDatatypeChannelMessageConverter.java:85) at org.springframework.messaging.converter.CompositeMessageConverter.toMessage(CompositeMessageConverter.java:83) at org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry$FunctionInvocationWrapper.lambda$convertOutputValueIfNecessary$2(BeanFactoryAwareFunctionRegistry.java:620) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359) at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464) at org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry$FunctionInvocationWrapper.convertOutputValueIfNecessary(BeanFactoryAwareFunctionRegistry.java:626) at org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry$FunctionInvocationWrapper.doApply(BeanFactoryAwareFunctionRegistry.java:569) at org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry$FunctionInvocationWrapper.get(BeanFactoryAwareFunctionRegistry.java:474) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282) at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:266) at org.springframework.integration.endpoint.MethodInvokingMessageSource.doReceive(MethodInvokingMessageSource.java:112) ... 19 moreyi ```
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值