SprringCloud Gateway动态添加路由不重启

news2024/11/17 11:56:37

文章目录

      • 前言:
      • 一、动态路由必要性
      • 二、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;
	}

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

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

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

	@Override
	public 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实现动态路由以及事件刷新机制实现动态路由都是基于该类的方法      * 实现的
     */
	@Override
	public 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();
	}

    // 获取所有缓存的路由信息
	@Override
	public 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方法,获取最新的路由定义信息
	@Override
	public 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中,最后返回所有路由对象
     */
	@Override
	public Flux<Route> getRoutes() {
		Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions()
				.map(this::convertToRoute);

		if (!gatewayProperties.isFailOnRouteDefinitionError()) {
			// instead of letting error bubble up, continue
			routes = 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方法获取路由对象信息
	@Override
	public 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);
	}

    // 获取路由信息
	@Override
	public 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方法生成新的路由信息,同时放入缓存中
	@Override
	public void onApplicationEvent(RefreshRoutesEvent event) {
		fetch().materialize().collect(Collectors.toList())
				.doOnNext(routes -> cache.put(CACHE_KEY, routes)).subscribe();
	}

	@Deprecated
	/* for testing */ void handleRefresh() {
		refresh();
	}

	@Override
	public 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;
	}

	@Override
	public 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: sup
          predicates: 
            - Path=/api/sup/**
          filters:
            - StripPrefix=2
          uri: lb://isrm-sup-provider
        - id: auth
          predicates: 
            - Path=/api/auth/**
          filters:
            - StripPrefix=2
          uri: lb://isrm-auth-provider
        - id: basic
          predicates: 
            - Path=/api/basic/**
          filters:
            - StripPrefix=2
          uri: lb://isrm-basic-provider
        - id: system
          predicates: 
            - Path=/api/system/**
          filters:
            - StripPrefix=2
          uri: 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变量存储
      */
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    // 注入事件发布器
    @Autowired
    private 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";
        }
    }


    @Override
    public 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 {

    @Autowired
    private GatewayRouteEventPublisherAware gatewayRouteEventPublisherAware;

    public NacosRouteRefreshListener() {
        System.out.println("--->>> Init NacosRouteRefreshListener.");
    }

    @Override
    public Executor getExecutor() {
        return null;
    }

   
    /**
     * 获取最新的路由定义信息,然后由GatewayRouteEventPublisherAware对路由定义信息进行更新
     * @param configInfo
     */
    @Override
    public 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: isrm
  timeout: 6000
  namespace: ${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 {


    // 注入路由文件配置变化监听器
    @Autowired
    private NacosRouteRefreshListener nacosRouteRefreshListener;

    // 注入配置类
    @Autowired
    private NacosGatewayConfig nacosGatewayConfig;

    // 注入路由定义信息接口操作类
    @Autowired
    private GatewayRouteEventPublisherAware gatewayRouteEventPublisherAware;

    /**
     * 当Bean初始化时执行,初始化路由配置
     */
    @PostConstruct
    public 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类的方法,重新加载新的路由,流程和初始化流程差不多就不讲了

在这里插入图片描述

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1817949.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

maven学习小结

背景 大佬指路我负责实践 目录结构 maven为项目提供一个标准目录结构 环境配置 下载maven包后解压&#xff0c;配置解压目录的bin到path变量&#xff0c;然后终端mvn -v&#xff0c;有回显则表明maven安装成功 pom POM&#xff0c;Project Object Model&#xff0c;项目对…

计算机基本架构-时序逻辑电路回顾

计算机基本架构-时序逻辑电路回顾 D锁存器(D-Latch)D触发器(D-Flip-Flop)时序违规寄存器移位寄存器计数器摩尔状态机(Moore machine)米利状态机内存 计算机基本架构-时序逻辑电路回顾 D锁存器(D-Latch) D锁存器(D-Latch)是逻辑设计中最基本的存储元件。它具有数据输入D、时…

el-table有横向滚动条时,最后一行数据被横向滚动条遮挡,且不出现纵向滚动条;只有当鼠标移到fixed列才能纵向滚动,移到非fixed列无法纵向滚动。

问题背景 项目使用的vue2&#xff0c;el-table有横向滚动条时&#xff0c;最后一行数据被横向滚动条遮挡&#xff0c;且不出现纵向滚动条&#xff1b;只有当鼠标移到fixed列才能纵向滚动&#xff0c;移到非fixed列无法纵向滚动。 见下图&#xff1a;最后一行被遮挡住了一部分…

数字的魅力:数学中最重要的7个常数

数学常数是数学中一类特殊的数&#xff0c;具有固定不变的值。这些常数并非数学家随意凭空制定&#xff0c;而是源于深刻的数学原理和规律。它们不仅深刻地影响着数学理论的建立与发展&#xff0c;更连接着人类思维的奇妙之旅。 本文将简介数学中 7 个最基本的常数&#xff0c…

AI和机器学习论文中 指标F1是什么意思

在AI和机器学习领域的实验中&#xff0c;F1值&#xff08;F1 Score&#xff09;是一种用于评估分类模型性能的指标。它是精确率&#xff08;Precision&#xff09;和召回率&#xff08;Recall&#xff09;的调和平均数&#xff0c;特别适用于不平衡数据集。F1值综合了精确率和召…

JVM原理之运行时数据区域

Java运行时数据区(Runtime Data Area)是Java虚拟机(JVM)在运行Java程序时内部维护的一系列数据区域。这些区域共同协作,确保Java程序能够高效、稳定地运行。本文将详细介绍Java运行时数据区的结构和作用。 java虚拟机运行时数据区域 根据《Java虚拟机规范》规定,jvm内存…

华为云EI生态

1、人工智能技术趋势 2、华为AI发展思路 3、华为云EI&#xff1a;让企业更智能 4、华为云服务全景图 5、基础平台类服务 6、MLS:解决特性到模型应用的完整过程 7.DLS 8.GES超大规模一体化图分析与查询 9、EI视觉认知 10、EI语音语义 11、OCR&#xff1a;提供高精度光学文字自动…

工业操作系统是企业把舵的“仪表盘”

supOS向下连接海量工业设备、仪器、仪表、产品&#xff0c;为各类设备提供统一的接口&#xff0c;实现不同设备之间的互联互通&#xff1b;向上连接各类工业应用软件&#xff0c;将企业内部的生产数据、运营数据、管理数据汇集起来&#xff0c;是链接海量工业设备和各类应用软件…

第17章通信系统架构设计理论与实践

常见的5种常用的网络架构和构建网络的相关技术&#xff0c;以及网络构建的分析和设计方法。 17.1通信系统概述 通信技术和网络技术的发展&#xff0c;通信网络发生很大变化&#xff0c;入网的形式变化&#xff0c;传输的速率的提高、接入网络的方式多样化、网络结构的更为复杂…

将 x 减到 0 的最小操作数

题⽬要求的是数组「左端右端」两段连续的、和为 x 的最短数组&#xff1b;我们可以转化成求数组内⼀段连续的、和为 sum(nums) - x 的最⻓数组。 a. 转化问题&#xff1a;求 target sum(nums) - x 。如果 target < 0 &#xff0c;问题⽆解&#xff1b; b. 初始化左右指针 …

LogicFlow 学习笔记——3. LogicFlow 基础 节点 Node

节点 Node LogicFlow 内置了一些基础节点&#xff0c;开发者在实际应用场景中&#xff0c;可以基于这些基础节点&#xff0c;定义符合自己业务逻辑的节点。 认识基础节点 LogicFlow是基于svg做的流程图编辑框架&#xff0c;所以我们的节点和连线都是svg基本形状&#xff0c;…

MySQL查询优化最佳实践15条(建议收藏)

目录 1 优化方法&#xff08;15条&#xff09; 2 总结 MySQL的数据库常规查询的过程中性能的优化非常重要&#xff0c;其中很多点是和开发习惯有关&#xff0c;能熟练掌握不只能提高工作的效率&#xff0c;同时也能提高个人的技能。有一些优化的技巧同样也适合于其他的数据库…

git的ssh安装,windows通过rsa生成密钥认证问题解决

1 windows下载 官网下载可能出现下载太慢的情况&#xff0c;Git官网下载地址为&#xff1a;官网&#xff0c;推荐官网下载&#xff0c;如无法下载&#xff0c;可移步至CSDN&#xff0c;csdn下载地址&#xff1a;https://download.csdn.net/download/m0_46309087/12428308 2 Gi…

Tabby:一款革新的Mac/Win现代化终端模拟器

在信息技术日新月异的今天&#xff0c;终端操作已成为众多开发者、系统管理员和技术爱好者的日常必备工具。然而&#xff0c;传统的终端模拟器往往功能单一、界面陈旧&#xff0c;无法满足用户对于高效、便捷操作体验的追求。Tabby应运而生&#xff0c;作为一款现代化、功能强大…

6大好用的变音软件推荐,最好用的变声器免费版有哪些?

您在录制视频时&#xff0c;是否曾对自己的声音感到厌烦&#xff1f;有没有想过换一种声音让别人认不出您&#xff1f;变声软件允许你通过先进的AI算法改变声音。它可以增加所需的失真度、调整音高并改变语音的音调&#xff0c;从而将你的声音变为名人、机器人或卡通人物的声音…

C# WPF入门学习主线篇(二十一)—— 静态资源和动态资源

C# WPF入门学习主线篇&#xff08;二十一&#xff09;—— 静态资源和动态资源 欢迎来到C# WPF入门学习系列的第二十一篇。在上一章中&#xff0c;我们介绍了WPF中的资源和样式。本篇文章将深入探讨静态资源&#xff08;StaticResource&#xff09;和动态资源&#xff08;Dynam…

立创·天空星开发板-GD32F407VE-Timer

本文以 立创天空星开发板-GD32F407VET6-青春版 作为学习的板子&#xff0c;记录学习笔记。 立创天空星开发板-GD32F407VE-Timer 定时器基本定时器示例 定时器 定时器是嵌入式系统中常用的一种外设&#xff0c;它可以产生一定的时间间隔、延时、定时等功能&#xff0c;广泛应用于…

深度学习500问——Chapter11:迁移学习(2)

文章目录 11.2 迁移学习的基本思路有哪些 11.2.1 基于样本迁移 11.2.2 基于特征迁移 11.2.3 基于模型迁移 11.2.4 基于关系迁移 11.2 迁移学习的基本思路有哪些 迁移学习的基本方法可以分为四种。这四种基本方法分别是&#xff1a;基于样本的迁移&#xff0c;基于模型的迁移&a…

Three.js动效(第15辑):让前端手撕UI,拳打后端的效果。

three.js的设计效果非常复杂&#xff0c;后端提供的数据接口问题百出&#xff0c;这很容易让前端手撕UI、拳打后端&#xff0c;这种请详细该如何办呢&#xff1f; 前端 VS UI&#xff1a; 1. 沟通协调&#xff1a;UI和前端应该加强沟通&#xff0c;理解对方的工作难点和需求&…

SpringSecurity6从入门到实战之SpringSecurity6自定义认证规则

SpringSecurity6从入门到实战之SpringSecurity6自定义认证规则 Spring Security 中默认所有的 http 请求都需要先认证通过后&#xff0c;才能访问。那么&#xff0c; 如何指定不需要认证就可以直接访问的资源呢&#xff1f;比如 用户的登录页面和注册页面&#xff0c;都是不需要…