• [技术干货] Java21新特性-虚拟线程
    Java 21新特性中的虚拟线程(Virtual Threads)是一项革命性的改进,为Java并发编程带来了显著的优势。以下是对Java 21虚拟线程的详细介绍:一、定义与背景虚拟线程,也称为用户模式线程(user-mode threads)或纤程(fibers),是Java 21引入的一种轻量级线程实现方式。这一特性旨在简化并发编程,提供更好的可扩展性,并大幅提升Java的并发能力。随着企业应用的规模不断壮大,大量的网络请求或读写I/O场景越来越多,虚拟线程的引入对于I/O密集型程序的性能带来了大幅度的提升。二、核心优势轻量级:虚拟线程的创建、销毁和切换开销比操作系统级别的线程更小。因此可以创建大量的虚拟线程来支持更高的并发度。高效并发:虚拟线程在用户级别进行线程调度和管理,提供了更高的可控性。显著提升了Java的并发能力,尤其在处理I/O密集型任务时。资源优化:虚拟线程基于操作系统级别的线程(传统Java线程),但由JVM(Java虚拟机)进行调度和管理。不会在整个生命周期内都占用一个操作系统线程,多个虚拟线程可以在一个操作系统线程上运行。这种设计减少了线程调度的开销,并允许创建大量的虚拟线程而不会占用大量操作系统资源。高并发、低延迟:适用于金融应用服务器等需要实现多线程业务逻辑的场景,提高交易响应的流畅度和响应速度。Web应用可以使用虚拟线程来处理高并发的HTTP请求,提高Web服务器的吞吐量和响应速度。三、工作原理任务调度:JDK先将虚拟线程分配给平台线程(即操作系统线程),然后平台线程按照通常的方式由操作系统进行调度。JDK的虚拟线程调度器是一个以FIFO(先进先出)模式运行的ForkJoinPool,它负责管理和调度虚拟线程的执行。阻塞处理:当虚拟线程遇到阻塞(如I/O操作)时,JVM会立刻挂起该虚拟线程。通过操作系统事件(如epoll)通知I/O完成,从而在合适时机重新唤醒该虚拟线程。这套机制确保了即使在大量I/O阻塞场景下,系统也不会因为传统线程资源不足而性能急剧下降。四、使用方式在Java 21中,创建和使用虚拟线程有多种方法,包括:使用Thread.startVirtualThread方法:该方法接受一个Runnable对象作为参数,并立即启动一个新的虚拟线程来执行该Runnable对象中的代码。使用Thread.ofVirtual方法:该方法可以创建一个虚拟线程的构建器,允许设置线程名称等属性,并通过调用start方法启动虚拟线程。使用ExecutorService:Java 21中引入了新的ExecutorService来适配虚拟线程。可以使用Executors.newVirtualThreadPerTaskExecutor方法创建一个为每个提交的任务创建虚拟线程的ExecutorService。五、与传统线程的区别线程创建方式:虚拟线程:不直接创建操作系统线程,运行时由传统线程池调度。传统线程:每创建一个线程,JVM都会启动一个独立的操作系统线程。资源开销:虚拟线程:内存开销极小,可轻松创建百万级虚拟线程。传统线程:资源开销较大,一般只能支持几千个线程。上下文切换:虚拟线程:由JVM管理,开销低。传统线程:依赖OS级调度,切换开销较高。阻塞行为:虚拟线程:任务调度完全由JVM控制,遇到阻塞时只挂起任务,不会占用底层线程。传统线程:阻塞操作会直接占用线程,影响线程池整体吞吐。六、总结Java 21的虚拟线程特性为Java并发编程带来了显著的改进。通过降低线程创建和管理的开销、提高并发能力和资源利用率,虚拟线程使得开发人员能够更轻松地编写和维护高并发应用程序。这一特性在处理I/O密集型任务时尤其高效,为Java应用提供了更强的性能和可扩展性。
  • [技术干货] Spring Cloud 服务的追踪-Zipkin存储到ES
    在Spring Cloud服务的追踪中,Zipkin能够将追踪数据存储到Elasticsearch(ES)中,从而提供高效的搜索、分析和可视化功能。以下是对Zipkin存储到ES的详细解释:一、Zipkin存储到ES的背景Zipkin是一个分布式追踪系统,它用于收集、存储、查找和展示微服务架构中的请求链路数据。在微服务架构中,服务间的调用关系错综复杂,通过Zipkin可以轻松地追踪请求的处理路径和耗时,从而定位性能瓶颈和问题所在。为了高效地存储和查询这些链路数据,Zipkin支持多种存储后端,包括内存、MySQL、Cassandra以及Elasticsearch等。其中,Elasticsearch以其强大的搜索和分析能力,成为Zipkin存储数据的理想选择。二、Zipkin存储到ES的优势高效的搜索能力:Elasticsearch提供了基于Lucene的搜索引擎,支持全文搜索、结构化搜索以及复合搜索等多种搜索方式,能够快速定位到所需的链路数据。丰富的分析功能:Elasticsearch提供了丰富的数据分析功能,如聚合分析、时间序列分析等,可以对链路数据进行深入的分析和挖掘。可视化展示:结合Kibana等可视化工具,Elasticsearch可以将链路数据以图表、报表等形式展示出来,便于开发人员理解和分析系统的运行状态。三、Zipkin存储到ES的实现步骤搭建Elasticsearch环境:安装Elasticsearch并配置其运行参数,如集群名称、节点数量、内存分配等。启动Elasticsearch服务,并验证其运行状态。配置Zipkin Server:下载并解压Zipkin Server的二进制包。修改Zipkin Server的配置文件,指定存储类型为Elasticsearch,并配置Elasticsearch的地址和端口。启动Zipkin Server服务。配置微服务:在微服务的配置文件中指定Zipkin Server的地址,以便将链路数据发送到Zipkin Server。确保微服务已经集成了Spring Cloud Sleuth或其他兼容Zipkin的客户端库。验证存储效果:访问微服务并触发请求,观察Zipkin Server的日志输出,确认链路数据已经被成功发送到Zipkin Server。登录Elasticsearch的管理界面或使用Elasticsearch的客户端工具,查询存储的链路数据,验证数据是否正确存储到Elasticsearch中。访问Zipkin Server的Web界面,查看链路追踪信息,确认能够正常展示和分析数据。四、注意事项Elasticsearch的版本兼容性:确保Zipkin Server和Elasticsearch的版本兼容,避免出现不兼容导致的错误。资源分配:合理配置Elasticsearch的内存、CPU等资源,确保其能够高效运行并处理大量的链路数据。数据安全:对Elasticsearch进行必要的安全配置,如启用访问控制、数据加密等,以保护链路数据的安全性和隐私性。性能监控:定期监控Elasticsearch的性能指标,如CPU使用率、内存占用率、磁盘I/O等,及时发现并解决性能瓶颈。综上所述,Zipkin存储到Elasticsearch可以提供一个高效、可靠且易于分析的链路追踪方案。在实际应用中,需要根据具体的需求和环境来配置和优化整个链路追踪系统。
  • [技术干货] Spring Cloud 服务的追踪-Zipkin整合RabbitMQ
    在Spring Cloud服务的追踪中,Zipkin整合RabbitMQ可以提供一个高性能且容错的链路追踪方案。以下是对Zipkin整合RabbitMQ的详细解释:一、Zipkin与RabbitMQ整合的背景Zipkin通常通过HTTP方式收集微服务之间的调用关系数据,并存储到内存中或数据库中进行管理。然而,这种方式存在一些问题,如网络闪断或Zipkin服务端故障时可能导致数据丢失。为了解决这个问题,可以使用RabbitMQ作为消息中间件来异步收集并持久化存储这些数据。二、Zipkin整合RabbitMQ的优势异步收集数据:微服务将链路数据发送到RabbitMQ,Zipkin服务端从RabbitMQ中异步收集数据,这种方式可以提高性能并减少网络延迟。容错性更高:即使Zipkin服务端出现故障,微服务仍然可以继续发送数据到RabbitMQ,待Zipkin服务端恢复后再从RabbitMQ中拉取数据,确保数据的完整性。持久化存储:RabbitMQ可以将链路数据持久化存储到磁盘上,防止数据丢失。三、Zipkin整合RabbitMQ的实现步骤搭建RabbitMQ环境:安装Docker并配置阿里云镜像加速。使用Docker拉取RabbitMQ镜像并启动容器。安装RabbitMQ的可视化插件,方便管理和监控。搭建Zipkin Server环境:使用Docker拉取Zipkin Server镜像。启动Zipkin Server容器,并配置其从RabbitMQ中收集数据,同时将数据存储到Elasticsearch中(因为链路数据量庞大,内存和数据库都不太适合存储)。配置微服务:在微服务的pom.xml文件中添加Spring Cloud Sleuth和Spring Cloud Stream Binder Rabbit的依赖。配置微服务的application.properties或application.yml文件,指定Zipkin Server的地址和RabbitMQ的相关信息。验证整合效果:启动Eureka Server、微服务以及Zipkin Server。访问微服务并观察RabbitMQ的管理界面,确认Zipkin队列中有消息处理。访问Zipkin Server的Web界面,确认能够查看到链路追踪信息。四、注意事项确保RabbitMQ的稳定性和可靠性:RabbitMQ作为消息中间件,其稳定性和可靠性对整个链路追踪系统至关重要。因此,需要合理配置RabbitMQ的资源、监控其运行状态,并及时处理可能出现的故障。合理配置Zipkin Server:Zipkin Server需要从RabbitMQ中收集数据并存储到Elasticsearch中,因此需要合理配置其存储方式、并发消费者数量等参数,以确保其能够高效、稳定地运行。注意数据安全和隐私保护:链路追踪数据中可能包含敏感信息,因此需要采取相应的安全措施来保护数据的安全性和隐私性。例如,可以使用加密技术来传输和存储数据,同时限制对数据的访问权限。综上所述,Zipkin整合RabbitMQ可以提供一个高性能且容错的链路追踪方案,有助于开发人员更好地理解和优化分布式系统。在实际应用中,需要根据具体的需求和环境来配置和调优整个链路追踪系统。
  • [技术干货] Spring Cloud 服务的追踪-Zipkin的使用
    在Spring Cloud服务追踪中,Zipkin的使用是一个关键部分。以下是对Zipkin在Spring Cloud服务追踪中使用的详细介绍:一、Zipkin的基本概念Zipkin是一个开源的分布式追踪系统,它最初由Twitter开发,现在隶属于OpenZipkin社区。Zipkin的主要功能包括数据收集、存储和检索、以及可视化分析。它可以收集来自各个服务的追踪数据(Spans),这些数据由Sleuth或其他兼容Zipkin的客户端库产生。然后,Zipkin提供存储Spans的后端存储解决方案,并允许用户通过Web UI查看、搜索和分析请求的调用链路,从而了解服务间的调用关系、请求耗时、是否存在性能瓶颈等问题。二、Zipkin的使用步骤1. 搭建Zipkin Server搭建Zipkin Server有多种方式,包括使用官方提供的可直接启动的Jar包、通过Docker镜像运行、或者手动添加依赖创建Zipkin服务端(但这种方式在Spring Boot 2.0后不再被官方推荐)。以下是使用Docker镜像运行Zipkin Server的示例:docker run -d -p 9411:9411 openzipkin/zipkin这条命令将在本地主机的9411端口启动一个Zipkin服务器。2. 在Spring Cloud应用中引入Zipkin依赖在你的Spring Cloud应用的pom.xml文件中添加Zipkin的依赖。例如:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> 3. 配置Zipkin在你的application.properties或application.yml文件中配置Zipkin的相关信息。例如:spring: zipkin: baseUrl: http://localhost:9411 # Zipkin服务器的地址 4. 启动并配置微服务确保所有参与追踪的微服务都已正确配置并启动。一旦它们开始工作,Sleuth(通常与Zipkin一起使用)将会自动为每个请求生成追踪ID和跨度ID(spanID),并且将追踪数据发送给Zipkin服务器。5. 查看追踪信息你可以通过访问Zipkin的Web界面(默认情况下运行在localhost:9411)来查看和查询追踪信息。在这里,你可以根据不同的条件搜索追踪记录,并深入了解每个服务调用的时间消耗、调用顺序等信息。三、Zipkin的高级使用1. 自定义追踪信息你可以通过代码自定义追踪信息,如添加自定义标签或日志。这有助于在追踪信息中添加额外的上下文信息,从而更容易地理解和排查问题。2. 与第三方日志系统的集成Zipkin可以与第三方日志系统(如ELK Stack)集成,以便将追踪信息与日志信息相关联。这有助于开发人员更全面地了解系统的运行状态和问题所在。3. 数据持久化为了确保追踪数据的持久化存储,你可以将Zipkin与数据库(如MySQL、Cassandra或Elasticsearch)集成。这样,即使Zipkin服务器重启,追踪数据也不会丢失。四、注意事项性能影响:虽然Zipkin提供了强大的分布式追踪功能,但它也可能会对应用程序的性能产生一定的影响。因此,在生产环境中使用时需要权衡性能和追踪需求。安全性:确保追踪信息的安全性和隐私性非常重要。避免将敏感信息泄露给未经授权的用户,并采取相应的安全措施来保护追踪数据。兼容性:确保Zipkin与所使用的Spring Cloud版本和其他相关组件兼容。在升级Spring Cloud或Zipkin时,注意查看官方文档和兼容性说明。综上所述,Zipkin在Spring Cloud服务追踪中发挥着重要作用。通过正确搭建和使用Zipkin,开发人员可以轻松地收集、查询和可视化分布式系统中的追踪数据,从而提高系统的可靠性和性能。
  • [技术干货] Spring Cloud 服务的追踪-Sleuth的使用
    Spring Cloud Sleuth是一个分布式跟踪解决方案,用于跟踪分布式系统中的请求。以下是关于Spring Cloud服务的追踪中Sleuth使用的详细介绍:一、Sleuth的基本概念Span:基本单元,执行一次服务调用就生成一个Span,用于记录当时的情况。Span以一个64位ID作为唯一标识,并包含其他数据标识,如摘要、时间戳信息、关键tag等。Trace:一次请求,以一个64位ID为唯一标识(也可以是一个业务号),通过该ID可以将多个Span标识为同一个业务请求。Trace会以一个树状图的形式展示服务的调用情况。Annotation:注解,代表调用的客户端和服务端的行为。包括:Cs:客户端发起一个服务调用,即Span的开始。Sr:服务端获取请求信息,并开始处理。Sr-Cs的时间得到一个时间戳,即网络延迟时间。Ss:服务端处理完请求,将结果返回客户端。Ss-Sr的时间得到一个时间戳,即服务端处理请求的所用时间。Cr:客户端成功接收到服务端的响应。Cr-Cs得到的时间戳即客户端从服务端获取响应的时间。二、Sleuth的使用步骤添加依赖:在Spring Cloud项目的pom.xml文件中添加Spring Cloud Sleuth的依赖。配置日志:在application.properties或application.yml文件中配置Sleuth的日志采样率等参数。例如,设置采样率为100%以确保所有请求都被追踪。编写代码:在需要追踪的代码中添加日志输出,Sleuth会自动为每个请求生成唯一的跟踪ID和跟踪标签。运行应用程序:启动应用程序并观察日志输出,Sleuth会为每个请求生成跟踪信息。查看追踪信息:使用Zipkin等分布式追踪系统来查看和分析生成的追踪信息。Zipkin是一个开源的分布式跟踪系统,它可以帮助开发人员收集、查询和可视化分布式系统中的跟踪数据。三、Sleuth与Zipkin的集成启动Zipkin服务:下载并启动Zipkin服务,它提供了一个Web界面来查看和分析追踪信息。配置Sleuth将追踪信息发送到Zipkin:在Spring Cloud项目的配置文件中添加Zipkin的相关配置,如Zipkin服务器的地址等。查看追踪信息:在Zipkin的Web界面中,可以通过服务名、操作名称、标签、持续时间等属性来查询和分析追踪信息。Zipkin还可以提供服务的依赖图可视化等功能,以帮助开发人员更好地理解和管理分布式应用系统。四、Sleuth的高级使用自定义样本标记属性:开发人员可以在Sleuth中自定义样本标记属性,以便在追踪信息中添加额外的上下文信息。例如,可以记录是否是GET请求、请求的参数等。持久化全链路数据:为了确保全链路追踪数据的持久化存储,可以将Zipkin与数据库(如MySQL)集成,并将追踪数据保存到数据库中。五、注意事项性能影响:开启Sleuth会对应用程序的性能产生一定的影响,特别是在高并发场景下。因此,在生产环境中使用时需要权衡性能和追踪需求。安全性:确保追踪信息的安全性和隐私性,避免敏感信息泄露给未经授权的用户。兼容性:确保Sleuth与所使用的Spring Cloud版本和其他相关组件兼容。综上所述,Spring Cloud Sleuth是一个强大的分布式跟踪解决方案,它可以帮助开发人员更好地理解和优化分布式系统。通过结合Zipkin等分布式追踪系统,开发人员可以轻松地收集、查询和可视化分布式系统中的跟踪数据,从而提高系统的可靠性和性能。
  • [技术干货] Spring Cloud 服务的动态配置-Config连接RabbitMQ
    在Spring Cloud服务的动态配置中,Config组件可以结合RabbitMQ实现配置的动态刷新。以下是对Spring Cloud Config连接RabbitMQ的详细解析:一、概述Spring Cloud Config是一个集中式的配置管理解决方案,它允许你将应用的配置(如数据库连接信息、应用参数等)外部化,并集中存储在一个地方。RabbitMQ是一个开源的消息代理软件,它实现了高级消息队列协议(AMQP)。通过将Spring Cloud Config与RabbitMQ结合,可以实现配置的动态刷新,即当配置发生变更时,无需重启服务即可使新的配置生效。二、连接步骤安装RabbitMQ:首先,你需要在你的环境中安装并配置RabbitMQ。你可以从RabbitMQ的官方网站下载并安装它,或者通过Docker等容器技术来部署。配置Spring Cloud Config Server:在Spring Cloud Config Server的配置文件中(如application.yml或bootstrap.yml),你需要添加RabbitMQ的连接信息,并启用Spring Cloud Bus(一个基于消息代理的通信机制,用于在微服务之间传播状态更改事件)。以下是一个示例配置:spring: rabbitmq: host: localhost port: 5672 username: guest password: guest cloud: config: server: git: uri: <your-git-repo-url> bus: amqp: enabled: true 注意:这里的<your-git-repo-url>需要替换为你的Git仓库地址。配置Spring Cloud Config Client:在Spring Cloud Config Client的配置文件中,你也需要添加RabbitMQ的连接信息,并启用Spring Cloud Bus。此外,你还需要配置客户端以从Config Server获取配置。以下是一个示例配置:spring: application: name: <your-client-app-name> cloud: config: uri: http://<config-server-address>:<config-server-port> discovery: enabled: true service-id: CONFIG-SERVER-SERVICE-ID # 如果使用服务发现,则替换为Config Server的服务ID bus: amqp: enabled: true rabbitmq: host: localhost port: 5672 username: guest password: guest注意:这里的<your-client-app-name>、<config-server-address>、<config-server-port>和CONFIG-SERVER-SERVICE-ID需要替换为你的客户端应用名称、Config Server的地址和端口以及服务ID(如果使用服务发现的话)。添加依赖:在你的Spring Cloud Config Server和Client项目的pom.xml文件中,你需要添加Spring Cloud Bus AMQP的依赖。以下是一个示例依赖配置:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> 启动服务:启动你的Spring Cloud Config Server和Client服务。当配置发生变更时,Config Server会通过RabbitMQ发送一个刷新事件给所有的Client服务。Client服务在接收到这个事件后,会从Config Server重新获取配置并应用。三、注意事项安全性:确保你的RabbitMQ连接是安全的,特别是当它在生产环境中使用时。你可以使用SSL/TLS来加密通信,并为RabbitMQ设置强密码。网络配置:确保你的Spring Cloud Config Server和Client服务能够访问RabbitMQ服务器。如果它们在不同的网络环境中(如不同的VPC或子网),你可能需要配置网络ACLs、安全组或VPN来允许它们之间的通信。性能考虑:当你有大量的微服务实例时,每次配置变更都会触发大量的刷新请求。这可能会对RabbitMQ和Spring Cloud Config Server造成压力。因此,你需要根据你的需求来配置刷新策略,如批量刷新或延迟刷新。版本兼容性:确保你使用的Spring Cloud Config、RabbitMQ和Spring Cloud Bus AMQP的版本是相互兼容的。不匹配的版本可能会导致意外的行为或错误。综上所述,通过将Spring Cloud Config与RabbitMQ结合,你可以实现配置的动态刷新,从而提高你的微服务应用的灵活性和可用性。
  • [技术干货] Spring Cloud 服务的动态配置-Config内网穿透
    在Spring Cloud服务的动态配置中,Config组件允许服务从外部配置源(如Git仓库)获取配置,并通过内网穿透技术使得内部网络中的服务能够访问这些外部配置。以下是对Spring Cloud Config内网穿透的详细解析:一、内网穿透简介内网穿透是一种网络技术,它允许外部网络(如互联网)上的设备访问位于内部网络(例如公司或家庭局域网LAN)中没有直接公网IP地址的设备。这通常依赖于NAT(网络地址转换)穿越技术,通过创建一个临时的通信通道,使得外网设备可以通过这个通道与内网设备进行数据交换。二、Spring Cloud Config与内网穿透的结合在Spring Cloud微服务架构中,Config组件通常部署在外部网络上,以便集中管理配置。然而,内部网络中的微服务需要访问这些配置。为了实现这一点,可以使用内网穿透技术,将Config服务的端口映射到公网上,从而使得内部网络中的微服务能够通过公网地址访问Config服务。三、内网穿透的实现方式第三方内网穿透服务:使用如natapp、frp等第三方内网穿透服务,这些服务提供了简单易用的客户端和服务器端软件,可以方便地实现内网穿透。用户只需在内部网络中部署客户端软件,并配置好需要穿透的端口,然后在外部网络上访问由第三方服务提供的公网地址和端口即可。自建内网穿透服务器:对于有一定技术能力的用户,可以选择自建内网穿透服务器。这通常涉及到在外部网络上部署一个具有公网IP地址的服务器,并在内部网络中部署客户端软件。通过配置客户端和服务器之间的连接,实现内部网络服务的公网访问。四、Spring Cloud Config内网穿透的具体步骤部署Config服务:在外部网络上部署Spring Cloud Config服务,并确保其能够正常运行。配置内网穿透:根据所选的内网穿透方式(第三方服务或自建服务器),配置相应的客户端和服务器端软件。确保内部网络中的Config客户端能够成功连接到外部网络上的服务器,并将Config服务的端口映射到公网上。更新微服务配置:在内部网络中的微服务配置文件中,将Config服务的地址更新为通过内网穿透获得的公网地址和端口。验证配置访问:启动微服务,并验证其是否能够成功访问Config服务并获取配置。五、注意事项安全性:内网穿透涉及到将内部网络服务暴露到公网上,因此需要注意安全性问题。建议使用HTTPS协议进行通信,并对访问进行身份验证和授权。稳定性:内网穿透服务的稳定性直接影响到微服务的配置访问。因此,在选择内网穿透服务时,需要考虑其稳定性和可靠性。网络延迟:内网穿透可能会增加网络延迟,特别是在跨地域访问时。因此,在部署微服务时,需要考虑到这一点,并尽量将Config服务部署在靠近微服务的位置。综上所述,Spring Cloud Config内网穿透技术使得内部网络中的微服务能够方便地访问外部网络上的配置服务。通过选择合适的内网穿透方式和注意相关事项,可以实现高效、安全的配置管理。
  • [技术干货] Spring Cloud 服务的动态配置-Config实现手动刷新和自动刷新
    在Spring Cloud服务的动态配置中,Config提供了手动刷新和自动刷新两种机制,以确保服务能够实时获取最新的配置信息。以下是关于这两种刷新机制的详细实现方式:一、手动刷新手动刷新是指通过显式地触发刷新操作来更新配置。这通常涉及以下几个步骤:在Config Client中添加依赖:引入spring-boot-starter-actuator依赖,该依赖提供了/refresh端点,用于触发配置的重新加载。配置暴露端点:在Config Client的配置文件中,设置management.endpoints.web.exposure.include=*,以暴露所有的Actuator端点,包括/refresh。声明需要刷新配置的Bean:使用@RefreshScope注解标记需要动态刷新的Bean。当配置更新并触发/refresh端点后,这些Bean将重新初始化,并应用新的配置值。编写接口以验证配置更新:创建一个控制器,用于返回当前配置的值。通过访问该接口,可以验证配置是否已更新。触发刷新操作:向Config Client的/refresh端点发送POST请求,以触发配置的重新加载。这可以通过工具如Postman或curl命令来完成。二、自动刷新自动刷新是指当配置发生变更时,无需手动触发,系统能够自动地更新配置。这通常依赖于Spring Cloud Bus和消息中间件(如RabbitMQ、Kafka等)来实现。以下是实现自动刷新的步骤:搭建消息中间件:根据所选的消息中间件(如RabbitMQ或Kafka),进行安装和配置。在Config Server和Config Client中添加依赖:引入spring-cloud-starter-bus-amqp(对于RabbitMQ)或spring-cloud-starter-bus-kafka(对于Kafka)等依赖。配置Config Server和Config Client:在Config Server和Config Client的配置文件中,设置消息中间件的连接信息,并启用Spring Cloud Bus。配置Git WebHooks(可选):如果配置存储在Git仓库中,可以设置Git WebHooks,以便在配置发生变更时自动触发刷新操作。当Git仓库中的配置发生变更时,Git WebHooks将向Config Server发送HTTP请求,Config Server随后通过Spring Cloud Bus向所有Config Client广播刷新事件。验证自动刷新:更新Git仓库中的配置,并观察Config Client是否能够自动地获取并应用新的配置值。注意事项在生产环境中使用自动刷新功能时,应谨慎操作,以避免因配置错误导致系统故障。确保消息中间件的稳定性和可靠性,以防止刷新事件丢失或延迟。对于一些关键的配置信息,可以采用手动刷新方式,以确保操作的严谨性。综上所述,Spring Cloud Config提供了手动刷新和自动刷新两种机制,以满足不同场景下的配置更新需求。通过合理配置和使用这些机制,可以确保服务能够实时获取最新的配置信息,并提高系统的灵活性和可用性。
  • [技术干货] Spring Cloud 服务的动态配置-Config
    Spring Cloud Config是Spring Cloud体系中的分布式配置管理工具,它允许你将配置(例如数据库配置、服务器端口等)存储在外部(例如Git仓库),并为微服务提供集中化的配置管理。以下是关于Spring Cloud服务的动态配置-Config的详细解析:一、Spring Cloud Config简介Spring Cloud Config分为Config Server和Config Client两部分:Config Server:集中管理配置文件,支持多环境配置和版本控制(通常基于Git仓库)。它对外提供REST接口,供Config Client获取配置。Config Client:微服务通过它来获取并动态加载配置。二、动态配置的核心组件与机制Spring Cloud配置动态更新的核心功能主要依赖以下组件和机制:Config Server:提供统一的配置管理入口,通过REST接口对外暴露配置文件内容。支持多种存储后端,如Git、SVN、文件系统等。Config Client:微服务中的一个客户端模块,负责从Config Server拉取配置。它通过Spring的Environment和PropertySource机制将配置注入到应用上下文中。Spring Boot Actuator:提供了丰富的监控和管理功能,其中的/refresh端点是实现动态更新的关键,用于触发配置的重新加载。Spring Cloud Bus:通过消息中间件(如RabbitMQ、Kafka等)实现事件广播。它在配置更新时,向所有相关服务广播刷新事件,从而实现分布式配置的同步更新。三、动态配置的实现原理配置加载流程:Config Client启动时,通过REST接口从Config Server拉取配置,并将其加载到Spring的Environment中。当配置发生变更时,通过手动或自动触发/refresh端点,重新加载并应用新的配置。配置动态更新的触发机制:手动触发:直接调用Spring Boot Actuator提供的/refresh端点。Actuator的/refresh端点会重新加载配置,并刷新Spring的应用上下文。配合@RefreshScope注解,可以让特定的Bean实例在配置更新后重新初始化,从而应用新的配置值。自动触发:通常通过Spring Cloud Bus实现。配置文件在Git仓库中更新后,通知Config Server。Config Server向消息中间件发送刷新事件。所有订阅该事件的服务接收到通知,并自动调用/refresh端点更新配置。四、动态配置的应用场景数据库配置动态调整:在微服务项目中,数据库的连接参数(如URL、用户名、密码)可能需要动态调整。通过Spring Cloud Config,可以在不重启服务的情况下,实时更新数据库配置。第三方服务接口参数变更:对于调用第三方服务的微服务,其接口地址或认证参数可能会变更。动态更新机制可以快速应用新配置,避免停机操作。高并发场景下的策略调整:在高并发场景下,系统的限流、熔断等策略参数需要灵活调整。通过动态更新,可以快速响应流量变化,保障系统稳定性。五、使用注意事项确保Actuator的相关端点已启用:在Config Client中,需要确保Actuator的/refresh端点等已启用,以便能够接收刷新请求。正确使用@RefreshScope注解:在需要动态刷新的Bean上添加@RefreshScope注解,以便在配置更新后能够重新初始化这些Bean。检查Spring Cloud Bus的配置:如果使用Spring Cloud Bus实现自动刷新,需要确保消息中间件的配置正确,并且消息中间件运行正常。综上所述,Spring Cloud Config为微服务提供了集中化、动态化的配置管理方案。通过合理配置和使用相关组件,可以实现高效的配置管理和更新机制,提高系统的可用性和稳定性。
  • [技术干货] Spring Cloud 服务间消息传递-Stream 消费者手动ack
    在Spring Cloud服务间消息传递中,使用Stream框架时,消费者可以配置为手动提交ack(确认)。这一功能在处理需要确保消息被正确处理的场景时尤为重要,因为它允许消费者在处理完消息后显式地确认消息已被成功处理。以下是对Spring Cloud Stream消费者手动ack的详细解析:一、手动ack的配置YML文件配置:在Spring Cloud Stream的配置文件中,需要为特定的绑定(binding)设置acknowledge-mode为manual,以启用手动ack模式。例如,对于RabbitMQ,配置可能如下所示:spring: cloud: stream: rabbit: bindings: myInputChannel: consumer: acknowledge-mode: manual在这个配置中,myInputChannel是输入通道的名称,acknowledge-mode: manual表示启用手动ack模式。依赖引入:确保项目中已经引入了Spring Cloud Stream和RabbitMQ(或其他消息中间件)的依赖。例如,对于RabbitMQ,可以在pom.xml中添加以下依赖:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency> 二、手动ack的实现在消费者代码中,需要处理接收到的消息,并在处理完成后手动提交ack。这通常涉及以下几个步骤:接收消息:使用@StreamListener注解来监听输入通道上的消息。例如:@StreamListener("myInputChannel") public void handleMessage(Message<MyMessagePayload> message) { // 处理消息逻辑 MyMessagePayload payload = message.getPayload(); // ... 处理逻辑 ... // 手动提交ack Acknowledgment acknowledgment = message.getHeaders().get(RabbitHeaders.ACKNOWLEDGMENT, Acknowledgment.class); if (acknowledgment != null) { acknowledgment.acknowledge(); } } 在这个例子中,MyMessagePayload是消息的有效载荷类型,message是接收到的消息对象。通过message.getHeaders().get(RabbitHeaders.ACKNOWLEDGMENT, Acknowledgment.class)获取到Acknowledgment对象后,调用其acknowledge()方法来手动提交ack。异常处理:在处理消息时,可能会遇到异常情况。为了确保消息不会被丢失,可以在捕获异常后进行适当的处理,例如记录日志、重试消息等。如果决定不提交ack,那么消息中间件可能会认为该消息尚未被处理,并可能会重新发送它。三、注意事项确保消息处理的幂等性:在手动ack模式下,如果消费者在处理消息时失败并决定不提交ack,那么消息中间件可能会重新发送该消息。因此,消费者代码需要确保消息处理的幂等性,即多次处理同一消息时不会产生不同的结果。处理消息超时:某些消息中间件可能设置了消息处理的超时时间。如果消费者在处理消息时超过了这个时间限制,那么消息中间件可能会认为该消息处理失败,并重新发送它。因此,需要确保消费者能够在合理的时间内处理完消息并提交ack。资源释放:在处理完消息并提交ack后,需要确保释放与消息处理相关的所有资源,以避免资源泄漏。综上所述,Spring Cloud Stream消费者手动ack功能为处理需要确保消息被正确处理的场景提供了强大的支持。通过合理配置和编写消费者代码,可以实现可靠的消息传递和处理机制。
  • [技术干货] Spring Cloud 服务间消息传递-Stream 的重复消费问题
    在Spring Cloud服务间消息传递中,使用Stream框架时可能会遇到重复消费的问题。以下是对该问题的详细分析:一、重复消费问题的原因消费者分组不明确:当没有为消费者指定分组时,每个消费者都被视为独立的消费者,它们都会接收到发布的消息。这会导致同一个消息被多个消费者消费,从而产生重复消费的问题。消息中间件特性:某些消息中间件(如RabbitMQ、Kafka等)在默认情况下会向所有订阅者广播消息。如果没有通过分组或其他机制来限制消息的接收者,那么所有订阅了该消息的消费者都会收到它。二、解决重复消费问题的方法明确消费者分组:在Spring Cloud Stream中,可以通过配置来指定消费者分组。将多个消费者指定为同一个消费者组后,这些消费者将共同处理消息,并且每个消息只会被该组中的一个消费者处理。这可以通过在配置文件中设置spring.cloud.stream.bindings.<channel-name>.group属性来实现。使用消息确认机制:某些消息中间件提供了消息确认机制,即消费者在处理完消息后需要向消息中间件发送确认消息。只有当消息中间件收到确认消息后,才会认为该消息已被成功处理,并移除它。这可以防止消息被重复消费。确保消息的唯一性:在发布消息时,可以为每个消息生成一个唯一的标识符(如UUID)。消费者在接收到消息后,可以根据该标识符来判断是否已经处理过该消息。这可以避免对同一消息的重复处理。使用幂等性处理:幂等性是指多次执行同一操作所产生的结果是相同的。在消费者处理消息时,可以设计幂等性处理逻辑,即无论消息被接收多少次,处理结果都是相同的。这可以通过在数据库中记录已处理消息的唯一标识符来实现。三、示例配置以下是一个示例配置文件,展示了如何在Spring Cloud Stream中为消费者指定分组:spring: cloud: stream: bindings: myInputChannel: destination: myTopic group: myConsumerGroup在这个配置中,myInputChannel是输入通道的名称,myTopic是消息主题,myConsumerGroup是消费者组的名称。将多个消费者配置为使用相同的myConsumerGroup值后,它们将共同处理myTopic主题上的消息,并且每个消息只会被该组中的一个消费者处理。四、总结Spring Cloud Stream框架为微服务架构中的服务间消息传递提供了高效、可靠和灵活的解决方案。然而,在使用过程中可能会遇到重复消费的问题。通过明确消费者分组、使用消息确认机制、确保消息的唯一性以及使用幂等性处理等方法,可以有效地解决这一问题。
  • [技术干货] Spring Cloud 服务间消息传递-Stream
    Spring Cloud Stream是一个用于构建消息驱动型微服务的框架,它允许服务间通过消息进行通信。以下是对Spring Cloud服务间消息传递中Stream的详细解析:一、Spring Cloud Stream的基本概念Spring Cloud Stream是Spring Cloud生态系统中的一个组件,建立在Spring Boot之上。它旨在简化和统一消息中间件的集成和使用,提供了一种声明式的方式来定义输入和输出消息通道。通过抽象和封装消息中间件的细节,Spring Cloud Stream屏蔽了不同消息中间件之间的差异,降低了切换消息中间件的成本。二、Spring Cloud Stream的核心组件Binder:Binder是应用与消息中间件之间的封装。它实现了应用程序与消息中间件细节之间的隔离,使得开发人员可以更加专注于业务逻辑的实现。目前,Spring Cloud Stream已经实现了Kafka和RabbitMQ的Binder。消息通道:消息通道是Spring Cloud Stream中用于传递消息的基础设施。它分为输入通道和输出通道两种类型。输入通道用于接收消息,而输出通道用于发送消息。开发人员可以通过注解或配置文件来定义消息通道。消息转换器:消息转换器用于将消息从一种格式转换为另一种格式。例如,可以将JSON格式的消息转换为Java对象,或者将Java对象转换为JSON格式的消息。Spring Cloud Stream提供了多种消息转换器,以满足不同的需求。三、Spring Cloud Stream的使用步骤添加依赖:在项目的pom.xml文件中添加Spring Cloud Stream和所选消息中间件(如RabbitMQ或Kafka)的依赖。配置消息中间件:在application.yml或application.properties文件中配置所选消息中间件的连接信息和其他相关属性。定义消息通道:使用@Input注解定义输入通道,使用@Output注解定义输出通道。这些通道将用于接收和发送消息。实现消息发送和接收逻辑:使用@EnableBinding注解启用绑定器,并将应用程序绑定到指定的消息通道。然后,使用@StreamListener注解定义消息监听器方法,以处理从输入通道接收到的消息。同时,可以使用MessageChannel的send方法发送消息到输出通道。启动应用程序:运行Spring Boot应用程序,Spring Cloud Stream将自动连接到所选的消息中间件,并开始处理消息。四、Spring Cloud Stream的优势简化消息中间件集成:Spring Cloud Stream提供了统一的编程模型和配置方式,简化了消息中间件的集成过程。支持多种消息中间件:Spring Cloud Stream支持多种常见的消息中间件,如RabbitMQ和Kafka等,使得开发人员可以根据需求选择合适的消息中间件进行集成。提供丰富的功能:Spring Cloud Stream提供了消息转换、消息分组、消息重试、错误通知和死信队列等机制,以确保消息的可靠性和可恢复性。易于扩展和定制:Spring Cloud Stream提供了灵活的扩展和定制选项,允许开发人员根据需求进行定制和扩展。五、示例场景假设有两个微服务:订单服务和库存服务。当订单服务创建一个新订单时,它需要通知库存服务更新库存数量。通过使用Spring Cloud Stream,订单服务可以将新订单的消息发送到一个输出通道,而库存服务可以从一个输入通道接收该消息并更新库存数量。这样,订单服务和库存服务之间就实现了松耦合的通信方式,它们不需要直接调用对方的API或共享数据库。综上所述,Spring Cloud Stream为微服务架构中的服务间消息传递提供了一种高效、可靠和灵活的解决方案。通过简化消息中间件的集成和使用,它使得开发人员可以更加专注于业务逻辑的实现,从而提高了开发效率和系统的可扩展性。
  • [技术干货] Spring Cloud 多语言支持-Sidecar
    Spring Cloud Sidecar是一种支持多语言微服务的架构模式,它允许非Java程序或第三方接口接入Spring Cloud生态系统。以下是对Spring Cloud多语言支持中Sidecar的详细解析:一、Sidecar的基本概念Sidecar(边车)模式是一种将一组紧密结合的任务与主应用程序共同放在一台主机(Host)中,但将它们部署在各自的进程或容器中的方法。边车服务不一定是应用程序的一部分,但它与主应用程序相关联,并适用于父应用程序的任何位置。对于应用程序的每个实例,边车的实例被部署并与其一起托管。二、Sidecar的工作原理在Spring Cloud项目中,如果需要接入一些非Java程序或第三方接口(这些程序无法直接接入Eureka、Hystrix、Feign等Spring Cloud组件),可以通过启动一个代理的微服务(即Sidecar)去和非Java程序或第三方接口进行交流。然后,再将这个代理的微服务注册到Spring Cloud的相关组件中。这样,非Java服务就可以通过Sidecar在服务注册表中注册,并使用服务发现来查找其他服务。三、Sidecar的实现步骤创建一个Spring Boot项目:这个项目将作为Sidecar的载体。添加相关依赖:在项目的pom.xml文件中添加Spring Cloud Sidecar和Eureka Client的依赖。启用Sidecar功能:在主应用程序类上添加@EnableSidecar注解来开启Sidecar功能。配置Sidecar:在application.yml或application.properties文件中配置Sidecar的相关属性,如所代理的服务的端口号、健康检查URL等。启动Eureka服务器:确保Eureka服务器已经启动,并且Sidecar可以注册到Eureka上。启动Sidecar应用程序:运行Spring Boot应用程序,Sidecar将会启动并注册到Eureka服务器上。四、Sidecar的优势多语言支持:Sidecar允许非Java程序接入Spring Cloud生态系统,实现了多语言微服务的支持。服务注册与发现:通过Sidecar,非Java服务可以在服务注册表中注册,并使用服务发现来查找其他服务。零侵入性:对于异构服务代码,Sidecar提供了零侵入性的解决方案,不需要直接根据Nacos或其他注册中心API进行注册。简化配置:通过Sidecar,可以简化非Java服务的配置和管理,使其更容易集成到Spring Cloud生态系统中。五、示例场景假设有一个用Node.js编写的微服务,它提供了一个/hello端点。通过Spring Cloud Sidecar,可以将这个Node.js微服务注册到Eureka服务器上,并通过Zuul代理或Feign客户端来访问它。这样,即使Node.js微服务不是用Java编写的,也可以轻松地与Spring Cloud生态系统中的其他服务进行交互。综上所述,Spring Cloud Sidecar是一种强大的多语言支持解决方案,它允许非Java程序接入Spring Cloud生态系统,并提供了服务注册、发现、配置管理等一系列功能。通过Sidecar,可以轻松地实现多语言微服务的集成和管理。
  • [技术干货] SpringBoot实现websocket服务端及客户端的详细过程【转】
    一、WebSocket通信过程客户端构建一个websocket实例,并且为它绑定一个需要连接到的服务器地址,当客户端连接服务端的候,会向服务端发送一个http get报文,告诉服务端需要将通信协议切换到websocket,服务端收到http请求后将通信协议切换到websocket,同时发给客户端一个响应报文,返回的状态码为101,表示同意客户端协议转请求,并转换为websocket协议。以上过程都是利用http通信完成的,称之为websocket协议握手(websocket Protocol handshake),经过握手之后,客户端和服务端就建立了websocket连接,以后的通信走的都是websocket协议了。二、服务端实现1.pom文件添加依赖       <!--webSocket-->         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-websocket</artifactId>         </dependency>2.启用Springboot对WebSocket的支持package com.lby.websocket.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /**  * @author Liby  * @date 2022-04-25 16:18  * @description:  * @version:  */ @Configuration public class WebSocketConfig {     @Bean     public ServerEndpointExporter serverEndpointExporter() {         return new ServerEndpointExporter();     } } 3.核心配置:WebSocketServer因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller@ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端新建一个ConcurrentHashMap webSocketMap 用于接收当前userId的WebSocket,方便传递之间对userId进行推送消息。下面是具体业务代码:package org.example.server; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; /* 启动项目后可通过 http://websocket.jsonin.com/ 进行测试 输入网址ws://localhost:8080/websocket/1211,检测是否能进行连接  */ @ServerEndpoint(value = "/websocket/{userId}") @Component public class WebSocket {     private final static Logger logger = LogManager.getLogger(WebSocket.class);     /**      * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的      */     private static int onlineCount = 0;     /**      * concurrent包的线程安全Map,用来存放每个客户端对应的MyWebSocket对象      */     private static ConcurrentHashMap<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();     /**      * 与某个客户端的连接会话,需要通过它来给客户端发送数据      */     private Session session;     private String userId;     /**      * 连接建立成功调用的方法      */     @OnOpen     public void onOpen(Session session, @PathParam("userId") String userId) {         this.session = session;         this.userId = userId;         //加入map         webSocketMap.put(userId, this);         addOnlineCount();           //在线数加1         logger.info("用户{}连接成功,当前在线人数为{}", userId, getOnlineCount());         try {             sendMessage(String.valueOf(this.session.getQueryString()));         } catch (IOException e) {             logger.error("IO异常");         }     }     /**      * 连接关闭调用的方法      */     @OnClose     public void onClose() {         //从map中删除         webSocketMap.remove(userId);         subOnlineCount();           //在线数减1         logger.info("用户{}关闭连接!当前在线人数为{}", userId, getOnlineCount());     }     /**      * 收到客户端消息后调用的方法      *      * @param message 客户端发送过来的消息      */     @OnMessage     public void onMessage(String message, Session session) {         logger.info("来自客户端用户:{} 消息:{}",userId, message);         //群发消息         for (String item : webSocketMap.keySet()) {             try {                 webSocketMap.get(item).sendMessage(message);             } catch (IOException e) {                 e.printStackTrace();             }         }     }     /**      * 发生错误时调用      *      */     @OnError     public void onError(Session session, Throwable error) {         logger.error("用户错误:" + this.userId + ",原因:" + error.getMessage());         error.printStackTrace();     }     /**      * 向客户端发送消息      */     public void sendMessage(String message) throws IOException {         this.session.getBasicRemote().sendText(message);         //this.session.getAsyncRemote().sendText(message);     }     /**      * 通过userId向客户端发送消息      */     public void sendMessageByUserId(String userId, String message) throws IOException {         logger.info("服务端发送消息到{},消息:{}",userId,message);         if(StrUtil.isNotBlank(userId)&&webSocketMap.containsKey(userId)){             webSocketMap.get(userId).sendMessage("hello");         }else{             logger.error("用户{}不在线",userId);         }     }     /**      * 群发自定义消息      */     public void sendInfo(String message) throws IOException {         for (String item : webSocketMap.keySet()) {             try {                 webSocketMap.get(item).sendMessage(message);             } catch (IOException e) {                 e.printStackTrace();             }         }     }     public static synchronized int getOnlineCount() {         return onlineCount;     }     public static synchronized void addOnlineCount() {         WebSocket.onlineCount++;     }     public static synchronized void subOnlineCount() {         WebSocket.onlineCount--;     } } 三、客户端实现1.pom文件添加依赖        <dependency>             <groupId>org.java-websocket</groupId>             <artifactId>Java-WebSocket</artifactId>             <version>1.5.3</version>         </dependency>2.客户端实现代码package org.example; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import java.net.URI; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /**  * @author kele  * @date 2024/2/19  **/ @Slf4j public class MyWebSocketClient extends WebSocketClient {     public MyWebSocketClient(URI serverUri) {         super(serverUri);     }     @SneakyThrows     @Override     public void onOpen(ServerHandshake data) {         try {             log.info("WebSocket连接已打开。");         }catch (Exception e){             log.error("onOpen error :{}",e.getMessage());         }     }     @SneakyThrows     @Override     public void onMessage(String message) {         try {             if (message != null && !message.isEmpty()) {                 log.info("收到消息: {}",message);             }         }catch (Exception e){             log.error("onMessage error : {}",message);         }     }     @Override     public void onClose(int code, String reason, boolean remote) {         log.info("WebSocket连接已关闭。");     }     @Override     public void onError(Exception ex) {         log.info("WebSocket连接发生错误:{}", ex.getMessage());     }     /**      * 连接定时检查      */     public void startReconnectTask(long delay, TimeUnit unit) {         System.out.println("WebSocket 心跳检查");         log.info("WebSocket 心跳检查");         // 以下为定时器,建议使用自定义线程池,或交给框架处理(spring)         ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();         executorService.scheduleWithFixedDelay(() -> {             // 检查逻辑:判断当前连接是否连通             if (!this.isOpen()) {                 System.out.println("WebSocket 开始重连......");                 log.info("WebSocket 开始重连......");                 // 重置连接                 this.reconnect();                 // 以下为错误示范                 //this.close();                 //this.connect();             }         }, 0, delay, unit);     } }3.开启客户端连接服务,并开启定时检查websocket连接package org.example; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.net.URI; import java.util.concurrent.TimeUnit; @Slf4j @Component public class Init implements Runnable {     public static MyWebSocketClient myWebSocketClient;     @PostConstruct     public void run () {         try {             //启动连接             log.info("连接websocket服务端");             log.info("项目启动");             // 服务地址             URI uri = new URI("ws://127.0.0.1:8077/websocket/123");             log.info("服务地址 -{}", uri);             // 创建客户端             myWebSocketClient = new MyWebSocketClient(uri);             // 建立连接             myWebSocketClient.connect();             // 开启 定时检查             myWebSocketClient.startReconnectTask(5, TimeUnit.SECONDS);         }catch (Exception e){             e.printStackTrace();         }     } }
  • [技术干货] SpringBoot中Get请求和POST请求接收参数示例详解【转】
    1、Get请求1.1 方法形参接收参数 这种方式一般适用参数比较少的情况,并且前后端参数名称必须保持一致@RestController @RequestMapping("/user") @Slf4j public class DemoController {     @GetMapping("/query")     public void getStudent(String name,String age) {         log.info("name:{}",name);         log.info("age:{}",age);     } }参数用 @RequestParam 标注,使用value属性指定参数名,required属性表示这个参数是否必传@RestController @RequestMapping("/user") @Slf4j public class DemoController {     @GetMapping("/query")     public void getStudent(@RequestParam(value = "name", required = false) String name,                            @RequestParam(value = "age", required = false) String age) {         log.info("name:{}",name);         log.info("age:{}",age);     } }1.2 实体类接收参数注意:Get 请求以实体类接收参数时,不能用 RequestParam 注解进行标注,因为不支持这样的方式获取参数。@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @GetMapping("/query")     public void getStudent(Student student) {         log.info("name:{}",student.getName());         log.info("age:{}",student.getAge());     } } @Data class Student{     private String name;     private Integer age; }1.3 通过HttpServletRequest接收参数@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @GetMapping("/query")     public void getStudent(HttpServletRequest request) {         String name = request.getParameter("name");         String phone = request.getParameter("age");         log.info("name:{}",name);         log.info("age:{}",age);     } }1.4 通过@PathVariable接收参数@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @GetMapping("/query/{name}/{age}")     public void getStudent(@PathVariable String name, @PathVariable String age) {         log.info("name:{}",name);         log.info("age:{}",age);     } }1.5 接收数组参数@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @GetMapping("/query")     public void getStudent(String[] names) {         Arrays.stream(names).forEach(System.out::println);     } }1.6 接受集合参数注意:SpringBoot 接收集合参数,必须用 @RequestParam 注解声明!@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @GetMapping("/query")     public void getStudent(@RequestParam List<String> names) {         names.forEach(System.out::println);     } }2、POST请求2.1 方法形参接收参数 前后端参数名称必须保持一致@RestController @RequestMapping("/user") @Slf4j public class DemoController {     @PostMapping("/save")     public void saveStudent(String name,String age) {         log.info("name:{}",name);         log.info("age:{}",age);     } }参数用 @RequestParam 标注,使用value属性指定参数名,required属性表示这个参数是否必传@RestController @RequestMapping("/user") @Slf4j public class DemoController {     @PostMapping("/save")     public void saveStudent(@RequestParam(value = "name", required = false) String name,                            @RequestParam(value = "age", required = false) String age) {         log.info("name:{}",name);         log.info("age:{}",age);     } }2.2 通过HttpServletRequest接收参数@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @PostMapping("/save")     public void saveStudent(HttpServletRequest request) {         String name = request.getParameter("name");         String phone = request.getParameter("age");         log.info("name:{}",name);         log.info("age:{}",age);     } }2.3 通过@PathVariable接收参数@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @PostMapping("/save/{name}/{age}")     public void saveStudent(@PathVariable String name, @PathVariable String age) {         log.info("name:{}",name);         log.info("age:{}",age);     } }2.4 通过param方式提交参数,以实体类接收参数直接以实体类可以接收param、form-data、 x-www-form-urlencoded 提交的参数@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @PostMapping("/save")     public void saveStudent(Student student) {         log.info("name:{}",student.getName());         log.info("age:{}",student.getAge());     } } @Data class Student{     private String name;     private Integer age; }2.5 请求体以JSON格式提交参数,通过 @RequestBody 注解接收参数接受实体类JSON参数@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @PostMapping("/save")     public void saveStudent(@RequestBody Student student) {         log.info("name:{}",student.getName());         log.info("age:{}",student.getAge());     } } @Data class Student{     private String name;     private Integer age; }2.6 通过 Map 接收参数@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @PostMapping("/save")     public void saveStudent(@RequestParam Map<String,Object> map) {         log.info("name:{}",map.get("name"));         log.info("age:{}",map.get("age"));     } }2.7 通过@RequestBody 接收一个参数@RestController @RequestMapping("/demo") @Slf4j public class DemoController {     @PostMapping("/save")     public void saveStudent(@RequestBody String name) {         log.info("name:{}",name);     } }