三分钟了解Spring Cloud Gateway路由转发之自动路由

news2024/11/20 13:18:31

文章目录

    • 一、前言
    • 二、路由配置
      • 1. 静态路由
      • 2. 动态路由
      • 3. 自动路由
    • 三、Spring Cloud Gateway 是如何实现动态路由
      • 工作原理
      • 源码解析
        • 路由转发原理
        • 路由转发源码解析
    • 四 、问题核心
      • DiscoveryClientRouteDefinitionLocator源码解析
    • 五、总结

大家好,我是冰点,今天和大家分享一下关于Spring Cloud Gateway 利用服务注册与发现实现自动路由的原理和源码解读。希望对大家有所帮助。

一、前言

今天有个新同学,问我为什么我们的网关服务Spring Cloud Gateway,没有配置路由就可以将请求到路由服务,说他们之前的项目的网关是将路由配置在配置文件中。指定路由类似以下写法。而在现在的项目的配置文件中未发现任何路由配置。觉得很奇怪,Spring-Cloud-Gateway 是如何将请求路由到指定的服务的呢。我让他比对一下配置文件有什么不同,他说就是只有一个spring.cloud.gateway.discovery.locator.enabled=true

如下配置一般是大多数项目配置路由的我们一般称之为静态路由,是由配置文件硬编码后在程序启动的时候加载的。

