当前位置: 首页 > news >正文

SprringCloud Gateway动态添加路由不重启

文章目录

      • 前言:
      • 一、动态路由必要性
      • 二、SpringCloud Gateway路由加载过程
        • RouteDefinitionLocator接口
        • PropertiesRouteDefinitionLocator类
        • DiscoveryClientRouteDefinitionLocator
        • InMemoryRouteDefinitionRepository
        • CompositeRouteDefinitionLocator类
        • CachingRouteDefinitionLocator类
        • RouteLocator接口
        • Route类
        • RouteDefinitionRouteLocator类
        • CachingRouteLocator类
        • RouteRefreshListener类
      • 三、Nacos实现动态路由
      • 四、通过 Spring Boot Actuator实现动态路由
      • 五、通过事件刷新机制自定义实现动态路由
        • GatewayRouteEventPublisherAware类
        • NacosRouteRefreshListener类
        • NacosGatewayConfig类
        • NacosRouteListener类
        • isrm-gateway配置文件
        • 初始化流程
        • 监听流程


前言:

在微服务项目中,SpringCloud Gateway扮演着极为重要的角色,主要提供了路由、负载均衡、认证鉴权等功能。本文主要讲解如何实现网关的自定义动态路由配置,无需重启网关模块即可生效。

一、动态路由必要性

在微服务架构中,随着功能的迭代和上线,经常需要在网关添加路由配置。传统的做法是通过修改配置文件并重启网关服务来实现,但这种方式会导致服务中断,给用户带来不便。

例如如下配置:

spring:  cloud:  gateway:  routes:    - id: system  predicates:  - Path=/api/system/**  filters:  - StripPrefix=2  uri: lb://isrm-system-provider- id: basic  predicates:  - Path=/api/basic/**  filters:  - StripPrefix=2  uri: lb://isrm-basic-provider

该配置写在jar包同级的 isrm-gateway.yml文件中,假如现在网关模块是运行的,并且路由配置只有system模块。此时系统增加了basic模块,需要在配置文件中进行路由配置,但是配置完成之后,路由并未生效,只能重启网关模块去读取最新的配置来加载路由信息,重启过程中,整个网关模块都是用不了的,所有经过网关的请求都会失败,影响用户的体验。因此,动态添加路由而不重启服务成为了一个实际需求。

二、SpringCloud Gateway路由加载过程

SpringCloud Gateway路由加载过程

在看完上面的文章大概知道了路由相关类和接口的相关作用

  • RoutePredicateFactory,断言工厂,用于创建具体的断言。
  • GatewayFilterFactory,过滤器工厂,用于创建具体的过滤器。
  • Predicate,断言接口。
  • GatewayFilter,过滤器接口。
  • RouteDefinition,路由定义对象,在yml里配置的路由规则其实就是配置它,包含一组断言工厂和过滤器工厂。
  • Route, 路由对象,包含了一组断言规则列表和过滤器列表。
  • RouteDefinitionLocator,用于获取一组RouteDefinition,最常见的就是从yml里配置,或者基于服务发现的默认路由配置。
  • RouteLocator,用于把RouteDefinition转换成真正的Route对象。
RouteDefinitionLocator接口

主要有以下的实现类,该接口主要用来获取路由定义信息,比如上面yml配置文件的路由信息

在这里插入图片描述

PropertiesRouteDefinitionLocator类
public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {private final GatewayProperties properties;public PropertiesRouteDefinitionLocator(GatewayProperties properties) {this.properties = properties;}@Overridepublic Flux<RouteDefinition> getRouteDefinitions() {return Flux.fromIterable(this.properties.getRoutes());}}

主要获取配置文件的路由信息,其中GatewayProperties类标注着@ConfigurationProperties(“spring.cloud.gateway”)注解,可以获取配置文件中spring.cloud.gateway前缀的配置内容

DiscoveryClientRouteDefinitionLocator

spring.cloud.gateway.discovery.locator.enabled=true为ture时会装配该Bean

主要是从注册中心获取所有的服务列表,然后挨个加入Path断言以及去掉第一段路径的过滤器;比如某个服务名称为isrm-basic-provider,那么它会被该类发现,并加入Path断言’/isrm-basic-provider/**‘和过滤器’‘/isrm-basic-provider/(?. *)’,当访问/isrm-basic-provider/user/get时会被拦截,有过滤器重写路径为/user/get,最终访问lb://isrm-basic-provider/user/get。

如图,我并没有在配置文件配置相关路由信息,也可以经过网关访问basic服务的接口,因此可以通过DiscoveryClientRouteDefinitionLocator类发现注册的服务,然后添加默认的路由定义信息

在这里插入图片描述

InMemoryRouteDefinitionRepository

主要提供了对路由定义信息的增加、删除、查询方法,由一个LinkedHashMap变量存储,并包装成线程安全的SynchronizedMap

public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {private final Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());@Overridepublic Mono<Void> save(Mono<RouteDefinition> route) {return route.flatMap(r -> {if (StringUtils.isEmpty(r.getId())) {return Mono.error(new IllegalArgumentException("id may not be empty"));}routes.put(r.getId(), r);return Mono.empty();});}@Overridepublic Mono<Void> delete(Mono<String> routeId) {return routeId.flatMap(id -> {if (routes.containsKey(id)) {routes.remove(id);return Mono.empty();}return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));});}@Overridepublic Flux<RouteDefinition> getRouteDefinitions() {return Flux.fromIterable(routes.values());}}
CompositeRouteDefinitionLocator类

把其它的RouteDefinitionLocator组合在一起,也是Spring Cloud Gateway默认装配的RouteDefinitionLocator bean。加了@Primary注解会优先注入。

// GatewayAutoConfiguration类部分代码,通过入参List<RouteDefinitionLocator> routeDefinitionLocators会把除了CachingRouteDefinitionLocator类
// 的所有RouteDefinitionLocator的实现类注入进来
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
}

CompositeRouteDefinitionLocator代码

public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator {private static final Log log = LogFactory.getLog(CompositeRouteDefinitionLocator.class);private final Flux<RouteDefinitionLocator> delegates;private final IdGenerator idGenerator;public CompositeRouteDefinitionLocator(Flux<RouteDefinitionLocator> delegates) {this(delegates, new AlternativeJdkIdGenerator());}public CompositeRouteDefinitionLocator(Flux<RouteDefinitionLocator> delegates,IdGenerator idGenerator) {this.delegates = delegates;this.idGenerator = idGenerator;}/** * 主要逻辑就是遍历所有注入的RouteDefinitionLocator的实现类,执行它们的getRouteDefinitions方法,合并它们返回的路由定义信息,如果路由定义信息没有* id,则默认生成一个随机id* PropertiesRouteDefinitionLocator:获取配置文件的路由定义信息* DiscoveryClientRouteDefinitionLocator:按服务名称生成默认的路由定义信息* InMemoryRouteDefinitionRepository:获取维护的路由定义信息,后续SpringBoot Actuator实现动态路由以及事件刷新机制实现动态路由都是基于该类的方法      * 实现的*/@Overridepublic Flux<RouteDefinition> getRouteDefinitions() {return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions).flatMap(routeDefinition -> {if (routeDefinition.getId() == null) {return randomId().map(id -> {routeDefinition.setId(id);if (log.isDebugEnabled()) {log.debug("Id set on route definition: " + routeDefinition);}return routeDefinition;});}return Mono.just(routeDefinition);});}protected Mono<String> randomId() {return Mono.fromSupplier(idGenerator::toString).publishOn(Schedulers.boundedElastic());}}
CachingRouteDefinitionLocator类

