| <!DOCTYPE html> |
| <html class="writer-html5" lang="en" > |
| <head> |
| <meta charset="utf-8" /> |
| <meta http-equiv="X-UA-Compatible" content="IE=edge" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <link rel="shortcut icon" href="../../img/favicon.ico" /> |
| <title>2.0.0 新特性介绍: 弱类型契约 - ServiceComb Java Chassis 开发指南</title> |
| <link rel="stylesheet" href="../../css/theme.css" /> |
| <link rel="stylesheet" href="../../css/theme_extra.css" /> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/styles/github.min.css" /> |
| |
| <script> |
| // Current page data |
| var mkdocs_page_name = "2.0.0 \u65b0\u7279\u6027\u4ecb\u7ecd\uff1a \u5f31\u7c7b\u578b\u5951\u7ea6"; |
| var mkdocs_page_input_path = "featured-topics/features/weak-type-contrast.md"; |
| var mkdocs_page_url = null; |
| </script> |
| |
| <script src="../../js/jquery-3.6.0.min.js" defer></script> |
| <!--[if lt IE 9]> |
| <script src="../../js/html5shiv.min.js"></script> |
| <![endif]--> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/highlight.min.js"></script> |
| <script>hljs.initHighlightingOnLoad();</script> |
| </head> |
| |
| <body class="wy-body-for-nav" role="document"> |
| |
| <div class="wy-grid-for-nav"> |
| <nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav"> |
| <div class="wy-side-scroll"> |
| <div class="wy-side-nav-search"> |
| <a href="../../index.html" class="icon icon-home"> ServiceComb Java Chassis 开发指南 |
| </a> |
| </div> |
| |
| <div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu"> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../toc.html">目录</a> |
| </li> |
| </ul> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../index.html">概述</a> |
| </li> |
| </ul> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../start/catalog.html">快速入门</a> |
| </li> |
| </ul> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../start/design.html">设计选型参考</a> |
| </li> |
| </ul> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../build-provider/definition/service-definition.html">微服务定义</a> |
| </li> |
| </ul> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../build-provider/catalog.html">开发服务提供者</a> |
| </li> |
| </ul> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../build-consumer/catalog.html">开发服务消费者</a> |
| </li> |
| </ul> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../general-development/catalog.html">通用功能开发</a> |
| </li> |
| </ul> |
| <p class="caption"><span class="caption-text">多样化的通信协议功能参考</span></p> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../transports/introduction.html">多协议介绍</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../transports/rest-over-servlet.html">REST over Servlet</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../transports/rest-over-vertx.html">REST over Vertx</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../transports/http2.html">REST over HTTP2</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../transports/highway-rpc.html">Highway</a> |
| </li> |
| </ul> |
| <p class="caption"><span class="caption-text">多样化的服务注册与发现功能参考</span></p> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../registry/introduction.html">注册发现说明</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../registry/service-center.html">使用服务中心</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../registry/local-registry.html">本地注册发现</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../registry/distributed.html">去中心化注册发现</a> |
| </li> |
| </ul> |
| <p class="caption"><span class="caption-text">管理服务配置</span></p> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../config/general-config.html">通用配置说明</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../config/read-config.html">在程序中读取配置信息</a> |
| </li> |
| </ul> |
| <p class="caption"><span class="caption-text">服务治理功能参考</span></p> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../references-handlers/intruduction.html">处理链介绍</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../references-handlers/loadbalance.html">负载均衡</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../references-handlers/ratelimit.html">限流</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../references-handlers/router.html">灰度发布</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../references-handlers/fault-injection.html">故障注入</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../references-handlers/governance.html">流量特征治理</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../references-handlers/fail-retry.html">快速失败和重试</a> |
| </li> |
| </ul> |
| <p class="caption"><span class="caption-text">网关功能参考</span></p> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../edge/open-service.html">介绍</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../edge/by-servicecomb-sdk.html">使用 Edge Service 做网关</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../edge/zuul.html">使用 `zuul` 和 `spring cloud gateway` 做网关</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../edge/nginx.html">nginx 网关简单介绍</a> |
| </li> |
| </ul> |
| <p class="caption"><span class="caption-text">安全特性参考</span></p> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../references-handlers/publickey.html">公钥认证</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../security/tls.html">使用TLS通信</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../security/shi-yong-rsa-ren-zheng.html">使用RSA认证</a> |
| </li> |
| </ul> |
| <p class="caption"><span class="caption-text">服务打包和运行</span></p> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../packaging/standalone.html">以standalone模式打包</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../packaging/web-container.html">以WEB容器模式打包</a> |
| </li> |
| </ul> |
| <p class="caption"><span class="caption-text">专题文章</span></p> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../using-java-chassis-in-spring-boot/using-java-chassis-in-spring-boot.html">在Spring Boot中使用java chassis</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../features.html">新功能介绍系列文章</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../compatibility.html">兼容问题和兼容性策略</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../upgrading.html">升级指导系列文章</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../performance.html">性能问题分析和调优</a> |
| </li> |
| </ul> |
| <p class="caption"><span class="caption-text">常用配置项参考</span></p> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../config-reference/rest-transport-client.html">REST Transport Client 配置项</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../config-reference/config-center-client.html">Config Center Client 配置项</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../config-reference/service-center-client.html">Service Center Client 配置项</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../config-reference/kie-client.html">ServiceComb Kie Client 配置项</a> |
| </li> |
| </ul> |
| <p class="caption"><span class="caption-text">常见问题</span></p> |
| <ul> |
| <li class="toctree-l1"><a class="reference internal" href="../../question-and-answer/faq.html">FAQ</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../question-and-answer/question_answer.html">Q & A</a> |
| </li> |
| <li class="toctree-l1"><a class="reference internal" href="../../question-and-answer/interface-compatibility.html">微服务接口兼容常见问题</a> |
| </li> |
| </ul> |
| </div> |
| </div> |
| </nav> |
| |
| <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"> |
| <nav class="wy-nav-top" role="navigation" aria-label="Mobile navigation menu"> |
| <i data-toggle="wy-nav-top" class="fa fa-bars"></i> |
| <a href="../../index.html">ServiceComb Java Chassis 开发指南</a> |
| |
| </nav> |
| <div class="wy-nav-content"> |
| <div class="rst-content"><div role="navigation" aria-label="breadcrumbs navigation"> |
| <ul class="wy-breadcrumbs"> |
| <li><a href="../../index.html" class="icon icon-home" alt="Docs"></a> »</li> |
| <li>2.0.0 新特性介绍: 弱类型契约</li> |
| <li class="wy-breadcrumbs-aside"> |
| </li> |
| </ul> |
| <hr/> |
| </div> |
| <div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article"> |
| <div class="section" itemprop="articleBody"> |
| |
| <h1 id="200">2.0.0 新特性介绍: 弱类型契约</h1> |
| <p>"以契约为中心" 是 Java Chassis 的核心设计理念。 “契约” 扮演着用户与开发者,开发者与开发者之间的沟通桥梁。 |
| 举几个简单的例子。</p> |
| <p>开发者发布了一个微服务A,同时发布了一份契约文件:</p> |
| <pre><code class="language-yaml">swagger: '2.0' |
| info: |
| title: rest api |
| version: 1.0.0 |
| |
| basePath: /controller |
| produces: |
| - application/json |
| |
| paths: |
| /add: |
| get: |
| operationId: add |
| parameters: |
| - name: a |
| in: query |
| required: true |
| type: integer |
| format: int32 |
| - name: b |
| in: query |
| required: true |
| type: integer |
| format: int32 |
| responses: |
| "200": |
| description: add numer |
| schema: |
| type: integer |
| format: int32 |
| </code></pre> |
| <p>微服务A的用户可以基于这份契约,使用浏览器访问<code>add</code>接口,也可以在自己的微服务B中,使用<code>RestTemplate</code> |
| 调用<code>add</code>接口,用户不需要知道微服务A的实现细节,也不需要依赖任何微服务A提供的接口,微服务B保持完全 |
| 与微服务A独立。只要契约不变,微服务B就可以保持不变。</p> |
| <p>作为微服务A的其他开发者,可以基于契约开发治理功能,不需要知道<code>add</code>接口的实现细节。比如可以给<code>add</code> |
| 接口增加流量控制 <code>servicecomb.flowcontrol.Provider.qps.limit.[ServiceA].[Controller].[add]=1000</code>, |
| 限制其流量为1000 TPS;还可以基于 <code>add</code> 接口的参数做灰度发布,在灰度发布代码里面可能会存在下面的代码片段: |
| <code>invocation.getSwaggerArgument("a")</code> 来获取参数值。</p> |
| <p>"以契约为中心"是保持微服务"独立性"非常重要的手段,“独立开发、独立交付”是微服务被引入,提升软件工程能力 |
| 最重要的价值。</p> |
| <p>本文重点介绍2.0.0版本引入的“弱类型契约”,以及它与之前“强类型契约”之间的差别与联系。</p> |
| <h2 id="vs">弱类型契约 vs 强类型契约</h2> |
| <p>弱类型契约和强类型契约的共同点都是"以契约为中心",因此弱类型契约并没有改变 Java Chassis 的整体设计理念, |
| 而是在实现层面发生了一些变化,进而影响到部分开发体验。2.0.0之前的版本是强类型契约。产生强类型契约和一些技术背景 |
| 有关系,讨论的起点可以从 Java Chassis Highway 的 ProtoBuffer 讲起。 </p> |
| <p>熟悉 ProtoBuffer 的开发者都知道, ProtoBuffer 主要被用于 gRPC, 其他处理 ProtoBuffer 编解码的库还有 ProtoStuff。 |
| gRPC 需要写 IDL 文件, 然后根据 IDL 生成运行时的代码,在gRPC的运行过程中,处理编解码的类在编译时间就已经确定,无法 |
| 更改。ProtoStuff 提供的类库比 gRPC 更加灵活,对于同样的 IDL 文件,可以在运行时指定不同的类进行序列化和反序列化。 |
| 无论如何,在一个微服务里面,如果存在一个契约(IDL文件),那么必须在编译时就确定这个契约对应的类是什么。这个类可以 |
| 有一个,也可以有多个,但是必须在编译的时候确定类的类型。</p> |
| <p>强类型契约可以定义为:在一个微服务里面,契约必须存在一个或者多个编译时确定的类型与它对应。在2.0.0版本之前,这个对应 |
| 的类型是在契约文件里面指定的,比如<code>x-java-interface: org.apache.servicecomb.demo.controller.Controller</code> 。 开发者 |
| 可能注意到在消费端代码,可以不依赖任何提供端的类,这是因为消费端的代码会根据契约描述,采用 <code>javassist</code> 工具动态生成 |
| 一个类型与契约对应,不同版本的契约需要生成不一样的类型,如果灰度环境版本过多,就可能导致内存过大的问题,特别是在边缘 |
| (Edge Service)服务里面。</p> |
| <p>强类型契约的一个外在体现就是下面的代码:</p> |
| <pre><code class="language-java">Person result = restTemplate.postForObject("/getPerson", null, Person.class); |
| </code></pre> |
| <p>可能抛出 <code>ClassCastException</code> 异常。 因为 <code>getPerson</code> 的返回值的类型在编译时已经确定, 如果返回值 Person 类型 |
| 与编译时的类型不一样,就会报告 <code>ClassCastException</code> 异常。</p> |
| <p>了解了强类型契约后,弱类型契约的定义就很明显了: 在一个微服务里面,契约可以存在一个或者多个编译时确定的类型与它对应, |
| 也可以不存在编译时确定的类型与它对应。 引入弱类型契约后,下面的代码:</p> |
| <pre><code class="language-java">Person1 result = restTemplate.postForObject("/getPerson", null, Person1.class); |
| Person2 result = restTemplate.postForObject("/getPerson", null, Person2.class); |
| </code></pre> |
| <p>都不会报告 <code>ClassCastException</code> 异常,只需要 <code>Person1</code> 和 <code>Person2</code> 的定义都能够和契约对应。 由此可以看出,弱类型 |
| 契约在使用方式上更加灵活,去掉了动态创建类的过程,降低了内存占用,缩短了微服务启动时间。</p> |
| <h2 id="_1">利用弱类型契约增强写代码的灵活性</h2> |
| <p>弱类型契约不要求提供者与消费者使用一样的类型,在代码书写上提供了很大的方便。比如提供者有如下接口:</p> |
| <pre><code class="language-java">@RestSchema(schemaId = "weakSpringmvc") |
| @RequestMapping(path = "/weakSpringmvc", produces = MediaType.APPLICATION_JSON_VALUE) |
| public class WeakSpringmvc { |
| @GetMapping(path = "/diffNames") |
| @ApiOperation(value = "differentName", nickname = "differentName") |
| public int diffNames(@RequestParam("x") int a, @RequestParam("y") int b) { |
| return a * 2 + b; |
| } |
| |
| @GetMapping(path = "/genericParams") |
| @ApiOperation(value = "genericParams", nickname = "genericParams") |
| public List<List<String>> genericParams(@RequestParam("code") int code, @RequestBody List<List<String>> names) { |
| return names; |
| } |
| |
| @GetMapping(path = "/genericParamsModel") |
| @ApiOperation(value = "genericParamsModel", nickname = "genericParamsModel") |
| public GenericsModel genericParamsModel(@RequestParam("code") int code, @RequestBody GenericsModel model) { |
| return model; |
| } |
| |
| @GetMapping(path = "/specialNameModel") |
| @ApiOperation(value = "specialNameModel", nickname = "specialNameModel") |
| public SpecialNameModel specialNameModel(@RequestParam("code") int code, @RequestBody SpecialNameModel model) { |
| return model; |
| } |
| } |
| </code></pre> |
| <p>而消费者只需要访问其中一个接口diffNames,只需要定义一个非常简单的接口:</p> |
| <pre><code class="language-java">interface DiffNames { |
| int differentName(int x, int y); |
| } |
| |
| @RpcReference(microserviceName = "springmvc", schemaId = "weakSpringmvc") |
| private DiffNames diffNames; |
| </code></pre> |
| <p>其中接口名称是和契约里面的接口名称一致 <code>differentName</code>, 而不是和服务端的代码一致 <code>diffNames</code>。 契约包含 <code>x</code> 和 |
| <code>y</code> 两个参数, 并且契约的参数是顺序无关的, 下面的代码也是可以访问同一个接口的:</p> |
| <pre><code class="language-java">interface DiffNames2 { |
| int differentName(int y, int x); |
| } |
| |
| @RpcReference(microserviceName = "springmvc", schemaId = "weakSpringmvc") |
| private DiffNames2 diffNames2; |
| </code></pre> |
| <p>需要注意的是,2.0.0 版本要求保留参数名称, 在编译代码的时候,需要加上 -parameters 编译参数。</p> |
| <p>开发者还可以通过 RestTemplate 调用这个接口:</p> |
| <pre><code class="language-java">restTemplate.getForObject("cse://springmvc/weakSpringmvc/diffNames?x=2&y=3", Integer.class) |
| </code></pre> |
| <p>或者采用 <code>InvokerUtils</code> 调用: </p> |
| <pre><code class="language-java">Map<String, Object> args = new HashMap<>(); |
| args.put("x", 2); |
| args.put("y", 3); |
| InvokerUtils.syncInvoke("springmvc", "weakSpringmvc", "differentName", args); |
| </code></pre> |
| <h2 id="_2">弱类型契约的治理功能</h2> |
| <p>弱类型契约在 <code>Invocation</code> 里面提供了独立的方法,让开发者开发治理功能更加容易。 </p> |
| <pre><code class="language-java"> public Map<String, Object> getInvocationArguments() { |
| return this.invocationArguments; |
| } |
| |
| public Map<String, Object> getSwaggerArguments() { |
| return this.swaggerArguments; |
| } |
| |
| public Object getInvocationArgument(String name) { |
| return this.invocationArguments.get(name); |
| } |
| |
| public Object getSwaggerArgument(String name) { |
| return this.swaggerArguments.get(name); |
| } |
| </code></pre> |
| <p><code>getSwaggerArguments</code> 始终获取的是和契约对应的参数,如果契约为 <code>x</code> 和 <code>y</code> 两个 query 参数, 那么得到的参数就是 |
| 包含 <code>x</code> 和 <code>y</code> 的 Map 。 <code>getInvocationArgument</code> 获取的是实际类型参数, 在服务提供者,这个参数列表的个数和类型 |
| 和实际的 Method 的列表对应, 比如可能包含 <code>InvocationContext</code> , <code>HttpServletRequest</code> 等注入参数。 还有很多情况, |
| 契约参数和类型参数不对应,比如聚合参数的情况,契约参数是多个 query 参数, 而类型参数是一个 POJO; 再比如 POJO 接口 |
| 定义的时候, 类型参数可能是多个, 而契约参数只有一个 body 参数。 在服务消费者,如果用户采用 POJO 方式调用服务提供者, |
| 两个接口的返回的值与服务提供者类似,存在语义差别;如果采用 RestTemplate 或者 InvokerUtils 调用, 那么两个接口返回的 |
| 内容一样,都是契约参数。在边缘服务, 两个接口返回的都是契约参数。 这种行为体现了弱类型契约的语义: 是否存在编译时 |
| 类型与契约对应。</p> |
| <h2 id="_3">弱类型契约带来的一些变更</h2> |
| <p>总体而言,对于 <code>REST</code> 通信模式, 弱类型契约不仅增强了写代码的灵活性, 还完整保留了强类型契约的写代码方式,几乎不 |
| 存在用户需要感知的变更。 对于 <code>HIGHWAY</code> 通信模式, 由于底层采用 ProtoBuffer 编码, 而 ProtoBuffer 天然就是一种 |
| 强类型契约的编解码过程, java-chassis 为了支持弱类型契约, 做了大量努力, 在一些边界条件处理上与弱类型契约存在 |
| 变更,两个版本的编解码是不兼容的,需要同时升级提供者和消费者。 在编码方式上,差异主要体现在对于缺省值的处理,对于 |
| <code>null</code> 的处理等问题上, 详细参考<a href="../upgrading/1_3_0T2_0_0.html">1.3.0 升级 2.0.0指导</a> 。</p> |
| |
| </div> |
| </div><footer> |
| |
| <hr/> |
| |
| <div role="contentinfo"> |
| <!-- Copyright etc --> |
| </div> |
| |
| Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>. |
| </footer> |
| |
| </div> |
| </div> |
| |
| </section> |
| |
| </div> |
| |
| <div class="rst-versions" role="note" aria-label="Versions"> |
| <span class="rst-current-version" data-toggle="rst-current-version"> |
| |
| |
| |
| </span> |
| </div> |
| <script>var base_url = '../..';</script> |
| <script src="../../js/theme_extra.js" defer></script> |
| <script src="../../js/theme.js" defer></script> |
| <script src="../../search/main.js" defer></script> |
| <script defer> |
| window.onload = function () { |
| SphinxRtdTheme.Navigation.enable(true); |
| }; |
| </script> |
| |
| </body> |
| </html> |