spring:
  cloud:
    gateway:
      discovery:
        locator:
          lower-case-service-id: true # 忽略服务名的大小写
      routes:
        - id: service1
          uri: lb://service1
          predicates:
            - Path=/service1/**
        - id: service2
          uri: lb://service2
          predicates:
            - Path=/service2/**

除了上述的路由配置外,其实我们通俗的将gateway 的路由可以分为三种

  1. 静态路由
  2. 动态路由
  3. 自动路由

下面我们详细了解一下这三种路由
Spring Cloud Gateway 支持三种类型的路由:静态路由、动态路由和自动路由。

二、路由配置

1. 静态路由

静态路由是指在配置文件中预先定义好的路由规则,它们在应用启动时就已经存在。静态路由的优点是可以快速定位和处理请求,缺点是需要手动配置,不支持动态添加、修改和删除路由规则。

在 Spring Cloud Gateway 中,可以通过配置文件来定义静态路由规则。例如:

spring:
  cloud:
    gateway:
      routes:
        - id: service1
          uri: http://localhost:8081
          predicates:
            - Path=/service1/**
        - id: service2
          uri: http://localhost:8082
          predicates:
            - Path=/service2/**

这段配置文件定义了两个静态路由规则,分别对应于服务 service1 和服务 service2。当请求的路径匹配 /service1/** 时,它就会被转发到 http://localhost:8081;当请求的路径匹配 /service2/** 时,它就会被转发到 http://localhost:8082。

2. 动态路由

动态路由是指在运行时动态添加、修改和删除路由规则,可以根据不同的条件动态地调整路由规则,例如根据请求路径、请求头、请求参数等条件。动态路由的优点是可以根据实际情况调整路由规则,缺点是需要额外的管理和维护成本。
在 Spring Cloud Gateway 中,可以通过 API 来动态添加、修改和删除路由规则。例如:

@Autowired
private RouteDefinitionWriter routeDefinitionWriter;

public void addRoute(String id, String uri, String predicates) {
    RouteDefinition routeDefinition = new RouteDefinition();
    routeDefinition.setId(id);
    routeDefinition.setUri(URI.create(uri));
    routeDefinition.setPredicates(Collections.singletonList(new PredicateDefinition(predicates)));
    routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
}

public void updateRoute(String id, String uri, String predicates) {
    RouteDefinition routeDefinition = new RouteDefinition();
    routeDefinition.setId(id);
    routeDefinition.setUri(URI.create(uri));
    routeDefinition.setPredicates(Collections.singletonList(new PredicateDefinition(predicates)));
    routeDefinitionWriter.delete(Mono.just(routeDefinition.getId())).then(Mono.defer(() -> {
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        return Mono.empty();
    })).subscribe();

}

public void deleteRoute(String id) {
    routeDefinitionWriter.delete(Mono.just(id)).subscribe();
}

通过注入 RouteDefinitionWriter 对象来操作路由规则。 addRoute 方法可以添加一条路由规则, updateRoute 方法可以修改一条路由规则, deleteRoute 方法可以删除一条路由规则。这些操作会实时生效,不需要重启应用。需要在 Spring Boot 应用启动时加载 RouteDefinitionLocator 对象,以便正确加载动态路由规则。

3. 自动路由

自动路由是指根据服务注册中心的服务信息自动生成路由规则。当有新的服务上线或下线时,路由规则也会自动更新。自动路由的优点是可以根据实际情况自动调整路由规则,缺点是需要服务注册中心的支持。其实服务发现可以支持很多种,主要实现spring cloud 提供的接口即可。下次我专门写一篇介绍

服务发现功能的实现可以通过 Spring Cloud Commons 中的 DiscoveryClient 类实现。Spring Cloud
Discovery 可以与多种服务发现组件集成,包括 EurekaConsulZookeeper 等。Spring Cloud
Gateway 会自动与 Spring Cloud Discovery 集成,可以使用 Spring Cloud Discovery
来获取服务实例列表,并将这些服务实例转换为路由规则。大家感兴趣可以先了解一下这两个接口。
在这里插入图片描述

在 Spring Cloud Gateway 中,可以通过配置服务注册中心来启用自动路由功能。例如:

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true

启用了服务发现功能,并将服务 ID 转换为小写

三、Spring Cloud Gateway 是如何实现动态路由

客户端发送请求到Spring Cloud Gateway,Gateway Handler Mapping确定请求与路由匹配,则会将请求交给Gateway Web Handler处理。

工作原理

图片来源spring官网 https://cloud.spring.io/spring-cloud-gateway/reference/html/
这是官网的图-How It Works

源码解析

Spring Cloud Gateway 是一款基于 Spring Framework 和 Spring Boot 的网关框架,它提供了统一的路由转发、负载均衡、请求过滤和请求转换等功能。在 Spring Cloud Gateway 中,路由转发是其中最核心的功能之一。

下面是 Spring Cloud Gateway 路由转发的原理和源码解析。

路由转发原理

  1. Spring Cloud Gateway 的路由转发基于 Netty 和 Reactor 实现。当一个请求到达 Spring Cloud
    Gateway 时,它会首先经过一系列过滤器的处理,然后根据路由规则将请求转发到正确的目标地址。

  2. 路由规则由路由配置组件管理,它可以通过多种方式来创建,例如基于配置文件的路由配置、基于 Java代码的路由配置、基于服务发现的路由配置等。每个路由规则包含一个路由条件和一个目标 URI,当一个请求满足路由条件时,它就会被转发到目标
    URI。

  3. 路由条件由路由规则的路由条件工厂类创建,例如
    PathRoutePredicateFactory、HeaderRoutePredicateFactory、MethodRoutePredicateFactory等。它们可以根据请求的路径、请求头、请求方法等条件来判断一个请求是否满足路由条件。

  4. 目标 URI 可以通过多种方式指定,例如硬编码的 URI、基于服务发现的 URI、基于请求头的 URI 等。在确定了目标 URI 后,Spring Cloud Gateway 会将请求转发到目标 URI,并将响应返回给客户端。

路由转发源码解析

在 Spring Cloud Gateway 中,路由转发的核心代码位于 org.springframework.cloud.gateway.handler 包中。其中,RoutePredicateHandlerMapping 类是 Spring Cloud Gateway 的路由转发入口,它继承了 AbstractHandlerMapping 类,并实现了其中的 getHandlerInternal 方法。

RoutePredicateHandlerMapping 的源码解析 为了方便理解,添加了中文注释

public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {

    private final Map<String, RoutePredicateFactory> predicates; // 路由条件工厂类映射表
    private final GatewayFilterHandlerFilter filterHandlerFilter; // 过滤器处理器
    private final Map<String, Object> globalFilters; // 全局过滤器映射表
    private final RouteDefinitionLocator routeDefinitionLocator; // 路由规则定位器

    public RoutePredicateHandlerMapping(List<RoutePredicateFactory> predicates,
                                        GatewayFilterHandlerFilter filterHandlerFilter,
                                        List<GlobalFilter> globalFilters,
                                        RouteDefinitionLocator routeDefinitionLocator) {
        this.predicates = predicates.stream()
                .collect(Collectors.toMap(RoutePredicateFactory::name, Function.identity())); // 将路由条件工厂类列表转换为路由条件工厂类映射表
        this.filterHandlerFilter = filterHandlerFilter;
        this.globalFilters = globalFilters.stream()
                .collect(Collectors.toMap(GlobalFilter::name, Function.identity())); // 将全局过滤器列表转换为全局过滤器映射表
        this.routeDefinitionLocator = routeDefinitionLocator;
        setOrder(-1); // 设置路由转发的优先级
    }

    @Override
    protected Object getHandlerInternal(ServerHttpRequest request) throws Exception {
        List<RouteDefinition> definitions = this.routeDefinitionLocator.getRouteDefinitions().collectList().block(); // 获取所有路由规则
        if (definitions == null) { // 如果路由规则列表为空,则返回 null
            return null;
        }
        for (RouteDefinition routeDefinition : definitions) { // 遍历所有路由规则
            RoutePredicateFactory predicate = this.predicates.get(routeDefinition.getPredicate().getName());            RoutePredicate routePredicate = predicate.apply(routeDefinition.getPredicate().getArgs()); // 创建路由条件
            if (routePredicate.test(request)) { // 判断请求是否满足路由条件
                Route route = new Route(routeDefinition.getId(), routeDefinition.getUri(), routeDefinition.getFilters()); // 创建路由对象
                List<GatewayFilter> gatewayFilters = new ArrayList<>(routeDefinition.getFilters()); // 获取路由规则中的过滤器
                gatewayFilters.addAll(getGlobalFilters()); // 添加全局过滤器
                FilteringWebHandler filteringWebHandler = new FilteringWebHandler(new DefaultWebHandler(), new GatewayFilterChain(gatewayFilters)); // 创建过滤器链
                return new DefaultWebHandlerAdapter().handle(request, filteringWebHandler); // 返回路由转发处理器
            }
        }
        return null;
    }

    private Collection<Object> getGlobalFilters() {
        return this.globalFilters.values(); // 返回全局过滤器集合
    }
}

在 RoutePredicateHandlerMapping 中,首先通过构造方法初始化了路由条件工厂类映射表、过滤器处理器、全局过滤器映射表和路由规则定位器。然后,实现了 AbstractHandlerMapping 中的 getHandlerInternal 方法。在 getHandlerInternal 方法中,首先获取所有路由规则,并遍历每个路由规则。对于每个路由规则,将其路由条件工厂类名称作为 key,从路由条件工厂类映射表中获取对应的路由条件工厂类,并使用路由条件工厂类创建路由条件。然后,判断当前请求是否满足路由条件,如果满足,则创建路由对象,并获取路由规则中的过滤器和全局过滤器。将这些过滤器组成过滤器链,并将过滤器链和默认的 Web 处理器一起作为参数创建过滤器 Web 处理器。最后,使用过滤器 Web 处理器和当前请求创建 DefaultWebHandlerAdapter 的实例,并返回路由转发处理器。

写到这儿其实我们只是了解了一个请求在路由到后台服务之前必须要要经过的几道工序,就如同我最开始从Spring 官网获得的工作原理图。

四 、问题核心

我们来回答最开始的那个问题。那么如果在不配置路由规则的Spring Cloud Gateway 服务中,网关是如何做的转发呢,这才是我们核心问题。

那就不得不说一个重要的核心的接口和实现类 位于spring-cloud-gateway-core-2.x.RELEASE 下。
RouteDefinitionLocator。其实里面就一个核心方法 getRouteDefinitions

RouteDefinitionLocator接口

DiscoveryClientRouteDefinitionLocator源码解析

DiscoveryClientRouteDefinitionLocator 是RouteDefinitionLocator 实现类。是 Spring Cloud Gateway 提供的一个基于服务发现的路由规则定位器,它可以自动将服务实例列表转换为路由规则,从而实现基于服务发现的路由配置。

为了方便大家理解,我在源码上添加了一些注释

public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
    // 服务发现客户端
    private final DiscoveryClient discoveryClient;
    // 路由规则转换器
    private final RouteDefinitionLocator routeDefinitionLocator;
    // 服务过滤器
    private final Predicate<ServiceInstance> predicate;
	// 默认使用所有服务实例
    public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, RouteDefinitionLocator routeDefinitionLocator) {
        this(discoveryClient, routeDefinitionLocator, instance -> true); // 默认使用所有服务实例
    }
	// 第二个构造方法可以指定服务过滤器
    public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, RouteDefinitionLocator routeDefinitionLocator, Predicate<ServiceInstance> predicate) {
        this.discoveryClient = discoveryClient;
        this.routeDefinitionLocator = routeDefinitionLocator;
        this.predicate = predicate;
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(getServiceNames()) // 获取所有服务名称
                .flatMap(this::getRoutes) // 遍历每个服务名称,获取该服务的路由规则
                .flatMap(routeDefinitionLocator::getRouteDefinitions) // 转换路由规则
                .doOnNext(route -> logger.debug("RouteDefinition matched: " + route)); // 打印日志
    }

    private List<String> getServiceNames() {
        return discoveryClient.getServices(); // 获取服务名称列表
    }

    private Mono<RouteDefinition> getRoutes(String serviceName) {
        return Mono.just(new RouteDefinition()) // 创建一个新的 RouteDefinition 对象
                .flatMap(routeDefinition -> Flux.fromIterable(getInstances(serviceName))) // 获取该服务的所有实例
                .filter(predicate) // 过滤服务实例
                .map(this::getInstanceRoute) // 将服务实例转换为路由规则
                .doOnNext(route -> logger.debug("RouteDefinition created: " + route)) // 打印日志
                .reduce(new RouteDefinition(), this::mergeRouteDefinitions); // 合并所有路由规则
    }

    private List<ServiceInstance> getInstances(String serviceName) {
        return discoveryClient.getInstances(serviceName); // 获取指定服务的所有实例
    }

    private RouteDefinition getInstanceRoute(ServiceInstance instance) {
        RouteDefinition route = new RouteDefinition(); // 创建一个新的 RouteDefinition 对象
        route.setId(instance.getServiceId()); // 设置路由规则的 ID 为服务名称
        URI uri = instance.getUri(); // 获取服务实例的 URI
        if (uri != null) {
            route.setUri(uri); // 设置路由规则的 URI
        }
        return route;
    }

    private RouteDefinition mergeRouteDefinitions(RouteDefinition route1, RouteDefinition route2) {
        route1.getFilters().addAll(route2.getFilters()); // 合并过滤器
        route1.getPredicates().addAll(route2.getPredicates()); // 合并谓词
        return route1;
    }
}
  1. DiscoveryClientRouteDefinitionLocator 类的主要作用是从服务注册中心获取服务信息并将其转换为路由规则。它实现了 RouteDefinitionLocator 接口,用于获取路由规则列表。具体来说,它通过 DiscoveryClient 类获取所有服务名称,遍历每个服务名称,再通过 DiscoveryClient 类获取该服务的所有实例,最后将实例信息转换为路由规则。
  2. getRouteDefinitions 方法是 DiscoveryClientRouteDefinitionLocator类的核心方法,用于获取所有的路由规则。它通过 Flux.fromIterable() 获取所有服务名称,然后通过 flatMap()方法遍历每个服务名称,获取该服务的路由规则。获取路由规则的方法是 getRoutes(),该方法通过 Mono.just()创建一个新的 RouteDefinition 对象,然后通过 Flux.fromIterable() 获取该服务的所有实例,再通过filter() 方法过滤服务实例,接着调用getInstanceRoute方法将服务实例转换为路由规则,最后通过reduce() 方法将所有路由规则合并成一个RouteDefinition 对象。在合并路由规则时,会调 mergeRouteDefinitions 方法实现合并过滤器和谓词的操作。
  3. getServiceNames获取所有服务名称,它通过 DiscoveryClient 类的 getServices() 方法实现。
  4. getInstances() 获取指定服务的所有实例,它通过 DiscoveryClient 类的 getInstances() 方法实现。
  5. getInstanceRoute() 方法用于将服务实例转换为路由规则,它创建一个新的 RouteDefinition对象,将服务名称作为路由规则的 ID,将服务实例的 URI 作为路由规则的 URI,并返回该路由规则对象。
  6. mergeRouteDefinitions方法用于合并路由规则,它将两个路由规则对象的过滤器和谓词合并到一个路由规则对象中,并返回该路由规则对象。

五、总结

所以总而言之要回答上面的问题,还是必须要有服务注册与发现的基础知识,才能理解。而实现这个特性的关键类=DiscoveryClientRouteDefinitionLocator 类,它通过服务发现客户端从服务注册中心获取服务信息并将其转换为路由规则,并实现了 RouteDefinitionLocator接口,用于获取路由规则列表。

好了今天的分享就到这儿,希望三分钟的阅读对你有所收获。我是冰点,下次再见。

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

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

相关文章

领导者指南:用四步空杯学习法避免你的成就成为累赘

好的领导者需要不断学习。而优秀的领导者更知道什么时候忘记过去&#xff0c;才能在未来取得成功。经营任何成功都存在学习曲线。但是&#xff0c;一旦你开始依赖过去的成就&#xff0c;或者陷入过时的思维和实践&#xff0c;不再起作用&#xff0c;你必须退后一步——空杯学习…

安捷伦E4440A(Agilent) e4440a 3HZ-26.5G频谱分析仪

Agilent E4440A、Keysight E4440A、HP E4440A频谱分析仪&#xff0c;3 Hz - 26.5 GHz&#xff08;PSA 系列&#xff09; ​Agilent / Keysight PSA 系列 E4440A 高性能频谱分析仪提供强大的一键式测量、多功能功能集和前沿技术&#xff0c;可满足您的项目和需求。选项可供您选…

Python实战基础14-递归函数

1、什么是递归函数 如果一个函数在内部不调用其它的函数&#xff0c;而是自己本身的话&#xff0c;这个函数就递归函数。 遵循&#xff1a; 必须要有出口每次递归向出口靠近 # 1-10 打印数字 def test(i):if i 10:print(10)else:print(i)i 1test(i) test(1)# 1-10 的累加…

海外网红营销ROI评估:出海品牌如何科学衡量投资回报?

随着全球化的不断推进&#xff0c;出海品牌越来越多地将目光投向海外市场&#xff0c;并利用海外网红的影响力进行营销。然而&#xff0c;对于出海品牌而言&#xff0c;如何科学衡量海外网红营销的投资回报率&#xff08;ROI&#xff09;却是一个关键的挑战。本文Nox聚星将和大…

Phaser笔记-精灵(sprite)的使用

如下&#xff1a; 使用逻辑如下&#xff1a; ①设置好physics&#xff1b; ②将资源添加到spritesheet&#xff1b; ③在physics中添加sprite&#xff1b; ④创建anims&#xff1b; ⑤播放anims&#xff1b; 完整代码如下&#xff1a; import phaser;class PlayGame exte…

进程间通信(命名管道)

目录&#xff1a; 1.命名管道 2.创建命名管道 --------------------------------------------------------------------------------------------------------------------------------- 1.命名管道 1.管道的一个应用限制就是只能在具有共同祖先&#xff08;具有亲缘关系&…

AI落地:高效学习指南

高效学习中有一个共识&#xff1a;学习最小可用知识&#xff0c;然后立马开始实践&#xff0c;做中学&#xff0c;不断获得反馈&#xff0c;不断在实践中改进。 现实生活中&#xff0c;如果我们想实现这种高效学习&#xff0c;基本上只能找一个老师1对1指导&#xff0c;费用贵…

[C语言][典例详解]打印杨辉三角(找规律简单实现)

目录 杨辉三角的相关知识 杨辉三角图&#xff1a; 杨辉三角的规律 在编程中实现 第一步 &#xff1a;我们先实现数字的打印&#xff0c;后面再加上空格构成三角形形状&#xff1b; ​编辑 1.首先我们可以直观的看出三角形的两个斜边都是1&#xff1b;所以我们先打印斜边的…

S型平滑函数功能块(SCL+梯形图)

S型平滑和斜坡平滑函数从字面理解也知道大概用处了,这里就不再具体阐述。S型曲线在温控上面的应用请参看系列专栏,链接如下: 带平滑功能的斜坡函数(多段曲线控温纯S型曲线SCL源代码+完整算法分析)_s斜坡函数_RXXW_Dor的博客-CSDN博客PLC运动控制基础系列之梯形速度曲线,…

Java 集合和数组,集合和字符串,数组和字符串的相互转换

在 Java 中&#xff0c;可以通过以下方式实现集合、数组和字符串之间的相互转换。 一、集合和数组的相互转化 ①、将集合转为数组&#xff1a;&#xff08;toArray 方法&#xff09; List<String> list new ArrayList<>(); list.add("apple"); lis…

手写服务限流6种实现方式

服务限流&#xff0c;我有 6 种实现方式 ImportNew 2023-05-29 11:31 发表于上海 以下文章来源于码农参上 &#xff0c;作者Dr Hydra 码农参上. 专注后端技术分享&#xff0c;有趣、深入、直接&#xff0c;与你聊聊技术。 今天来和大家聊聊服务的限流。 服务限流&#xff0…

2023 年 14 个最佳隐私和安全 Linux 发行版

随着数字时代的到来&#xff0c;隐私和安全成为了越来越重要的议题。对于那些关注隐私和安全的用户来说&#xff0c;选择一款专注于保护用户数据和系统安全的Linux发行版是至关重要的。在本文中&#xff0c;我们将介绍2023年14个最佳的隐私和安全Linux发行版&#xff0c;这些发…

stm32学习笔记-11 SPI通信

11 SPI通信 文章目录 11 SPI通信11.1 SPI通信协议11.2 W25Q64简介11.3 实验&#xff1a;软件SPI读写W25Q6411.4 SPI通信外设11.5 实验&#xff1a;硬件SPI读写W25Q64 注&#xff1a;笔记主要参考B站 江科大自化协 教学视频“ STM32入门教程-2023持续更新中”。 注&#xff1a…

Acrel-2000系列监控系统在亚运手球比赛馆建设

10kV供配电工程中的应用 摘要:智能化配电监控系统是数字化和信息化时代应运而生的产物&#xff0c;已经被广泛应用于电网用户侧楼宇、体育场馆、科研设施、机场、交通、医院、电力和石化行业等诸多领域的高/低压变配电系统中。安科瑞自研的Acrel-2000系列监控系统可监控高压开…

【自动化测试】自动化测试框架那些事儿!

无论是在自动化测试实践&#xff0c;还是日常交流中&#xff0c;经常听到一个词&#xff1a;框架。在教学的过程中&#xff0c;同学们一直对“框架”这个词知其然不知其所以然。 最近看了很多自动化相关的资料&#xff0c;加上一些实践&#xff0c;算是对“框架”有了一些理解…

OceanMind海睿思入选《2023中国企业数智化转型全景图中国数据智能产业图谱》

近日&#xff0c;国内知名大数据产业创新服务媒体数据猿携手上海大数据联盟发布了《2023中国企业数智化转型升级服务全景图/产业图谱》和《2023中国数据智能产业图谱》。 两份图谱系统梳理了中国数智化转型升级及数据智能行业发展现状和脉络&#xff0c;评选出极具商业合作价值…

​​​​Linux Shell 实现一键部署Oracle21 zip包方式

oracle前言 Oracle开发的关系数据库产品因性能卓越而闻名&#xff0c;Oracle数据库产品为财富排行榜上的前1000家公司所采用&#xff0c;许多大型网站也选用了Oracle系统&#xff0c;是世界最好的数据库产品。此外&#xff0c;Oracle公司还开发其他应用程序和软件。同时&#…

ABeam News|ABeam荣获「SAP BTP 卓越业务分析奖」

近日&#xff0c;「云铸数卯&#xff0c;榫合万象」SAP BTP 客户与生态峰会在桂林圆满落幕。此次年度峰会以全面的视角展现 RISE with SAPSAP BTP如何构建“新型中国企业”数字底座。 ABeam Consulting作为SAP金牌合作伙伴受邀出席&#xff0c;大中华区董事长兼总经理中野洋辅…

Ubuntu Go语言环境配置【GPT-4版】

目录 go语言的培训网课安装goGPT-4给出的过程在VSCode里怎样正确配置好Go的调试相关设置&#xff1f;如果我在Visual Studio Code中安装Go语言的相关工具总是失败怎么办&#xff1f;我已重启Visual Studio Code&#xff0c;接下来应该怎样检查日志&#xff1f;如果我还是不能成…

Vue2中给对象添加新属性界面不刷新

Vue2中给对象添加新属性界面不刷新? Vue2.x的响应式 实现原理 对象类型&#xff1a;通过Object.defineProperty()对属性的读取、修改进行拦截&#xff08;数据劫持&#xff09;。数组类型&#xff1a;通过重写更新数组的一系列方法来实现拦截。&#xff08;对数组的变更方法…