该类实现了ApplicationListener接口。主要逻辑是调用CompositeRouteDefinitionLocator类的getRouteDefinitions方法,因为CompositeRouteDefinitionLocator持有了一个RouteDefinitionLocator接口的实现类列表,所以调用getRouteDefinitions方法时,会依次调用它们的getRouteDefinitions方法,并将结果合并之后,缓存到CachingRouteDefinitionLocator类的routeDefinitions和cache中,这样就不需要每次都去调用fetch方法获取路由定义信息,只有监听到RefreshRoutesEvent事件时,才会重新调用fetch方法获取最新的路由定义信息。

注意:调试代码查看流程的时候,好像该类并没有被注入进来,而RefreshRoutesEvent事件会被下面介绍的CachingRouteLocator类处理

public class CachingRouteDefinitionLocator implements RouteDefinitionLocator, ApplicationListener<RefreshRoutesEvent> {private static final String CACHE_KEY = "routeDefs";private final RouteDefinitionLocator delegate;private final Flux<RouteDefinition> routeDefinitions;private final Map<String, List> cache = new ConcurrentHashMap<>();// 构造方法会注入RouteDefinitionLocator接口的实现类,因为CompositeRouteDefinitionLocator加了@Primary注解,所以会注入该类。public CachingRouteDefinitionLocator(RouteDefinitionLocator delegate) {this.delegate = delegate;// 执行fetch方法,获取所有路由定义信息,缓存起来routeDefinitions = CacheFlux.lookup(cache, CACHE_KEY, RouteDefinition.class).onCacheMissResume(this::fetch);}// 执行CompositeRouteDefinitionLocator类的getRouteDefinitions方法private Flux<RouteDefinition> fetch() {return this.delegate.getRouteDefinitions();}// 获取所有缓存的路由信息@Overridepublic Flux<RouteDefinition> getRouteDefinitions() {return this.routeDefinitions;}/*** Clears the cache of routeDefinitions.* @return routeDefinitions flux*/public Flux<RouteDefinition> refresh() {this.cache.clear();return this.routeDefinitions;}// 监听RefreshRoutesEvent事件,调用fetch方法,获取最新的路由定义信息@Overridepublic void onApplicationEvent(RefreshRoutesEvent event) {fetch().materialize().collect(Collectors.toList()).doOnNext(routes -> cache.put(CACHE_KEY, routes)).subscribe();}@Deprecated/* for testing */ void handleRefresh() {refresh();}}
RouteLocator接口

主要有以下实现类,该接口主要用于把RouteDefinition路由定义信息对象转换成真实的Route路由对象。

在这里插入图片描述

Route类

部分代码

public class Route implements Ordered {private final String id;private final URI uri;private final int order;private final AsyncPredicate<ServerWebExchange> predicate;private final List<GatewayFilter> gatewayFilters;private final Map<String, Object> metadata;
}

作用:

  1. 基本构建块:Route是Gateway网关的基本构建块,它负责定义和处理进入网关的网络请求。
  2. 组成元素:
    • ID:每个Route都有一个唯一的ID,用于标识和区分不同的路由规则。
    • 目标URI:目标URI指定了当路由匹配成功后,请求应该被转发到的目标地址或服务。
    • 断言(Predicate)集合:断言是路由处理的第一个环节,它是一个集合,可以包含多个断言规则。这些断言规则用于匹配HTTP请求的不同属性,只有当所有断言都匹配成功时,才认为该请求匹配了当前路由。
    • 过滤器(Filter)集合:如果请求通过了断言匹配,那么它将被发送到过滤器集合进行处理。过滤器可以对请求进行一系列的操作,如权限验证、参数修改等。过滤器可以在请求被转发之前或之后执行,提供了对请求和响应的精细化控制。
  3. 路由匹配:当客户端向Gateway发出请求时,Gateway会根据定义的Route对象进行路由匹配。如果请求与某个Route的断言集合匹配成功,那么该请求将被转发到该Route指定的目标URI,并经过该Route的过滤器集合处理。
  4. 服务发现和负载均衡:如果目标URI是基于服务注册名的方式(如Eureka中注册的服务名称),那么Gateway会借助服务发现机制(如Ribbon)来实现负载均衡,将请求分发到合适的服务实例上执行。

Route对象在Gateway中起到了定义路由规则、匹配网络请求、处理请求和响应的重要作用。通过配置合适的Route对象,可以实现复杂的路由逻辑和精细化的控制策略,提高系统的可扩展性和可维护性。

RouteDefinitionRouteLocator类

主要将RouteDefinition路由定义信息对象转换成真实的Route路由对象

GatewayAutoConfiguration部分代码

// 在创建RouteDefinitionRouteLocator的Bean时,会注入相关过滤器工厂、断言工厂、配置类、CompositeRouteDefinitionLocator对象
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,List<GatewayFilterFactory> gatewayFilters,List<RoutePredicateFactory> predicates,RouteDefinitionLocator routeDefinitionLocator,ConfigurationService configurationService) {return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,gatewayFilters, properties, configurationService);
}

RouteDefinitionRouteLocator部分代码

public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware {private final RouteDefinitionLocator routeDefinitionLocator;private final ConfigurationService configurationService;private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();private final GatewayProperties gatewayProperties;/*** 将容器中的断言工厂,过滤器工厂放到Map中,key为工厂名称前缀* 如PathRoutePredicateFactory, 则key=Path* 这些断言工厂和过滤器工厂基本都在GatewayAutoConfiguration自动配置类注册到Spring容器中的*/public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,List<RoutePredicateFactory> predicates,List<GatewayFilterFactory> gatewayFilterFactories,GatewayProperties gatewayProperties,ConfigurationService configurationService) {this.routeDefinitionLocator = routeDefinitionLocator;this.configurationService = configurationService;initFactories(predicates);gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));this.gatewayProperties = gatewayProperties;}/*** 调用CompositeRouteDefinitionLocator对象的getRouteDefinitions方法,获取所有路由定义信息,然后遍历路由定义信息列表,调用断言工厂以及过滤器工厂      * 的相关方法将断言定义信息和过滤器定义信息转成断言和过滤器,接着生成一个路由对象,添加到routes中,最后返回所有路由对象*/@Overridepublic Flux<Route> getRoutes() {Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute);if (!gatewayProperties.isFailOnRouteDefinitionError()) {// instead of letting error bubble up, continueroutes = routes.onErrorContinue((error, obj) -> {if (logger.isWarnEnabled()) {logger.warn("RouteDefinition id " + ((RouteDefinition) obj).getId()+ " will be ignored. Definition has invalid configs, "+ error.getMessage());}});}return routes.map(route -> {if (logger.isDebugEnabled()) {logger.debug("RouteDefinition matched: " + route.getId());}return route;});}
}
CachingRouteLocator类

主要作用是缓存路由对象信息,不然每次请求都会生成新的路由对象信息

GatewayAutoConfiguration部分代码

/**在创建CachingRouteLocator类的Bean时,会创建CompositeRouteLocator对象,而CompositeRouteLocator对象又会持有参数中注入的* RouteDefinitionRouteLocator的Bean*/
@Bean
@Primary
@ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
// TODO: property to disable composite?
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}

CompositeRouteLocator代码

public class CompositeRouteLocator implements RouteLocator {private final Flux<RouteLocator> delegates;public CompositeRouteLocator(Flux<RouteLocator> delegates) {this.delegates = delegates;}// 调用RouteDefinitionRouteLocator的getRoutes方法获取路由对象信息@Overridepublic Flux<Route> getRoutes() {return this.delegates.flatMap(RouteLocator::getRoutes);}}

CachingRouteLocator代码

public class CachingRouteLocator implements Ordered, RouteLocator, ApplicationListener<RefreshRoutesEvent> {private static final String CACHE_KEY = "routes";private final RouteLocator delegate;private final Flux<Route> routes;// 缓存路由信息private final Map<String, List> cache = new ConcurrentHashMap<>();public CachingRouteLocator(RouteLocator delegate) {this.delegate = delegate;routes = CacheFlux.lookup(cache, CACHE_KEY, Route.class).onCacheMissResume(this::fetch);}// 调用CompositeRouteLocator的方法获取路由对象信息,每次调用都会生成新的路由信息private Flux<Route> fetch() {return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);}// 获取路由信息@Overridepublic Flux<Route> getRoutes() {return this.routes;}/*** Clears the routes cache.* @return routes flux*/public Flux<Route> refresh() {this.cache.clear();return this.routes;}// 监听RefreshRoutesEvent事件,调用fetch方法生成新的路由信息,同时放入缓存中@Overridepublic void onApplicationEvent(RefreshRoutesEvent event) {fetch().materialize().collect(Collectors.toList()).doOnNext(routes -> cache.put(CACHE_KEY, routes)).subscribe();}@Deprecated/* for testing */ void handleRefresh() {refresh();}@Overridepublic int getOrder() {return 0;}}

因此,我们想要获取最新的路由信息,只需要发布一个RefreshRoutesEvent事件即可

在这里插入图片描述

RouteRefreshListener类

除了发布RefreshRoutesEvent事件可以获取最新路由信息之外,当Nacos配置中心发布新配置时,也会去重新获取路由信息

public class RouteRefreshListener implements ApplicationListener<ApplicationEvent> {private final ApplicationEventPublisher publisher;private HeartbeatMonitor monitor = new HeartbeatMonitor();public RouteRefreshListener(ApplicationEventPublisher publisher) {Assert.notNull(publisher, "publisher may not be null");this.publisher = publisher;}@Overridepublic void onApplicationEvent(ApplicationEvent event) {/*** ContextRefreshedEvent:Spring容器初始化完成之后会发布该事件,初始化路由信息* RefreshScopeRefreshedEvent:配置中心发生变化后@RefreshScope或@ConfigurationProperties标注的bean刷新完之后会发布该事件,                 	    * 然后PropertiesRouteDefinitionLocator会获取配置文件新的定义信息* InstanceRegisteredEvent:服务注册会发布该事件,DiscoveryClientRouteDefinitionLocator会处理服务名称,获取默认路由定义信息*/if (event instanceof ContextRefreshedEvent|| event instanceof RefreshScopeRefreshedEvent|| event instanceof InstanceRegisteredEvent) {reset();}else if (event instanceof ParentHeartbeatEvent) {ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;resetIfNeeded(e.getValue());}else if (event instanceof HeartbeatEvent) {HeartbeatEvent e = (HeartbeatEvent) event;resetIfNeeded(e.getValue());}}private void resetIfNeeded(Object value) {if (this.monitor.update(value)) {reset();}}// 发布RefreshRoutesEvent,获取新的路由信息private void reset() {this.publisher.publishEvent(new RefreshRoutesEvent(this));}}

接下来说一下我所知道的三种动态路由实现方式

三、Nacos实现动态路由

前面讲了RouteRefreshListener这个监听器会监听RefreshScopeRefreshedEvent事件,当在Nacos修改了路由配置,点击发布按钮就会发布RefreshScopeRefreshedEvent事件,然后监听器监听到了这个事件,就会重新获取新的路由定义信息,然后再将这些路由定义信息转换成真正的路由对象保存在内存中。

例如我Nacos中的配置文件如下:

spring:cloud:gateway:routes:- id: suppredicates: - Path=/api/sup/**filters:- StripPrefix=2uri: lb://isrm-sup-provider- id: authpredicates: - Path=/api/auth/**filters:- StripPrefix=2uri: lb://isrm-auth-provider- id: basicpredicates: - Path=/api/basic/**filters:- StripPrefix=2uri: lb://isrm-basic-provider- id: systempredicates: - Path=/api/system/**filters:- StripPrefix=2uri: lb://isrm-system-provider

因为我上面没有配置合同模块的路由定义信息,所以我在本地访问合同模块的查询接口时,会报下面的异常信息,找不到对应的路由

'Failed to handle request [POST http://localhost:8081/api/contract/tContract/query]: 404 NOT_FOUND "No matching handler

在这里插入图片描述

在网关的配置文件加入合同模块的路由定义信息

在这里插入图片描述

此时点击发布按钮,配置中心的配置发生了变化,会发布一个RefreshScopeRefreshedEvent事件,RouteRefreshListener监听到这个事件会发布一个RefreshRoutesEvent事件

在这里插入图片描述

然后CachingRouteLocator类会监听RefreshRoutesEvent事件,接着调用CompositeRouteLocator类的方法

在这里插入图片描述

CompositeRouteLocator类接着调用RouteDefinitionRouteLocator类的方法

在这里插入图片描述

RouteDefinitionRouteLocator里面会调用CompositeRouteDefinitionLocator方法获取所有路由定义信息,并转换成真实的Route对象

在这里插入图片描述

CompositeRouteDefinitionLocator依次会调用其他RouteDefinitionLocator实现类的方法获取路由定义信息

在这里插入图片描述

PropertiesRouteDefinitionLocator类主要是获取配置文件定义的路由信息的,因为GatewayProperties被@ConfigurationProperties(“spring.cloud.gateway”)注解标注,所以它能获取最新的配置

在这里插入图片描述

刚刚加入的合同模块路由配置已经被读取到了,如下图,拿到这些信息就可以动态地去更新网关服务的路由信息了,不需要重启服务

在这里插入图片描述

此时我们再次访问合同模块的查询接口,可以发现我们已经可以成功访问到合同模块的接口了

在这里插入图片描述

四、通过 Spring Boot Actuator实现动态路由

  • 利用 GatewayControllerEndpoint 端点暴露路由的 CRUD 操作接口。

    • 引入pom文件

      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      
    • 在 yml配置文件中暴露所有端点

      management:endpoints:web:exposure: include: "*"
      
    • GatewayControllerEndpoint

    @RestControllerEndpoint(id = "gateway"
    )
    public class GatewayControllerEndpoint extends AbstractGatewayControllerEndpoint {public GatewayControllerEndpoint(List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {super((RouteDefinitionLocator)null, globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter, routeLocator);}// 获取全部路由信息@GetMapping({"/routes"})public Flux<Map<String, Object>> routes() {return this.routeLocator.getRoutes().map(this::serialize);}Map<String, Object> serialize(Route route) {HashMap<String, Object> r = new HashMap();r.put("route_id", route.getId());r.put("uri", route.getUri().toString());r.put("order", route.getOrder());r.put("predicate", route.getPredicate().toString());if (!CollectionUtils.isEmpty(route.getMetadata())) {r.put("metadata", route.getMetadata());}ArrayList<String> filters = new ArrayList();for(int i = 0; i < route.getFilters().size(); ++i) {GatewayFilter gatewayFilter = (GatewayFilter)route.getFilters().get(i);filters.add(gatewayFilter.toString());}r.put("filters", filters);return r;}// 获取单个路由信息@GetMapping({"/routes/{id}"})public Mono<ResponseEntity<Map<String, Object>>> route(@PathVariable String id) {return this.routeLocator.getRoutes().filter((route) -> {return route.getId().equals(id);}).singleOrEmpty().map(this::serialize).map(ResponseEntity::ok).switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));}
    }
    

    @RestControllerEndpoint注解作用:

    在Spring Cloud Gateway中,@RestControllerEndpoint 注解通常与Actuator端点一起使用,用于暴露管理或监控端点。然而@RestControllerEndpoint 本身并不是Spring Cloud Gateway特有的,而是Spring Boot Actuator提供的一个注解。

    @RestControllerEndpoint@Endpoint@RestController 的组合,它允许你定义一个RESTful的Actuator端点。与 @Endpoint(它通常用于WebFlux或MVC的响应式或非响应式端点)不同,@RestControllerEndpoint 始终创建一个RESTful端点。

    id 属性用于定义端点的唯一标识符,该标识符将用于URL路径(例如,/actuator/{id})。

  • 通过 HTTP 请求(如使用 Postman)向这些接口发送请求,实现路由的添加、删除、查询等操作。

    • 添加路由:actuator/gateway/routes/{id}

    • 删除路由:actuator/gateway/routes/{id}

    • 查询单条路由:actuator/gateway/routes/{id}

    • 查询所有路由:actuator/gateway/routes

    • 增删改接口主要在其父类AbstractGatewayControllerEndpoint上

    public class AbstractGatewayControllerEndpoint implements ApplicationEventPublisherAware {private static final Log log = LogFactory.getLog(GatewayControllerEndpoint.class);protected RouteDefinitionLocator routeDefinitionLocator;protected List<GlobalFilter> globalFilters;protected List<GatewayFilterFactory> GatewayFilters;protected List<RoutePredicateFactory> routePredicates;protected RouteDefinitionWriter routeDefinitionWriter;protected RouteLocator routeLocator;protected ApplicationEventPublisher publisher;public AbstractGatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {this.routeDefinitionLocator = routeDefinitionLocator;this.globalFilters = globalFilters;this.GatewayFilters = gatewayFilters;this.routePredicates = routePredicates;this.routeDefinitionWriter = routeDefinitionWriter;this.routeLocator = routeLocator;}public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {this.publisher = publisher;}// 刷新路由配置接口@PostMapping({"/refresh"})public Mono<Void> refresh() {// 发布RefreshRoutesEvent事件this.publisher.publishEvent(new RefreshRoutesEvent(this));return Mono.empty();}@GetMapping({"/globalfilters"})public Mono<HashMap<String, Object>> globalfilters() {return this.getNamesToOrders(this.globalFilters);}@GetMapping({"/routefilters"})public Mono<HashMap<String, Object>> routefilers() {return this.getNamesToOrders(this.GatewayFilters);}@GetMapping({"/routepredicates"})public Mono<HashMap<String, Object>> routepredicates() {return this.getNamesToOrders(this.routePredicates);}private <T> Mono<HashMap<String, Object>> getNamesToOrders(List<T> list) {return Flux.fromIterable(list).reduce(new HashMap(), this::putItem);}private HashMap<String, Object> putItem(HashMap<String, Object> map, Object o) {Integer order = null;if (o instanceof Ordered) {order = ((Ordered)o).getOrder();}map.put(o.toString(), order);return map;}// 新增接口@PostMapping({"/routes/{id}"})public Mono<ResponseEntity<Object>> save(@PathVariable String id, @RequestBody RouteDefinition route) {// 新增路由定义信息return Mono.just(route).filter(this::validateRouteDefinition).flatMap((routeDefinition) -> {return this.routeDefinitionWriter.save(Mono.just(routeDefinition).map((r) -> {r.setId(id);log.debug("Saving route: " + route);return r;})).then(Mono.defer(() -> {return Mono.just(ResponseEntity.created(URI.create("/routes/" + id)).build());}));}).switchIfEmpty(Mono.defer(() -> {return Mono.just(ResponseEntity.badRequest().build());}));}private boolean validateRouteDefinition(RouteDefinition routeDefinition) {boolean hasValidFilterDefinitions = routeDefinition.getFilters().stream().allMatch((filterDefinition) -> {return this.GatewayFilters.stream().anyMatch((gatewayFilterFactory) -> {return filterDefinition.getName().equals(gatewayFilterFactory.name());});});boolean hasValidPredicateDefinitions = routeDefinition.getPredicates().stream().allMatch((predicateDefinition) -> {return this.routePredicates.stream().anyMatch((routePredicate) -> {return predicateDefinition.getName().equals(routePredicate.name());});});log.debug("FilterDefinitions valid: " + hasValidFilterDefinitions);log.debug("PredicateDefinitions valid: " + hasValidPredicateDefinitions);return hasValidFilterDefinitions && hasValidPredicateDefinitions;}// 删除接口@DeleteMapping({"/routes/{id}"})public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {// 根据id删除路由定义信息return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {return Mono.just(ResponseEntity.ok().build());})).onErrorResume((t) -> {return t instanceof NotFoundException;}, (t) -> {return Mono.just(ResponseEntity.notFound().build());});}@GetMapping({"/routes/{id}/combinedfilters"})public Mono<HashMap<String, Object>> combinedfilters(@PathVariable String id) {return this.routeLocator.getRoutes().filter((route) -> {return route.getId().equals(id);}).reduce(new HashMap(), this::putItem);}
    }
    
  • 需要注意的是,这种方式没有可视化界面,维护起来可能比较繁琐,因为需要手动调用接口来更新路由信息;如果网关有多个,那么每个网关都要手动调用接口来更新路由信息,非常繁琐;并且这些路由信息是保存在内存中的,一旦重启,这些路由信息就会失效。

  • 当然,你也可以重写这些接口,对这些接口实现可视化管理界面,并将这些路由信息保存在数据库中,这样这些路由信息即使重启还会保存下来,不会丢失;对于多个网关都要重复调用接口,我觉得可以集成消息队列进来,这样只要发布更新路由的消息到消息队列中,再由消息队列广播到所有网关中,每个网关再根据消息进行处理即可。

没在yml文件配置暴露所有端点访问获取所有路由信息节点会报错

在这里插入图片描述

配置了就不会报错了

在这里插入图片描述

五、通过事件刷新机制自定义实现动态路由

在第三点介绍的Nacos基于yml文件的配置就已经可以实现动态路由了,但是我想要将路由配置和该文件隔离,自定义实现动态路由,这样不仅可以集中化配置管理路由信息,也意味着你可以进行更多的自定义扩展操作,这取决于你的动态路由实现逻辑,比如可以实现根据特定条件动态加载或卸载路由规则。

本文提供的例子,仅进行了路由信息的添加操作和动态刷新功能,可根据自己的需求,自定义实现其他扩展逻辑,代码如下:

GatewayRouteEventPublisherAware类

提供动态路由的基础方法,可通过获取bean操作该类的方法,该类提供新增路由、更新路由、删除路由,然后实现发布的功能。

package com.itl.isrm.gateway.context;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;@Slf4j
@Service
public class GatewayRouteEventPublisherAware implements ApplicationEventPublisherAware {/**注入RouteDefinitionWriter实现类InMemoryRouteDefinitionRepository,该类在上面已经介绍过:主要提供了对路由定义信息的增加、删除、查询方法,由一       * 个LinkedHashMap变量存储*/@Autowiredprivate RouteDefinitionWriter routeDefinitionWriter;// 注入事件发布器@Autowiredprivate ApplicationEventPublisher publisher;/*** 增加路由定义信息** @param definition 路由定义* @return*/public String add(RouteDefinition definition) {log.info("新增路由:" + definition);routeDefinitionWriter.save(Mono.just(definition)).subscribe();// 添加完成之后需要发布RefreshRoutesEvent事件,通知CachingRouteLocator类处理RefreshRoutesEvent事件获取最新的路由配置this.publisher.publishEvent(new RefreshRoutesEvent(this));return "success";}/*** 更新路由定义信息** @param definition 路由定义* @return*/public String update(RouteDefinition definition) {log.info("更新路由:" + definition);try {// 先根据id删除路由定义信息this.routeDefinitionWriter.delete(Mono.just(definition.getId()));} catch (Exception e) {return "update fail,not find route  routeId: " + definition.getId();}try {routeDefinitionWriter.save(Mono.just(definition)).subscribe();// 添加完成之后需要发布RefreshRoutesEvent事件,通知CachingRouteLocator类处理RefreshRoutesEvent事件获取最新的路由配置this.publisher.publishEvent(new RefreshRoutesEvent(this));return "success";} catch (Exception e) {return "update route  fail";}}/*** 删除路由定义信息** @param id 路由ID* @return*/public String delete(String id) {try {// 删除路由定义信息this.routeDefinitionWriter.delete(Mono.just(id));// 发布事件this.publisher.publishEvent(new RefreshRoutesEvent(this));return "delete success";} catch (Exception e) {log.error(e.getMessage(), e);return "delete fail";}}@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.publisher = applicationEventPublisher;}
}
NacosRouteRefreshListener类

主要作用是监听Nacos中的路由文件配置,当该配置文件的配置发生变化时会通知该类进行路由更新

package com.itl.isrm.gateway.listener;import com.alibaba.nacos.api.config.listener.Listener;
import com.itl.isrm.common.util.JsonUtils;
import com.itl.isrm.gateway.context.GatewayRouteEventPublisherAware;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.concurrent.Executor;/*** 动态实时刷新路由配置*/
@Slf4j
@Component
public class NacosRouteRefreshListener implements Listener {@Autowiredprivate GatewayRouteEventPublisherAware gatewayRouteEventPublisherAware;public NacosRouteRefreshListener() {System.out.println("--->>> Init NacosRouteRefreshListener.");}@Overridepublic Executor getExecutor() {return null;}/*** 获取最新的路由定义信息,然后由GatewayRouteEventPublisherAware对路由定义信息进行更新* @param configInfo*/@Overridepublic void receiveConfigInfo(String configInfo) {List<RouteDefinition> list = JsonUtils.toList(configInfo, RouteDefinition.class);list.forEach(definition -> {gatewayRouteEventPublisherAware.update(definition);});}
}
NacosGatewayConfig类

主要作用是配置隔离的路由配置文件地址

package com.itl.isrm.gateway.config;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;/*** 自定义属性绑定值,可通过配置文件配置属性*/
@Configuration
@ConfigurationProperties(prefix = "nacos", ignoreUnknownFields = true)
public class NacosGatewayConfig {private String address;private String dataId;private String groupId;private Long timeout;private String nameSpace;public String getNameSpace() {return nameSpace;}public void setNameSpace(String nameSpace) {this.nameSpace = nameSpace;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public String getDataId() {return dataId;}public void setDataId(String dataId) {this.dataId = dataId;}public String getGroupId() {return groupId;}public void setGroupId(String groupId) {this.groupId = groupId;}public Long getTimeout() {return timeout;}public void setTimeout(Long timeout) {this.timeout = timeout;}}

需在本地配置文件中配置

spring:application:name: isrm-gateway
nacos:address: ${NACOS_HOST:ip:8848}data-id: ${spring.application.name}group-id: isrmtimeout: 6000namespace: ${NAME_SPACE:dev}
NacosRouteListener类
package com.itl.isrm.gateway.listener;import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.itl.isrm.common.util.JsonUtils;
import com.itl.isrm.gateway.config.NacosGatewayConfig;
import com.itl.isrm.gateway.context.GatewayRouteEventPublisherAware;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;/*** 服务启动时初始化路由配置信息*/
@Slf4j
@Component
public class NacosRouteListener {// 注入路由文件配置变化监听器@Autowiredprivate NacosRouteRefreshListener nacosRouteRefreshListener;// 注入配置类@Autowiredprivate NacosGatewayConfig nacosGatewayConfig;// 注入路由定义信息接口操作类@Autowiredprivate GatewayRouteEventPublisherAware gatewayRouteEventPublisherAware;/*** 当Bean初始化时执行,初始化路由配置*/@PostConstructpublic void loadRouteByNacosListener() {try {log.info("---->>> init nacos router data.");Properties nacosPro = new Properties();nacosPro.put("serverAddr", nacosGatewayConfig.getAddress());nacosPro.put("namespace", nacosGatewayConfig.getNameSpace());//添加命名空间ConfigService configService = NacosFactory.createConfigService(nacosPro);// 获取Nacos中命名空间为dev的isrm-gateway配置文件的路由定义信息String configInfo = configService.getConfig(nacosGatewayConfig.getDataId(), nacosGatewayConfig.getGroupId(), nacosGatewayConfig.getTimeout());// 新增路由addRoute(configInfo);// 添加srm-gateway配置文件发生变化时的监听器,监听Nacos Server下发的动态路由配置configService.addListener(nacosGatewayConfig.getDataId(), nacosGatewayConfig.getGroupId(), nacosRouteRefreshListener);} catch (NacosException e) {log.error(e.getMessage(), e);}}/*** 添加路由* @param configInfo*/private void addRoute(String configInfo) {if (StringUtils.isBlank(configInfo)) {throw new NullPointerException("route info is null");}// 将字符串转成RouteDefinition对象列表List<RouteDefinition> list = JsonUtils.toList(configInfo, RouteDefinition.class);// 遍历添加路由list.forEach(definition -> {gatewayRouteEventPublisherAware.update(definition);});}}
isrm-gateway配置文件

在这里插入图片描述

比如现在配置如下:

[{"id": "auth","order": 0,"predicates": [{"args": {"pattern": "/api/auth/**"},"name": "Path"}],"filters": [{"args": {"parts": "2"},"name": "StripPrefix"}],"uri": "lb://isrm-auth-provider"},{"id": "system","order": 0,"predicates": [{"args": {"pattern": "/api/system/**"},"name": "Path"}],"filters": [{"args": {"parts": "2"},"name": "StripPrefix"}],"uri": "lb://isrm-system-provider"},{"id": "basic","order": 0,"predicates": [{"args": {"pattern": "/api/basic/**"},"name": "Path"}],"filters": [{"args": {"parts": "2"},"name": "StripPrefix"}],"uri": "lb://isrm-basic-provider"},{"id": "sup","order": 0,"predicates": [{"args": {"pattern": "/api/sup/**"},"name": "Path"}],"filters": [{"args": {"parts": "2"},"name": "StripPrefix"}],"uri": "lb://isrm-sup-provider"}
]
初始化流程

启动网关服务,NacosRouteListener初始化时获取路由定义信息

在这里插入图片描述

遍历路由定义信息列表,调用GatewayRouteEventPublisherAware新增路由定义信息到InMemoryRouteDefinitionRepository中,同时发布RefreshRoutesEvent事件

在这里插入图片描述

CachingRouteLocator监听RefreshRoutesEvent事件,调用CompositeRouteLocator的getRoutes方法,然后由RouteDefinitionRouteLocator再去调用CompositeRouteDefinitionLocator的getRouteDefinitions方法获取所有的定义信息,然后转换成真实的路由对象

在这里插入图片描述

因为CompositeRouteDefinitionLocator持有了InMemoryRouteDefinitionRepository的引用,所以它能获取我们自定义维护的路由定义信息

在这里插入图片描述

到此,我们初始化路由配置完成

监听流程

因为上面的配置文件中没有合同模块的路由配置,所以调用合同模块的查询接口会报下面的错误

在这里插入图片描述

当我们在原有的配置文件基础上,新增合同模块的路由配置,然后点击发布按钮

[{"id": "auth","order": 0,"predicates": [{"args": {"pattern": "/api/auth/**"},"name": "Path"}],"filters": [{"args": {"parts": "2"},"name": "StripPrefix"}],"uri": "lb://isrm-auth-provider"},{"id": "system","order": 0,"predicates": [{"args": {"pattern": "/api/system/**"},"name": "Path"}],"filters": [{"args": {"parts": "2"},"name": "StripPrefix"}],"uri": "lb://isrm-system-provider"},{"id": "basic","order": 0,"predicates": [{"args": {"pattern": "/api/basic/**"},"name": "Path"}],"filters": [{"args": {"parts": "2"},"name": "StripPrefix"}],"uri": "lb://isrm-basic-provider"},{"id": "sup","order": 0,"predicates": [{"args": {"pattern": "/api/sup/**"},"name": "Path"}],"filters": [{"args": {"parts": "2"},"name": "StripPrefix"}],"uri": "lb://isrm-sup-provider"},{"id": "contract","order": 0,"predicates": [{"args": {"pattern": "/api/contract/**"},"name": "Path"}],"filters": [{"args": {"parts": "2"},"name": "StripPrefix"}],"uri": "lb://isrm-contract-provider"}
]

此时NacosRouteRefreshListener就能监听到配置文件的配置变化,重新调用GatewayRouteEventPublisherAware类的方法,重新加载新的路由,流程和初始化流程差不多就不讲了

在这里插入图片描述

重新调用合同模块的查询接口,发现数据出来了,接口没有报错,到此动态路由功能实现了,无需重启网关服务

在这里插入图片描述

相关文章:

SprringCloud Gateway动态添加路由不重启

文章目录 前言&#xff1a;一、动态路由必要性二、SpringCloud Gateway路由加载过程RouteDefinitionLocator接口PropertiesRouteDefinitionLocator类DiscoveryClientRouteDefinitionLocatorInMemoryRouteDefinitionRepositoryCompositeRouteDefinitionLocator类CachingRouteDef…...

Windows安装mysql

首先去官网下载社区版本的mysql&#xff08;如果连不上&#xff0c;挂梯子&#xff09; https://www.mysql.com/downloads/ 2. 去配置环境变量path 3. 在cmd里面初始化数据库&#xff08;在搜索框输入cmd&#xff0c;或者在资源管理器下搜索烂输入cmd回车就行&#xff09; my…...

chatgpt: linux 下用纯c 编写ui

在Linux下用纯C语言编写用户界面&#xff08;UI&#xff09;&#xff0c;通常会使用GTK或Xlib。GTK是一个更高级的库&#xff0c;提供了丰富的控件和功能&#xff0c;而Xlib则是一个更底层的库&#xff0c;提供了直接操作X Window系统的功能。 下面是一个使用GTK在Linux上创建…...

Java十六进制Dump打印数据

代码 package test;import java.io.IOException;import sun.misc.HexDumpEncoder;@SuppressWarnings("restriction")...

某棋牌渗透测试

前言 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、信息收集 这里通过fofa进行收集&#xff0c;语法为&#xff1a;body某棋牌 && titlexxx 图1-1 fofa资产收集 …...

JAVA面试(六)

缓存 MemcachedredisRedis常见数据类型和使用Redis缓存持久化RDB-快照AOF-追加文件 Redis数据过期机制惰性删除定期删除Redis缓存淘汰策略&#xff08;8种&#xff09;算法LRU &#xff08;Least Recently Used&#xff09;&#xff1a;最近最少使用LFU&#xff08;Least Frequ…...

【C语言】手写学生管理系统丨附源码+教程

最近感觉大家好多在忙C语言课设~ 我来贡献一下&#xff0c;如果对你有帮助的话谢谢大家的点赞收藏喔&#xff01; 1. 项目分析 小白的神级项目&#xff0c;99%的程序员&#xff0c;都做过这个项目&#xff01; 掌握这个项目&#xff0c;就基本掌握 C 语言了&#xff01; 跳…...

流媒体传输协议HTTP-FLV、WebSocket-FLV、HTTP-TS 和 WebSocket-TS的详细介绍、应用场景及对比

一、前言 HTTP-FLV、WS-FLV、HTTP-TS 和 WS-TS 是针对 FLV 和 TS 格式视频流的不同传输方式。它们通过不同的协议实现视频流的传输&#xff0c;以满足不同的应用场景和需求。接下来我们对这些流媒体传输协议进行剖析。 二、传输协议 1、HTTP-FLV 介绍&#xff1a;基于 HTTP…...

【机器学习】线性回归:从基础到实践的深度解析

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 线性回归&#xff1a;从基础到实践的深度解析引言一、线性回归基础1.1 定义与目…...

短视频开源项目MoneyPrinterTurbo:AI副业搞起来,视频制作更轻松!

目录 引言一、MoneyPrinterTurbo简介二、MoneyPrinterTurbo的核心功能三、MoneyPrinterTurbo的未来发展四、MoneyPrinterTurbo与AI副业五、部署实践1、克隆代码2、创建虚拟环境3、安装依赖4、安装好 ImageMagick5、端口映射6、启动Web界面7、模型配置8、填写主题9、视频生成10、…...

【JAVA】SpringBoot + skywalking 将接口的入参、出参、异常等信息上报到skywalking 链路追踪服务器上

【JAVA】SpringBoot skywalking 将接口的入参、出参、异常等信息上报到skywalking 链路追踪服务器上 1.下载SkyWalking APM https://skywalking.apache.org/downloads/ jdk8 不支持 SkyWalking APM 9.3.0以上版本&#xff0c;所以这里我们下载 9.3.0版本 2.下载 Java Agent …...

[xmake]构建静态库和动态库

xmake 静态库和动态库 在xmake中创建静态库和动态库的方法非常相似。以下是创建静态库和动态库的基本步骤&#xff1a; 创建xmake工程文件&#xff08;xmake.lua&#xff09;。 配置工程属性&#xff0c;包括工程名、版本等。 添加源代码文件到工程中。 设置是创建静态库还…...

功能测试 之 单模块测试----轮播图、登录、注册

单功能怎么测&#xff1f; 需求分析 拆解测试点 编写用例 1.轮播图 &#xff08;1&#xff09;需求分析 位置&#xff1a;后台--页面--广告管理---广告列表(搜索index页面增加广告位2) 操作完成后需要点击admin---更新缓存,前台页面刷新生效 &#xff08;2&#xff09;拆解…...

MyBatis-PageHelper 源码解说

归档 GitHub: MyBatis-PageHelper-源码解说 总说明 源码仓库&#xff1a; https://github.com/pagehelper/Mybatis-PageHelper克隆&#xff1a;git clone https://github.com/pagehelper/Mybatis-PageHelper.git切分支&#xff08;tag&#xff09;&#xff1a;git checkout m…...

基于uni-app和图鸟UI的智慧校园圈子小程序开发实践

摘要&#xff1a; 随着教育信息化和“互联网教育”的快速发展&#xff0c;智慧校园建设已成为推动校园管理现代化、提高教育教学质量的重要手段。本文介绍了基于uni-app和图鸟UI开发的智慧校园圈子小程序&#xff0c;旨在通过一站式服务、个性化定制、数据互通和安全可靠等特点…...

STM32 keil工程移植到Visual Studio Code环境中编译

1、GCC Vscode 搭建 STM32 开发环境 GCC Vscode 搭建 STM32 开发环境&#xff08;一&#xff09;- 环境部署 - 知乎 (zhihu.com) 2、在原有keil工程下找到原本CUBEMX生成的.ioc工程文件 3、将.ioc文件复制一个新的文件夹下双击打开工程&#xff0c;将IDE选为Makefile&…...

细说CountDownLatch

CountDownLatch是Java中提供的一个同步辅助类&#xff0c;它允许一个或多个线程等待其他线程完成操作。在面试中&#xff0c;面试官经常会询问候选人是否在实际项目中使用过CountDownLatch&#xff0c;以评估其对多线程编程和并发控制的理解和经验。本文将详细介绍CountDownLat…...

java-克隆应用

5.2 创建复杂对象 对于某些复杂对象&#xff0c;通过克隆来创建其副本比通过构造函数创建新实例更加高效。例如&#xff0c;当对象包含大量字段或需要进行复杂初始化时&#xff0c;克隆可以显著提高性能。 java 复制代码 class ComplexObject implements Cloneable { private …...

RPC协议

3.8 既然有 HTTP 协议&#xff0c;为什么还要有 RPC 假设我们需要在 A 电脑的进程发一段数据到 B 电脑的进程&#xff0c;我们一般会在代码里使用 Socket 进行编程。 这时候&#xff0c;我们可选项一般也就 TCP 和 UDP 二选一。TCP 可靠&#xff0c;UDP 不可靠。 类似下面这…...

医疗器械3D全景展会在线漫游创造数字化时代的展览新篇章

在数字化浪潮的引领下&#xff0c;VR虚拟网上展会正逐渐成为企业展示品牌实力、吸引潜在客户的首选平台。我们与广交会携手走过三年多的时光&#xff0c;凭借优质的服务和丰富的经验&#xff0c;赢得了客户的广泛赞誉。 面对传统展会活动繁多、企业运营繁忙的挑战&#xff0c;许…...

IP_Endpoint类型在CAPL中的使用

在使用TCP/IP协议栈通信时,创建Socket套接字调用接口函数实现通信的整个过程成为一种主流且便捷的方式。在CAPL中,Client需要创建TCP或UDP套接字,绑定自己的IP地址和一个端口号,作为自己的通信端点。 on key c {clientsocket = tcpOpen(ipGetAddressAsNumber("192.16…...

数据资产与用户体验优化:深入挖掘用户数据,精准分析用户需求与行为,优化产品与服务,提升用户体验与满意度,打造卓越的用户体验,赢得市场认可

一、引言 在数字化时代&#xff0c;数据已经成为企业最宝贵的资产之一。通过深入挖掘和分析用户数据&#xff0c;企业能够精准把握用户需求和行为&#xff0c;从而优化产品与服务&#xff0c;提升用户体验和满意度。这不仅有助于企业在激烈的市场竞争中脱颖而出&#xff0c;还…...

基于TCAD与紧凑模型结合方法探究陷阱对AlGaN/GaN HEMTs功率附加效率及线性度的影响

来源&#xff1a;Investigation of Traps Impact on PAE and Linearity of AlGaN/GaN HEMTs Relying on a Combined TCAD–Compact Model Approach&#xff08;TED 24年&#xff09; 摘要 本文提出了一种新型建模方法&#xff0c;用于分析GaN HEMTs的微波功率性能。通过结合工…...

具身智能概念

具身智能作为人工智能发展的一个重要分支&#xff0c;伴随着大模型技术的爆发与硬件成本的降低&#xff0c;即软硬件技术走向成熟&#xff0c;正在成为广泛关注的热门&#xff0c;一时之间&#xff0c;具身智能机器人也成为了科技界新的风向标。 什么是具身智能&#xff1f; …...

C++ 43 之 自增运算符的重载

#include <iostream> #include <string> using namespace std;class MyInt{friend ostream& operator<< (ostream& cout , MyInt& int1); public:MyInt(){this->m_num 0;}// 前置自增&#xff1a; 成员函数实现运算符的重载 返回的是 引用&a…...

计算机网络:1概述、2物理层

目录 概述因特网网络、互连网&#xff08;互联网&#xff09;与因特网的区别与关系因特网发展的三个阶段因特网服务提供者&#xff08;Internet Service Provider&#xff0c;ISP&#xff09;因特网的标准化工作因特网的管理结构 三种交换电路交换分组交换报文交换 计算机网络性…...

【Ardiuno】实验使用ESP32接收电脑发送的串口数据(图文)

使用ESP32可以非常方便的与电脑进行串口通讯&#xff0c;一般我们可以用串口接收ESP32的输出作为调试使用&#xff0c;今天我们再来实验一下从电脑端向ESP32单片机发送数据。 发送数据程序代码&#xff1a; void setup() {Serial.begin(9600); }void loop() { if(Serial.ava…...

思科ospf+rip重发布配置命令

——————————————————————————————————————————— 基础配置 R1 Router>en #进入配置模式 Router#conf #进入配置模式 Router(config)#h…...

椭圆的矩阵表示法

椭圆的矩阵表示法 flyfish 1. 标准几何表示法 标准几何表示法是通过椭圆的几何定义来表示的&#xff1a; x 2 a 2 y 2 b 2 1 \frac{x^2}{a^2} \frac{y^2}{b^2} 1 a2x2​b2y2​1其中&#xff0c; a a a 是椭圆的长半轴长度&#xff0c; b b b 是椭圆的短半轴长度。 2.…...

智慧乡村和美人家信息化系统

一、简介 智慧乡村和美人家信息化系统是一个综合管理平台&#xff0c;集成了首页概览、一张图可视化、数据填报、智能评估、便捷申报、公开公示、任务管理、活动发布和灵活配置等功能。该系统不仅提升了乡村管理效率&#xff0c;也优化了家庭生活的便捷性。通过一张图&#xf…...

如何上传自己做的网站/国内网站排名

0.前言 本文介绍如何在树莓派中通过编译源代码的方式安装opencv&#xff0c;并通过一个简单的例子说明如何使用opencv。更多内容请参考——【树莓派学习笔记——索引博文】1.下载若干依赖项 在开始安装之前&#xff0c;最好更新树莓派软件源。如果更新时间太长&#xff0c;请参…...

做外贸出口的网站/百度网站排名查询工具

%86时出现报错 Error in invoking target agent nmhs of makefile 解决方案在makefile中添加链接libnnz11库的参数修改$ORACLE_HOME/sysman/lib/ins_emagent.mk&#xff0c;将$(MK_EMAGENT_NMECTL)修改为&#xff1a;$(MK_EMAGENT_NMECTL) -lnnz11建议修改前备份原始文件[ora…...

门户网站开发介绍/今日国际新闻头条15条简短

我们知道Citrix的ICA Client是一个exe的文件&#xff0c;它可以直接双击安装&#xff0c;但是在有些情况下可能会使用到静默安装或者解压模块安装。 这2种方式的好处是都可以选择单独模块组安装&#xff0c;这里我用13.0的Client&#xff0c;CitrixReceiverEnterprise.exe来做事…...

中国建设银行积分兑换网站/网站关键词如何优化

centos7安装mysql5.7.40 1.先去下载安装包 下载地址 Tip:使用迅雷下载会快一点 2.创建一个mysql用户 groupadd mysql useradd -g mysql -s /sbin/nologin mysqln3.安装mysql依赖包 yum install -y ncurses-devel libaio-devel gcc gcc-c numactl libaio glibc cmake auto…...

wordpress抓取新闻/免费建站网站一级

随着6月结束&#xff0c;7月开始&#xff0c;最近上海最火的话题是垃圾分类无疑了。。上海人民是每天早晚两小时定时定点扔垃圾。 Ps&#xff1a;小编好几个上海朋友都已经请假回老家了2333 干湿垃圾要分开&#xff0c;把湿垃圾从垃圾袋里倒进桶里&#xff0c;再把垃圾袋扔进干…...

网站手机app开发/关键词文案生成器

真机下device and simulators 模拟器直接复制打印的路径&#xff0c;mac上前往文件夹就行...