业务流程

业务梳理

上一节我们做了HTTP请求会话协议处理,当发起HTTP请求时,网关可以拿到HTTP请求的内容。但是对于网关来说,只是单纯的拿到HTTP请求的内容这个还不够,API 网关的本质是:为了在分布式系统中统一“对外通信”的流程,简化客户端调用,增强系统的安全性、可观测性与灵活性。因此网关是微服务架构下的“统一入口”,负责请求转发、安全控制、协议转换、聚合处理等,是连接客户端与后端服务的中介,起到“门面”和“守门人”的作用。客户端请求时是为了向服务端发起请求得到想要的功能性内容,网关拿到请求内容后,需要将其进行解析,进行对应服务的调用。这就涉及到把来自网关的HTTP请求,转换到RPC调用上,这里就涉及到了一个网关非常重要的功能:代理RPC泛化调用。说的简单明白点就是:我在浏览器发起一个HTTP请求,这个请求需要调用服务端的功能,但是我的服务端已经被拆分为了许多的应用服务单独部署了起来,并且同一个服务可能还部署了多个,那么这每一个服务可能都有对应的调用IP地址,但是一般情况下,IP地址不会允许对外公布,所以每一个服务就会有一个专门的域名,但问题是我现在有很多的这种部署实例,是不是就意味着前端页面的请求地址要写很多个,如果某一个实例或多个实例在未来进行了变更,地址也发生了改变,那前端请求地址是不是也得改;因此我们需要一个组件将我们所有的服务对外只有一个接口地址,就像网上购物,你只会在专门的APP内付款下单,至于后面的商品运输什么的,就不再是我们需要关心的问题了,当请求到达这个地址后,这个地址再根据请求内容调用相关服务。

但是这里调用,又改如何调用呢?我们不可能将所有的服务调用都硬编码到网关里面,如果这样的话,以后增加了新服务,那不是就得重新编码网关,然后重新部署上线。所以这里就需要网关使用RPC提供的泛化调用服务,将不同的请求与不同的服务对应起来。

image.png

如上图(注意图片来源为小傅哥)所示 HTTP 经过网关调用到 RPC 中间的执行逻辑就是把两个模块用绑定的方式建立起连接,生成一个代理对象。代理对象中包装的就是执行网关接口泛化调用的参数准备和执行以及返回结果的操作。

这里面就涉及到以下几个知识点

  • 泛化调用,它是 RPC 接口设计中提供的一种反射调用机制,你不需要硬编码调用接口,只需要提供接口的方法名称、入参信息,即可调用到对应的 RPC 接口服务。可以参考Dubbo官网文档的泛化调用部分

  • 代理包装,虽然 RPC 框架提供了泛化调用,也就是说这里可以拿到网络协议转换的 HTTP 请求信息以后,就能直接调用到 RPC 接口,但是为了解耦 HTTP 请求与泛化调用的绑定方式,并为未来支持多种远程协议留下扩展空间,我们在 外部(GenericService) 包装了一层代理机制(后续代码体现:通过 GenericReferenceProxyFactory 动态创建代理类,GenericReferenceProxy 实现逻辑拦截,IGenericReference 抽象统一接口)。这样可以通过统一的方式 ($invoke(String args) )发起远程调用,避免重复写死调用逻辑,提升了灵活性、可维护性和扩展性。

  • Cglib,因为有代理操作的存在,我们就需要选择一种方式来做代理处理,而Cglib 可以满足我们自定义创建接口的方式进行代理,同时又可以在让一个代理类有多个接口。注意:多个接口的意思是,一个接口是用于标准的描述,在于使用上。另外一个接口是自主生成的,生成的是我们的 RPC 描述性接口,相当于自主生成了class字节码。

系统的大致流程如下(可以直接看业务实现的梳理总结部分,对这一块有详细的阐述)

image-igot.png

接下来进行补充本节需要的相关知识的简要介绍:

RPC泛化调用

官方文档定义:泛化调用(客户端泛化调用)是指在调用方没有服务提供方 API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果。调用方没有接口及模型类元,知道服务的接口的全限定类名和方法名的情况下,可以通过泛化调用调用对应接口

但实际说的通俗点就是泛化调用指调用方不必知道服务端到底有哪些接口和方法(不需要导入相关服务方执行类的包),只要知道方法名,参数类型,参数值,就可以远程调用。

泛化调用的实现原理涉及到Dubbo的动态代理机制以及序列化和反序列化的过程:

  • 动态代理

    • Dubbo 使用 Java 的动态代理技术,在客户端生成一个代理类,用于与服务端进行通信。

    • 代理类通过拦截客户端的方法调用,并将调用转发给底层的网络通信组件,完成远程调用的过程。

  • 序列化与反序列化

    • 在泛化调用中,客户端需要将方法名和参数序列化为字节流,并发送给服务端

    • 服务端接收到字节流后,需要进行反序列化,还原出方法名和参数,并根据方法名执行对应的方法。

    • Dubbo 使用了不同的序列化协议(如 Hessian、JSON、Protobuf 等)来实现序列化和反序列化的过程

  • 泛化调用实现

    • Dubbo 提供了 GenericService 接口,用于实现泛化调用。这个接口定义了 invoke 方法,允许客户端传入方法名和参数进行调用。

    • 客户端可以通过获取服务端的 GenericService 对象来实现泛化调用。在 Dubbo 的客户端代理中,可以通过调用 invoke 方法来实现对应的泛化调用

代理与泛化调用是有区别的:

  • 代理是实现调用方式的手段,泛化调用是一种调用策略。你可以用代理去实现泛化调用,让本地方法像调用普通接口一样触发泛化请求。

概念

泛化调用

代理

作用

不依赖接口类、方法签名就能远程调用服务

在方法调用时做“中间处理”,实现转发、拦截等功能

面向谁

面向远程服务的调用逻辑

面向本地代码调用的拦截

表现

$invoke(method, types, args)

proxy.sayHi("xx") → intercept(...)

常见用途

动态 API 网关、低代码、脚本系统

RPC 客户端、AOP、Spring Bean、权限校验等

关键关键词

动态参数、JSON、无接口

JDK Proxy、CGLIB、InvocationHandler

泛化调用的使用方式主要是API的调用方式,参考官方文档提供的示例代码

 private GenericService genericService;
 ​
 public static void main(String[] args) throws Exception {
     ApplicationConfig applicationConfig = new ApplicationConfig();
     applicationConfig.setName("generic-call-consumer");
     RegistryConfig registryConfig = new RegistryConfig();
     registryConfig.setAddress("zookeeper://127.0.0.1:2181");
 ​
     ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
     referenceConfig.setInterface("org.apache.dubbo.samples.generic.call.api.HelloService");
     applicationConfig.setRegistry(registryConfig);
     referenceConfig.setApplication(applicationConfig);
     referenceConfig.setGeneric("true");
     // do not wait for result, 'false' by default
     referenceConfig.setAsync(true);
     referenceConfig.setTimeout(7000);
 ​
     genericService = referenceConfig.get();
 }
 ​
 public static void invokeSayHello() throws InterruptedException {
     Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"world"});
     CountDownLatch latch = new CountDownLatch(1);
 ​
     CompletableFuture<String> future = RpcContext.getContext().getCompletableFuture();
     future.whenComplete((value, t) -> {
         System.err.println("invokeSayHello(whenComplete): " + value);
         latch.countDown();
     });
 ​
     System.err.println("invokeSayHello(return): " + result);
     latch.await();
 }

在这份代码中首先定义了应用信息和注册中心地址

     ApplicationConfig applicationConfig = new ApplicationConfig();
     applicationConfig.setName("generic-call-consumer");
     RegistryConfig registryConfig = new RegistryConfig();
     registryConfig.setAddress("zookeeper://127.0.0.1:2181");

然后创建 ReferenceConfig 泛化调用配置对象

 ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
 referenceConfig.setInterface("org.apache.dubbo.samples.generic.call.api.HelloService");
 referenceConfig.setGeneric("true");      // 开启泛化模式
 referenceConfig.setAsync(true);          // 启用异步调用
 referenceConfig.setTimeout(7000);        // 设置超时时间

最后绑定配置获取泛化代理对象

 referenceConfig.setApplication(applicationConfig);
 applicationConfig.setRegistry(registryConfig);
 genericService = referenceConfig.get();

完整绑定所有配置项,最终调用 get() 获取 GenericService 代理,这个代理将用于后续发起远程调用

对于GenericService,它是 Apache Dubbo 提供的一个特殊接口,用于实现 泛化调用,即:

消费者端不引入服务接口和模型类 的情况下,通过统一的调用方式调用任意 Dubbo 服务的方法。

GenericService 就像是 Dubbo 的“动态 RPC 万能钥匙”,无需接口类,只要你知道方法名、参数类型、参数值,就可以发起远程调用,是开发工具、运维平台、API 网关等场景中不可或缺的利器。

最后通过$invoke() 发起异步远程调用

 Object result = genericService.$invoke(
     "sayHello",                              // 方法名
     new String[]{"java.lang.String"},        // 参数类型(字符串数组)
     new Object[]{"world"}                    // 参数值
 );
 ​

这里即使本地没有 HelloService 接口类,仍可发起调用;返回值是异步调用的占位结果,最终结果通过回调获得

处理调用结果

 CompletableFuture<String> future = RpcContext.getContext().getCompletableFuture();
 future.whenComplete((value, t) -> {
     System.err.println("invokeSayHello(whenComplete): " + value);
     latch.countDown();
 });
 ​

通过 RpcContext 获取异步调用的 CompletableFuture,注册回调函数,在远程调用完成后处理结果

泛化调用 vs 普通调用 对比表

特征

普通调用

泛化调用

是否需要接口类

是否需要接口方法签名

是(编译时)

否(运行时传入)

使用场景

常规系统

动态系统、网关、低代码

易用性

强类型安全

灵活但类型校验靠自己

示例调用

service.method()

$invoke("method", type[], args[])

字节码操作框架

字节码操作框架(Bytecode Manipulation Framework)是用于 动态生成、修改、分析 Java 字节码(.class 文件) 的工具库。Java 源码在编译后变成字节码,运行时由 JVM 解释或编译执行。通过操作字节码,开发者可以在不修改源代码的前提下实现AOP、监控、安全、代理、动态类生成等高级功能。

常见的字节码框架

框架

特点

典型用途

ASM

极致轻量、性能高、操作最底层指令

直接生成/改写字节码,适合框架底层,如 Spring、Dubbo

Javassist

语法接近 Java,易上手,性能中等

Spring AOP、Mock、热修复

ByteBuddy

API 最现代、功能强大、写法优雅

Agent 插桩、Mock、动态代理

CGLIB

基于 ASM 的封装,适合动态代理类(非接口)

Spring 动态代理、Enhancer

BCEL

较老,冗长,低级别

主要用于研究或遗留项目

在在其中CGLIB(Code Generation Library)是一个用于在运行时为类创建代理对象的框架,不依赖接口。它通过 生成目标类的子类 来拦截方法调用。CGLIB 底层正是基于 ASM 实现的

CGLIB 与 JDK 动态代理对比

特性

JDK Proxy

CGLIB

是否依赖接口

✅ 必须接口

❌ 不需要

代理方式

实现接口

生成子类

适合范围

接口型系统

面向类的系统

性能

略慢

更快(无反射)

ASM vs CGLIB 对比总结

维度

ASM

CGLIB

操作级别

字节码指令级

类结构级(方法拦截)

API 复杂度

是否生成类

✅ 是

✅ 是(子类)

是否代理方法

❌ 不直接

✅ 是(通过拦截)

是否依赖 ASM

自身

✅ 底层使用 ASM

典型用途

框架底层增强、代码生成、热更新

动态代理、Spring AOP、懒加载

对于CGLIB其常用类,其核心结构如下

 ┌────────────┐
 │ Enhancer   │ ← 创建代理类的工厂
 └────────────┘
         ↓
 ┌────────────┐
 │ Callback   │ ← 拦截器接口(如 MethodInterceptor)
 └────────────┘
         ↓
 ┌──────────────────────┐
 │ MethodProxy.invokeSuper│ ← 调用原始方法
 └──────────────────────┘

其中

作用

说明

Enhancer

动态创建类代理

核心类

MethodInterceptor

方法拦截接口

类似 AOP

MethodProxy

原方法代理

调用目标方法

CallbackFilter

方法过滤器

指定哪些方法用哪个拦截器

FixedValue

固定返回值

返回固定值的 Callback

LazyLoader

延迟加载器

创建懒加载字段

Dispatcher

动态分发器

每次调用都返回新对象

本节我们主要用到的是Enhancer

 Enhancer enhancer = new Enhancer();
 enhancer.setSuperclass(TargetClass.class);
 enhancer.setCallback(new MyMethodInterceptor());
 Object proxy = enhancer.create();
  • 设置父类(目标类)

  • 设置回调(拦截器)

  • 创建代理对象

轻量级字节码框架特点:

特性

描述

运行时动态

无需源码、无需编译,运行中修改行为

高性能

相比反射,直接字节码操作更快

绕开类型限制

可以生成任意结构的方法、类

适合中间层框架

如 RPC、ORM、AOP、IOC 框架底层

最后总结一下:字节码操作框架 = 操作 Java 世界的底层魔术师。 我们可以用它静态增强类(如 Lombok),动态注入逻辑(如 APM),甚至创造没有源代码的新类(如动态代理器),是构建现代 Java 框架的核心利器。

接下来,我们就针对本节的业务进行一个实现

业务实现

泛化调用实现

统一代理类接口

在上面的业务流程中介绍道,我们本节使用到了一个代理包装,来解耦 HTTP 请求与泛化调用的绑定方式,并为未来支持多种远程协议留下扩展空间,所以我们在 外部(GenericService) 包装了一层代理机制。那么这个代理包装,我们会定义一个统一的接口,方便后续的管理。并用其作为整个RPC泛化调用机制的对外核心入口抽象

IGenericReference

 /**
  * @program: api-gateway-core
  * @ClassName IGenericReference
  * @description: 统一泛化调用接口,无论怎样的HTTP请求,我们暴露出去的都是这个接口,一致
  * @author: zs宝
  * @create: 2025-07-29 15:53
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.bind;
 ​
 public interface IGenericReference {
     String $invoke(String args);
 }
 ​

泛化调用静态代理

在业务流程中,我们介绍道,在网关中其实并没有服务的任何引入(即没有相关的包导入),为了调用服务,我们使用了泛化调用,但是在整体项目的代码的运行过程中,我们动态的在网关中创建对应HTTP服务的代理类,这个类中的对应的HTTP请求的方法没有任何实际执行逻辑,就只是个空架子,我们创建这个,也只是为了在进行RPC泛化调用的过程里面,不直接硬编码,而是进行一层包装代理进行解耦,以让项目有足够的扩展性,那么一但运行到了包装代理类的对应方法上,我们不可能真的执行一个空架子方法,所以我们要进行一次方法拦截(这个拦截就很像一个插拔式的设计一样),即在拦截过程中去进行真正的PRC泛化调用,这里我们就要来定义这个拦截的逻辑。

GenericReferenceProxy.java

 /**
  * @program: api-gateway-core
  * @ClassName GenericReferenceProxy
  * @description: 泛化调用静态代理,每发起一个HTTP请求,网关就会给对应的HTTP方法执行对应的PRC远程调用
  * @author: zs宝
  * @create: 2025-07-29 15:59
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.bind;
 ​
 import net.sf.cglib.proxy.MethodInterceptor;
 import net.sf.cglib.proxy.MethodProxy;
 import org.apache.dubbo.rpc.service.GenericService;
 ​
 ​
 import java.lang.reflect.Method;
 ​
 public class GenericReferenceProxy implements MethodInterceptor {
 ​
     /**
      * dubbo框架的rpc泛化调用服务提供的指定远程调用类
      * 所有的远程调用都靠这个接口来最终找到远程对应的服务
      * 为什么是final呢,因为dubbo一个注册中心配置对应一个genericService
      */
     private final GenericService genericService;
 ​
     /**
      * RPC泛化调用对应的方法名
      */
     private final String methodName;
 ​
     public GenericReferenceProxy(GenericService genericService, String methodName){
         this.genericService = genericService;
         this.methodName = methodName;
     }
 ​
     /**
      * 这个拦截方法拦截的是,本地调用生成的动态代理类的对应远程调用方法
      * 这个本地调用生成的动态代理类的对应远程调用方法中其实没有任何行为逻辑,是个空壳子
      * 因此本拦截方法就是要让执行本地调用生成的动态代理类的对应远程调用方法时转移到使用GenericService进行远程调用执行真正的调用方法
      * 需要把对应的方法名,参数类型,参数拿出来传递过去
      * 泛化调用文档:https://dubbo.apache.org/zh/docsv2.7/user/examples/generic-reference/
      * @param obj 代理对象
      * @param method 执行方法
      * @param args 方法内的参数
      * @param methodProxy 代理对象
      * @return RPC泛化调用执行方法的结果
      * @throws Throwable
      */
     @Override
     public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
         //获取方法的参数类型
         Class<?>[] parameterTypes = method.getParameterTypes();
         //将参数类型转化为字符串,方便进行传输
         String[]parameters=new String[parameterTypes.length];
         for(int i=0;i<parameterTypes.length;i++){
             parameters[i]=parameterTypes[i].getTypeName();
         }
         // 举例:genericService.$invoke("sayHi", new String[]{"java.lang.String"}, new Object[]{"world"});
         return genericService.$invoke(methodName,parameters,args);
     }
 }

整体流程如下

 你调用本地接口:          proxy.sayHi("zhangsan")
             │
             ▼
 GenericReferenceProxy 拦截代理调用
             │
             ▼
 构造泛化调用参数:
     方法名 -> "sayHi"
     参数类型 -> ["java.lang.String"]
     参数值 -> ["zhangsan"]
             │
             ▼
 调用泛化服务 genericService.$invoke(...)
             │
             ▼
 远程执行 HelloService.sayHi("zhangsan")(假设是这个)
             │
             ▼
 返回结果 "Hi, zhangsan"
代理对象动态创建

上面我们说的,本地要有一层代理包装,那么对于每一个的HTTP方法都会在运行时进行一层包装,每次都会有一个创建一个动态代理对象,这里我们就要实现一个在运行时根据方法动态创建代理对象的过程,并未这个代理对象的方法设置回调函数(拦截)

GenericReferenceProxyFactory.java

 /**
  * @program: api-gateway-core
  * @ClassName GenericReferenceProxyFactory
  * @description:泛化调用的静态代理工厂,主要是针对到达HTTP的方法动态生成一个接口类,在运行时动态生成接口类的代理对象但是这个代理对象中的所有方法都是没有执行逻辑的,即都是个空壳子,只是为了让代码运行不出错,所以具体的逻辑就会交由GenericReferenceProxy进行远程的泛化调用,因此要为代理对象的所有方法注入回调拦截逻辑
  * @author: zs宝
  * @create: 2025-07-29 16:29
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.bind;
 ​
 import net.sf.cglib.core.Signature;
 import net.sf.cglib.proxy.Enhancer;
 import net.sf.cglib.proxy.InterfaceMaker;
 import org.apache.dubbo.rpc.service.GenericService;
 import org.objectweb.asm.Type;
 ​
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 ​
 public class GenericReferenceProxyFactory {
     /**
      * RPC泛化调用服务
      */
     private final GenericService genericService;
     /**
      * 方法名到代理对象的缓存,避免重复多次创建
      */
     private final Map<String,IGenericReference>genericReferenceCache=new ConcurrentHashMap<>();
 ​
     public GenericReferenceProxyFactory(GenericService genericService) {
         this.genericService = genericService;
     }
 ​
     /**
      * 返回 方法名对应的代理对象
      * @param method
      * @return
      */
     public IGenericReference newInstance(String method){
         return genericReferenceCache.computeIfAbsent(method,k->{
             //首先创建对应的代理类继承的接口,这个接口中包含对应的HTTP请求的方法
             InterfaceMaker interfaceMaker=new InterfaceMaker();
             //接口添加对应的方法
             interfaceMaker.add(
                     //方法名,返回值类型,参数类型
                     new Signature(method, Type.getType(String.class),new Type[]{Type.getType(String.class)}),
                     null
             );
             //使用当前的方法签名集创建接口
             Class<?> interfaceClass = interfaceMaker.create();
 ​
             //现在有了具体的接口类,我们还得创建其执行过程中动态生成的代理对象
             Enhancer enhancer=new Enhancer();
             enhancer.setSuperclass(Object.class);
             //设置代理对象继承的接口
             //interfaceClass    根据泛化调用注册信息创建的接口,建立 http -> rpc 关联
             //IGenericReference  统一泛化调用接口
             enhancer.setInterfaces(new Class[]{interfaceClass, IGenericReference.class});
             //由于生成的动态代理对象其实是个空壳子,内部方法压根没有执行逻辑,所以我们需要给其添加回调函数拦截
             //让RPC的远程泛化调用去调用真正的执行方法
             //拿到泛化调用
             GenericReferenceProxy genericReferenceProxy=new GenericReferenceProxy(genericService,method);
             enhancer.setCallback(genericReferenceProxy);
             //返回代理对象
             //为什么返回的是IGenericReference呢?这是我们定义的统一代理对象的接口类
             return (IGenericReference)enhancer.create();
         });
     }
 }

下面来解释下其中的代码

 //首先创建对应的代理类继承的接口,这个接口中包含对应的HTTP请求的方法
  InterfaceMaker interfaceMaker=new InterfaceMaker();
  //接口添加对应的方法
  interfaceMaker.add(
                     //方法名,返回值类型,参数类型
                     new Signature(method, Type.getType(String.class),new Type[]{Type.getType(String.class)}),
                     null
             );

这段代码是用来在运行时用字节码技术动态生成一个只包含一个方法的接口类,这个方法名是传入的 method 字符串,方法签名为:

 new Signature(method, Type.getType(String.class), new Type[]{Type.getType(String.class)})

等价于

 new Signature("sayHi", 返回值类型, 参数类型数组)

interfaceMaker.add()的第二个参数null是方法实现体,但接口没有实现体所以传 null。在这中间我们用到了ASM的字节码工具org.objectweb.asm.TypeSignature,用于描述方法签名。那么什么是方法签名呢?

方法签名是方法的名称 + 参数类型列表(顺序、个数、类型)返回值不参与方法签名。Java 编译器根据签名区分方法(重载)。JVM 字节码中也是根据签名来定位方法的。Signature 描述方法结构,Type 描述参数和返回类型

这里有一个问题,为什么我们要先生成这样一个接口,然后再来生成动态代理类,而不是直接就构造一个这样的动态代理类呢?

因为CGLIB本身不能直接给你创建“包含指定方法签名”的类,除非我们提供一个接口或父类,而我们没有这些接口源码,所以只能用 InterfaceMaker 动态造一个接口,再让 Enhancer 用这个接口来生成代理类。Enhancer 只能代理已有类或接口,它不能直接“凭空”给你生成一个类,然后加一个方法进去。得给它一个:接口或者父类。所以CGLIBEnhancer 只能实现接口或者继承类来“生成实现类”,不能在没有接口的前提下创建具有某些方法名的新类。

这里还有一个问题,为什么我们创建的接口方法其返回值和参数都是String类型,正常来讲,调用的远程方法不可能参数都是String啊

 new Signature(method, Type.getType(String.class),new Type[]{Type.getType(String.class)})

这里就涉及到了PI网关+泛化调用场景下的统一设计策略。

原因如下

设计点

理由

✅ HTTP 接口只能传 JSON 字符串

所有的请求参数通过 HTTP body、query string 都是文本

✅ 网关内部使用 JSON 字符串进行泛化调用封装

接口统一接收一个 JSON 字符串,内部解析成泛化调用需要的参数

✅ 泛化调用 $invoke(...) 最终也是接收对象数组

JSON 字符串里封装了方法名、参数类型、参数值等

✅ 返回值也统一转成 JSON 字符串

让前端也能统一解析响应

最后,介绍下这里用到的Enhancer

Enhancer 是 CGLIB 提供的一个类生成器,它通过字节码生成机制,创建一个类的子类,并在方法调用时插入自定义逻辑(Callback)。

其主要功能如下

功能

说明

✅ 创建某个类的子类

即使没有接口也可以代理(JDK Proxy 只能代理接口)

✅ 实现多个接口

动态实现多个接口

✅ 设置方法拦截器(Callback)

所有方法调用都可被统一拦截处理

✅ 动态代理工厂

框架常用来生成 Bean 的运行时代理对象

其中在这里

 enhancer.setInterfaces(new Class[]{IGenericReference.class, interfaceClass});
  • IGenericReference 是项目中统一调用接口

  • interfaceClass 是运行时用 InterfaceMaker + ASM 生成的接口,里面含有想调用的目标方法(如 sayHi(String)

然后设置方法回调处理器

 enhancer.setCallback(genericReferenceProxy);
  • 当调用代理类的任意方法时,都会调用这个 MethodInterceptor 实现类;

  • 即前面讲过的 GenericReferenceProxy,会转发到 genericService.$invoke(...)

这样虽然我们动态创建的代理对象是个空壳子,但是所有方法都会被拦截器接管,实际调用会被转化为泛化调用

最终流程图

 调用 newInstance("sayHi")
          ↓
 InterfaceMaker 构造接口 sayHi(String)
          ↓
 CGLIB enhancer 创建代理:实现了
   - 这个接口(sayHi 方法)
   - IGenericReference 接口
          ↓
 任何调用 sayHi → 被代理拦截 → 调用 genericService.$invoke("sayHi", ...)
 ​

Dubbo泛化服务引用+缓存

现在基于前面的两个类,我们的代码已经可以动态创建代理对象,执行对应的泛化调用方法,但是捏,有一个非常重要的问题:这个泛化调用,怎么让它知道具体是调那个服务的那个方法?

GenericReferenceRegistry.Java

 /**
  * @program: api-gateway-core
  * @ClassName GenericReferenceRegistry
  * @description: 泛化调用注册器
  * @author: zs宝
  * @create: 2025-07-29 16:54
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.bind;
 ​
 import com.zshunbao.gateway.session.Configuration;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.config.ReferenceConfig;
 import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 import org.apache.dubbo.config.utils.ReferenceConfigCache;
 import org.apache.dubbo.rpc.service.GenericService;
 ​
 import java.util.HashMap;
 import java.util.Map;
 ​
 public class GenericReferenceRegistry {
     //配置类
     //通过它获取各种 Dubbo 的配置实例,避免重复 new。
     private final Configuration configuration;
 ​
     public GenericReferenceRegistry(Configuration configuration) {
         this.configuration = configuration;
     }
 ​
     //泛化调用静态代理工厂
     //每个方法名(如 "sayHi")都对应一个 GenericReferenceProxyFactory;
     //后续可以根据方法名快速创建代理
     private final Map<String,GenericReferenceProxyFactory> knownGenericReferences=new HashMap<>();
 ​
 ​
     /**
      * 根据方法名,获取泛化代理对象(实现了 IGenericReference)
      * @param methodName
      * @return
      */
     public IGenericReference getGenericReference(String methodName) {
         GenericReferenceProxyFactory genericReferenceProxyFactory = knownGenericReferences.get(methodName);
         if(genericReferenceProxyFactory==null){
             throw new RuntimeException("Type " + methodName + " is not known to the GenericReferenceRegistry.");
         }
         return genericReferenceProxyFactory.newInstance(methodName);
     }
 ​
     /**
      * 注册泛化调用服务接口方法
      * 网关找到HTTP请求中的方法对应于远程服务的那个方法,将其的引用缓存起来,方便后续的genericService调用
      * @param application
      * @param interfaceName
      * @param methodName
      */
     public void addGenericReference(String application, String interfaceName, String methodName) {
         //获取调用的基础服务
         //这几个的创建成本巨大,一般在项目初始化是会从远程将对应的引用拉取下来,缓存到JVM内存中去
         ApplicationConfig applicationConfig = configuration.getApplicationConfig(application);
         RegistryConfig registryConfig = configuration.getRegistryConfig(application);
         ReferenceConfig<GenericService> reference = configuration.getReferenceConfig(interfaceName);
 ​
         //构建Dubbo服务
         //把上面配置交给 Dubbo 启动器,完成注册中心连接、服务订阅、远程引用准备等工作
         DubboBootstrap bootstrap=DubboBootstrap.getInstance();
         bootstrap.application(applicationConfig).registry(registryConfig).reference(reference).start();
 ​
         //获取泛化服务引用对象
         //从JVM内存中获取到对应的泛化调用服务
         //避免重复创建连接
         ReferenceConfigCache cache = ReferenceConfigCache.getCache();
         //GenericService 是你后续发起泛化调用的真正入口
         GenericService genericService = cache.get(reference);
         //创建并保存泛化代理工厂
         knownGenericReferences.put(methodName,new GenericReferenceProxyFactory(genericService));
     }
 }

这个类的作用如下

GenericReferenceRegistry“Dubbo 泛化服务引用 + 工厂缓存管理器,即注册并缓存 Dubbo 泛化服务对象”,它:

  1. 用来向 Dubbo 注册中心注册并引用泛化服务;

  2. 创建 GenericReferenceProxyFactory 工厂;

  3. 将方法名与泛化代理工厂进行绑定;

  4. 允许调用者通过方法名拿到对应的 IGenericReference 代理。

这里注册并缓存 Dubbo 泛化服务对象指的是GenericReferenceRegistry 负责连接 Dubbo 注册中心,引用远程服务,并将其缓存起来供后续调用使用。这里涉及到了Dubbo远程调用的一些零碎知识

Dubbo 的远程调用是“先引用,后调用”

在 Dubbo 中,客户端要调用一个远程服务要先做:

 ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
 reference.setInterface("com.xx.MyService");
 reference.setGeneric(true);
 GenericService genericService = reference.get();

这一整套动作非常“重”(要连注册中心、服务发现、创建连接等),所以 Dubbo 官方建议:

引用一次后,缓存 GenericService 对象,避免每次都重新连接。

引用:引用是 Dubbo 中的术语,指客户端向 Dubbo 注册中心请求某个服务接口的实现类的代理对象(远程代理),以便后续发起调用。

远程代理:是指客户端本地生成的一个代理对象,它看起来就像是服务接口的实现类,但实际上内部会将方法调用转化为远程网络请求,去调用远端服务提供者的真实实现。说简单点,远程代理就是在本地调用一个“假的实现类”,它把你写的每一次方法调用,偷偷转化成网络调用请求发送到远端真实服务上,并把结果返回给你。

这个引用过程会发生

  • 连接注册中心(如 Zookeeper)

  • 订阅服务提供者列表

  • 负载均衡策略选择一个地址

  • 和远程服务建立连接(或准备连接池)

  • 构建 GenericService 代理对象(它内部就是一个动态代理)

  • 后面只需调它的 $invoke() 方法,它就能远程调用实际的方法

我们的代码中

         //获取调用的基础服务
         //这几个的创建成本巨大,一般在项目初始化是会从远程将对应的引用拉取下来,缓存到JVM内存中去
         ApplicationConfig applicationConfig = configuration.getApplicationConfig(application);
         RegistryConfig registryConfig = configuration.getRegistryConfig(application);
         ReferenceConfig<GenericService> reference = configuration.getReferenceConfig(interfaceName);
 ​
         //构建Dubbo服务
         //把上面配置交给 Dubbo 启动器,完成注册中心连接、服务订阅、远程引用准备等工作
         DubboBootstrap bootstrap=DubboBootstrap.getInstance();
         bootstrap.application(applicationConfig).registry(registryConfig).reference(reference).start();

这就是注册泛化服务对象 GenericService”,供后面调用用

在缓存之后,我们会把genericService 放进

 knownGenericReferences.put(methodName, new GenericReferenceProxyFactory(genericService));

所有泛化调用的请求,最终都会用这个被缓存的 GenericService 执行远程调用。

这里还有一个东西缓存那具体是缓存到哪里呢

我们的代码中有这样一句

 ReferenceConfigCache cache = ReferenceConfigCache.getCache();
 GenericService genericService = cache.get(reference);

ReferenceConfigCache 是 Dubbo 官方提供的 内存缓存机制,用于缓存引用。其本质是:缓存到 JVM 内存中的 ConcurrentMap<String, ReferenceConfig<?>>,一个接口对应一个代理。

参考Dubbo的官方文档说明:ReferenceConfig 是重量级对象,不应该频繁创建和销毁,否则会导致内存泄露、连接泄露。所以必须缓存,我们使用:

  • 初始化时:创建并缓存一次;

  • 使用时:从缓存中取出来用(cache.get(reference)

所以到这里引用和缓存其实指的是:使用 ReferenceConfig 向注册中心请求目标服务接口的远程代理对象(GenericService),并通过 Dubbo 提供的 ReferenceConfigCache 缓存起来,避免重复连接注册中心和重复创建代理,供后续 HTTP 泛化调用使用

这样中在执行GenericServiceinvoke方法时才知道应该调用那个远程服务方法

最后这里面涉及到一个配置类里面配置了应用服务,注册中心,以及泛化服务配置(这部分配置可以对照着业务流程出给的官方文档来看,基本一致)

Configuration.java

 /**
  * @program: api-gateway-core
  * @ClassName Configuration
  * @description: RPC远程泛化调用配置,会话生命周期配置
  * @author: zs宝
  * @create: 2025-07-29 16:56
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.session;
 ​
 import com.zshunbao.gateway.bind.GenericReferenceRegistry;
 import com.zshunbao.gateway.bind.IGenericReference;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.config.ReferenceConfig;
 import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.rpc.service.GenericService;
 ​
 import java.util.HashMap;
 import java.util.Map;
 ​
 public class Configuration {
     //对应的注册器
     private final GenericReferenceRegistry registry=new GenericReferenceRegistry(this);
     //根据官方文档的示例,dubbo的泛化调用的API调用方式主要有3个东西需要配置
     //分别是ApplicationConfig,RegistryConfig,ReferenceConfig<GenericService>
     //但是我们这是一个网关,被调用的服务方可能不止一个,因此,我们需要按照对应的服务名和配置名,将其对应起来保存
     //RPC 应用服务配置项
     //注意键为应用名称;值为应用服务配置项
     private final Map<String, ApplicationConfig> applicationConfigMap=new HashMap<>();
     //RPC 注册中心配置项
     //键为应用名称;值为注册中心配置项
     private final Map<String, RegistryConfig> registryConfigMap=new HashMap<>();
     //RPC 泛化服务配置项
     //注意这里的键:远程服务接口的全限定类名 ; 值为泛化引用配置对象
     private final Map<String, ReferenceConfig<GenericService>> referenceConfigMap=new HashMap<>();
 ​
     public Configuration(){
         //TODO 后期从配置中获取,本节主要内容是泛化服务的调用
         ApplicationConfig applicationConfig=new ApplicationConfig();
         applicationConfig.setName("api-gateway-test");
         applicationConfig.setQosEnable(false);
 ​
         RegistryConfig registryConfig=new RegistryConfig();
         //配置应用在那个注册中心可以被调用
         registryConfig.setAddress("zookeeper://127.0.0.1:2181");
         registryConfig.setRegister(false);
 ​
         //对应的泛化服务配置
         ReferenceConfig<GenericService> reference=new ReferenceConfig<>();
         //配置泛化调用的服务方接口
         reference.setInterface("cn.bugstack.gateway.rpc.IActivityBooth");
         reference.setVersion("1.0.0");
         reference.setGeneric("true");
 ​
         //加入缓存中去
         applicationConfigMap.put("api-gateway-test",applicationConfig);
         registryConfigMap.put("api-gateway-test",registryConfig);
         referenceConfigMap.put("cn.bugstack.gateway.rpc.IActivityBooth",reference);
 ​
     }
     //对应的服务配置项对外get方法
     public ApplicationConfig getApplicationConfig(String applicationName) {
         return applicationConfigMap.get(applicationName);
     }
 ​
     //对应的对外注册中心的get方法
     public RegistryConfig getRegistryConfig(String applicationName) {
         return registryConfigMap.get(applicationName);
     }
 ​
     //对应的对外泛化调用配置项get方法
     public ReferenceConfig<GenericService> getReferenceConfig(String interfaceName) {
         return referenceConfigMap.get(interfaceName);
     }
 ​
     //添加泛化调用方法
     public void addGenericReference(String application, String interfaceName, String methodName){
         registry.addGenericReference(application, interfaceName, methodName);
     }
 ​
     //获取泛化调用代理对象
     public IGenericReference getGenericReference(String methodName) {
         return registry.getGenericReference(methodName);
     }
 }
 ​
梳理总结

通过上面几个小节内容我们知道了,现在在这个服务里面进行运行的时候,会有两个代理对象,一个是使用 ReferenceConfig 向注册中心请求目标服务接口的远程代理对象(GenericService),另外一个是GenericReferenceProxyFactory的newInstance方法创建的代理对象。这两个代理对象有什么区别呢

项目

GenericService(Dubbo)

IGenericReference 代理(你项目)

创建者

Dubbo

自己用 CGLIB 创建

实现方式

JDK 动态代理

CGLIB 代理 + 动态接口生成

核心作用

发起远程调用

接收本地方法调用并转发

是否发起 RPC

✅ 发起 $invoke(...)

❌ 自身不发起,但会转给 GenericService

对外暴露

不适合直接用于 Controller 调用

是 HTTP 网关统一封装接口

动态性

固定 $invoke(...) 方法

每个方法都可以动态生成代理 sayHi(...)

总结:GenericService 是真正拥有“远程调用执行逻辑”的对象,而 GenericReferenceProxyFactory.newInstance(...) 创建的代理对象本身并不具有远程调用能力,只是通过方法拦截(GenericReferenceProxy把调用“转发”给 GenericService 去执行

这里我们再阐述一下为什么需要两层代理?

这是为了解耦、复用、网关适配的架构策略:

层级

说明

底层:GenericService

来自 Dubbo,是所有泛化调用的“底层通信代理”

中层:GenericReferenceProxy

拦截器,将方法调用翻译为 $invoke(...) 调用

上层:IGenericReference 代理对象(包装器)

网关统一调用接口,只暴露 String $invoke(String)String sayHi(String)

这里我还想在具体阐述下我动态创建的 GenericReferenceProxyFactory.newInstance(...) 的空壳代理对象,它内部到底有什么东西?

这个空壳子本质是 一个运行时生成的动态代理类对象,它具有方法签名(比如 sayHi(String)),也可以被调用,但 方法内部没有实现逻辑,调用时会被 拦截器接管

所以其内部会有

组成

内容

✔️ 类结构

是一个 Object 的子类,实现了若干接口(如 IGenericReference 和你动态生成的接口)

✔️ 方法签名

包含你动态生成的接口中的方法,比如 sayHi(String)$invoke(String)

✔️ 字节码

是一个实际存在的 .class 字节码类,由 CGLIB + ASM 生成

✔️ 被拦截机制

每个方法都被“方法拦截器”拦截,真实逻辑不在方法体内,而在拦截器中实现

✔️ 回调绑定

被绑定了一个 MethodInterceptor(你的是 GenericReferenceProxy

但是它不会有

不包含

理由

❌ 实际方法实现

所有方法都是“假实现”,内部不执行实际业务逻辑

❌ 业务字段

除了拦截器内部引用的 GenericService 外,没有业务属性

❌ 真正执行代码

所有执行逻辑都在 intercept(...) 方法中完成

最后我们总结一下上述泛化调用实现的执行逻辑吧:

在系统初始化阶段,每一个远程 Dubbo 服务接口(如 com.xxx.UserService)都会通过 Dubbo 的 ReferenceConfig 配置方式完成“泛化引用”。具体而言,是通过 reference.setGeneric(true) 配置为泛化服务引用,并设置目标接口的全限定类名、版本号等属性。

这一步并不会立即发起远程连接,而是在后续调用 reference.get() 时,Dubbo 框架会与注册中心(如 ZooKeeper)建立连接,订阅该接口对应的服务提供者信息,初始化网络连接池,并生成一个可以通过 $invoke(...) 方法进行任意方法远程调用的代理对象 GenericService。 由于这个引用对象创建过程非常“重”(涉及网络连接、线程池初始化、负载均衡策略等),因此系统通过 ReferenceConfigCache 对每个引用进行缓存,确保 一个服务接口(interfaceName + version)只创建一次 GenericService 代理对象,整个系统全局共享使用

在项目的架构中,这一过程通过 GenericReferenceRegistry 统一封装和管理,系统会为每个远程接口对应的方法名(如 "sayHi""getUserInfo")注册一个 GenericReferenceProxyFactory,并缓存在 Map<String, GenericReferenceProxyFactory> 中。每个 Factory 本质上是一个“方法级别的代理工厂”,内部持有一个已缓存的 GenericService,用于后续实际发起远程调用。

每个方法的代理对象都是通过 Factory 的 newInstance(methodName) 方法动态创建的。其生成过程使用了 CGLIB 与 ASM 字节码工具,在运行时动态构建了一个实现两个接口的代理类:一是自定义的统一接口 IGenericReference,二是通过 ASM InterfaceMaker + Signature 机制为方法名 methodName 构建的虚拟接口(该接口中只有一个形如 String methodName(String json) 的方法,用于标识当前接口请求的方法签名)。

然而这个代理对象只是一个“空壳子”——它包含正确的方法签名,但方法体中并无真实逻辑。为了避免运行时报错(例如 NullPointerException),所有方法都在创建代理时绑定了统一的 MethodInterceptor,即 GenericReferenceProxy。这个拦截器中维护着两个关键字段:

  • GenericService genericService:远程服务代理对象(具有 $invoke() 能力)

  • String methodName:当前代理方法名,用于告诉泛化调用要执行的是哪个方法

当客户端(如 Controller)层接收到一个 HTTP 请求后,例如 /api/activity/sayHi,会先通过 methodName 查找到对应的 GenericReferenceProxyFactory,然后通过 factory.newInstance("sayHi") 创建出动态代理对象(空壳子)。随后,通过调用该代理对象的 sayHi(String json) 方法或 $invoke(String json) 方法,进入 CGLIB 的方法拦截逻辑。

此时,调用会被转发至 GenericReferenceProxy.intercept(...) 方法,在这里系统会:

  1. 获取当前方法的参数类型数组(目前统一为 String.class

  2. 将方法名 methodName、参数类型 String[]、入参值 args 封装为 Dubbo 泛化调用所需的三元组

  3. 调用 genericService.$invoke(...),发起一次真正的 RPC 远程调用

这一调用将由 Dubbo 完整处理包括:服务路由、负载均衡、序列化传输、远程执行等,最终将结果以 Java 对象返回,并由泛化框架以字符串(JSON)形式返回给上层调用方。

 系统启动时:
   Configuration.addGenericReference(app, interfaceName, methodName)
     └── GenericReferenceRegistry 注册远程服务引用(ReferenceConfig<GenericService>)
     └── 获取 GenericService 实例(缓存 get(reference))
     └── new GenericReferenceProxyFactory(genericService)
     └── 保存 methodName → factory 映射
 ​
 HTTP 请求到来:
   Controller 调用 Configuration.getGenericReference(methodName)
     └── Factory.newInstance(methodName) 动态生成代理对象(实现 IGenericReference)
     └── 调用 proxy.$invoke(jsonParam)
       → 被 GenericReferenceProxy.intercept(...) 拦截
       → 内部调用 genericService.$invoke(methodName, paramTypes, args)
       → 返回远程方法结果
 ​

会话修改

在上面我们已经完成了第2章的核心内容代理RPC泛化调用,接下来我们就要将其与第1章的HTTP请求会话协议处理合并。在上一章中,我们的测试创建一个服务端只有一个SessionServer,没有一个统一的对外服务端创建接口,本节我们就将其完善(这部分其实并不是本节内容的重点,但是它又是项目完整性完整性方法不可缺少的一环)

首先创建一个打开会话的接口类

IGenericReferenceSessionFactory.java

 /**
  * @program: api-gateway-core
  * @ClassName IGenericReferenceSessionFactory
  * @description: 泛化调用会话工厂接口
  * @author: zs宝
  * @create: 2025-07-29 20:40
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.session;
 ​
 import io.netty.channel.Channel;
 ​
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 ​
 public interface IGenericReferenceSessionFactory {
     Future<Channel> openSession() throws ExecutionException, InterruptedException;
 }
 ​

实现这个接口

GenericReferenceSessionFactory.java

发现这个类根我们上一节的测试类基本相同,只是结合泛化调用进行了一个封装

 /**
  * @program: api-gateway-core
  * @ClassName GenericReferenceSessionFactory
  * @description:泛化调用会话工厂接口的默认实现
  * @author: zs宝
  * @create: 2025-07-29 20:41
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.session.defaults;
 ​
 import com.zshunbao.gateway.session.Configuration;
 import com.zshunbao.gateway.session.IGenericReferenceSessionFactory;
 import com.zshunbao.gateway.session.SessionServer;
 import io.netty.channel.Channel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 ​
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 ​
 public class GenericReferenceSessionFactory implements IGenericReferenceSessionFactory {
     private final Logger logger = LoggerFactory.getLogger(GenericReferenceSessionFactory.class);
 ​
     private final Configuration configuration;
 ​
     public GenericReferenceSessionFactory(Configuration configuration) {
         this.configuration = configuration;
     }
 ​
     //这里的这个方法主要是应对每一个来自不同客户端的请求建立一个不同的会话
     //这个与我们第1章的测试代码几乎相同,就是建立启动服务器建立会话,只是多了对于泛化调用配置的引入
     @Override
     public Future<Channel> openSession() throws ExecutionException, InterruptedException {
         SessionServer server=new SessionServer(configuration);
         Future<Channel> future = Executors.newFixedThreadPool(2).submit(server);
         Channel channel = future.get();
         if(null==channel){
             throw new RuntimeException("netty server start error channel is null");
         }
         while (!channel.isActive()){
             logger.info("netty server gateway start Ing ...");
             Thread.sleep(500);
         }
         logger.info("netty server gateway start Done! {}", channel.localAddress());
 ​
 ​
         return future;
     }
 }
 ​

在这其中,由于对于泛化调用有配置项,因此之前的一些内容需要将其添加进去进行修改,但是改动其实并不大

SessionServer.java

 /**
  * @program: api-gateway-core
  * @ClassName SessionServer
  * @description: 网关会话服务端
  * @author: zs宝
  * @create: 2025-07-25 16:48
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.session;
 ​
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelOption;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.channel.Channel;
 import io.netty.channel.socket.nio.NioServerSocketChannel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 ​
 ​
 import java.net.InetSocketAddress;
 import java.util.concurrent.Callable;
 //实现异步调用接口,使整个会话服务端的过程异步建立
 public class SessionServer implements Callable<Channel> {
     private final Logger logger = LoggerFactory.getLogger(SessionServer.class);
     private final EventLoopGroup boss=new NioEventLoopGroup(1);
     private final EventLoopGroup worker=new NioEventLoopGroup();
     private Channel channel;
 ​
     private final Configuration configuration;
     public SessionServer(Configuration configuration) {
         this.configuration = configuration;
     }
 ​
     @Override
     public Channel call() throws Exception {
         ChannelFuture channelFuture=null;
         try {
             ServerBootstrap b=new ServerBootstrap();
             b.group(boss,worker)
                     .channel(NioServerSocketChannel.class)
                     //设置 TCP 最大连接等待队列
                     .option(ChannelOption.SO_BACKLOG,128)
                     .childHandler(new SessionChannelInitializer(configuration));
             //InetSocketAddress 是 Java 用于表示“IP + 端口”的地址对象
             //syncUninterruptibly() 会阻塞直到启动完成(不抛出异常)
             channelFuture = b.bind(new InetSocketAddress(7397)).syncUninterruptibly();
             this.channel=channelFuture.channel();
         }catch (Exception e){
             logger.error("socket server start error.", e);
         }finally {
             if(null!=channelFuture && channelFuture.isSuccess()){
                 logger.info("socket server start done.");
             }else{
                 logger.error("socket server start error.");
             }
         }
         return channel;
     }
 }
 ​

SessionChannelInitializer.java

 /**
  * @program: api-gateway-core
  * @ClassName SessionChannelInitializer
  * @description:自定义netty服务端链接的childHandler的初始化工具
  * @author: zs宝
  * @create: 2025-07-25 16:56
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.session;
 ​
 ​
 import com.zshunbao.gateway.session.handlers.SessionServerHandler;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelPipeline;
 import io.netty.channel.socket.SocketChannel;
 import io.netty.handler.codec.http.HttpObjectAggregator;
 import io.netty.handler.codec.http.HttpRequestDecoder;
 import io.netty.handler.codec.http.HttpResponseEncoder;
 ​
 public class SessionChannelInitializer extends ChannelInitializer<SocketChannel>{
 ​
     private final Configuration configuration;
 ​
     public SessionChannelInitializer(Configuration configuration) {
         this.configuration = configuration;
     }
 ​
     @Override
     protected void initChannel(SocketChannel channel) throws Exception {
         //得到处理的流水线
         ChannelPipeline pipeline = channel.pipeline();
         pipeline.addLast(new HttpRequestDecoder());
         pipeline.addLast(new HttpResponseEncoder());
         pipeline.addLast(new HttpObjectAggregator(1024*1024));
         pipeline.addLast(new SessionServerHandler(configuration));
     }
 }
 ​

SessionServerHandler.java

 /**
  * @program: api-gateway-core
  * @ClassName SessionServerHandler
  * @description:
  * @author: zs宝
  * @create: 2025-07-25 17:09
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.session.handlers;
 ​
 import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.alibaba.fastjson.JSON;
 import com.zshunbao.gateway.bind.IGenericReference;
 import com.zshunbao.gateway.session.BaseHandler;
 import com.zshunbao.gateway.session.Configuration;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.http.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 public class SessionServerHandler extends BaseHandler<FullHttpRequest> {
     private final Logger logger = LoggerFactory.getLogger(SessionServerHandler.class);
 ​
     private final Configuration configuration;
 ​
     public SessionServerHandler(Configuration configuration) {
         this.configuration = configuration;
     }
 ​
     /**
      * 网关接受到外部请求的会话信息处理,这里主要是向外部返回一部分信息,表示网关接收到了
      * @param ctx
      * @param channel
      * @param request
      */
     @Override
     protected void session(ChannelHandlerContext ctx, Channel channel, FullHttpRequest request) {
         logger.info("网关接收请求 uri:{} method:{}", request.uri(), request.method());
 ​
         //返回信息控制:简单处理
         String methodName = request.getUri().substring(1);
         if(methodName.equals("favicon.ico")){
             return;
         }
 ​
         //返回信息处理
         DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
 ​
         //服务泛化调用
         IGenericReference reference = configuration.getGenericReference("sayHi");
         String result = reference.$invoke("test") + " " + System.currentTimeMillis();
 ​
         //设置响应体的内容
         response.content().writeBytes(JSON.toJSONBytes(result, SerializerFeature.PrettyFormat));
         //接下来进行响应头信息的设置
         //获得响应头
         HttpHeaders headers = response.headers();
         //配置响应体类型
         headers.add(HttpHeaderNames.CONTENT_TYPE,HttpHeaderValues.APPLICATION_JSON+"; charset=UTF-8");
         //配置响应体的长度
         headers.add(HttpHeaderNames.CONTENT_LENGTH,response.content().readableBytes());
         //配置响应的持久链接
         headers.add(HttpHeaderNames.CONNECTION,HttpHeaderValues.KEEP_ALIVE);
         //配置跨域访问
         headers.add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN,"*");
         headers.add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS,"*");
         headers.add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS,"GET, POST, PUT, DELETE");
         headers.add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS,"true");
 ​
         //最后向客户端通道返回网关的响应
         channel.writeAndFlush(response);
     }
 }

最后由于不可能只有一个服务请求到达网关,配置也会有多个,因此会话的建立会有多个,这里我们提供一个统一的对外接口

GenericReferenceSessionFactoryBuilder

 /**
  * @program: api-gateway-core
  * @ClassName GenericReferenceSessionFactoryBuilder
  * @description:会话工厂建造类
  * @author: zs宝
  * @create: 2025-07-29 20:57
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.session;
 ​
 import com.zshunbao.gateway.session.defaults.GenericReferenceSessionFactory;
 import io.netty.channel.Channel;
 ​
 ​
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.function.Function;
 ​
 public class GenericReferenceSessionFactoryBuilder {
 ​
     public Future<Channel>build(Configuration configuration){
         GenericReferenceSessionFactory genericReferenceSessionFactory = new GenericReferenceSessionFactory(configuration);
         try {
             return genericReferenceSessionFactory.openSession();
         }catch (ExecutionException | InterruptedException e){
             throw new RuntimeException(e);
         }
     }
 }

测试

注意完整测试用例和项目代码请去小傅哥的知识星球,这里只是笔者自己的学习笔记,很多东西并不完整

本次测试由于要使用注册中心,因此这里我们要先创建一个zookeeper的容器(注意版本信息要与项目中使用的一致)

 version: '1.0'
 services:
   zookeeper:
     image: zookeeper:3.4.13
     container_name: zookeeper
     restart: always
     hostname: zoo1
     ports:
       - 2181:2181
     environment:
       ZOO_MY_ID: 1
       ZOO_SERVERS: server.1=zookeeper:2888:3888
     networks:
       - my-network
 ​
 networks:
   my-network:
     driver: bridge
 ​

这里有一个服务

 @Service(version = "1.0.0")
 public class ActivityBooth implements IActivityBooth {
 ​
     @Override
 ​
     public String sayHi(String str) {
         return "hi " + str + " by api-gateway-test-provider";
     }
 ​
     @Override
 ​
     public String insert(XReq req) {
         return "hi " + JSON.toJSONString(req) + " by api-gateway-test-provider";
     }
 ​
     @Override
 ​
     public String test(String str, XReq req) {
         return "hi " + str + JSON.toJSONString(req) + " by api-gateway-test-provider";
     }
 ​
 }
 ​

配置application.yml

 server:
   port: 8082
 ​
 dubbo:
   application:
     name: api-gateway-test
     version: 1.0.0
   registry:
     #address: N/A 泛化调用不能使用此方式
     address: zookeeper://127.0.0.1:2181
   protocol:
     name: dubbo
     port: 20881
   scan:
     base-packages: cn.bugstack.gateway.rpc
 ​
 ​

启动服务

启动后,我们也启动我们的网关项目测试用例

 /**
  * @program: api-gateway-core
  * @ClassName ApiTest
  * @description:RPC泛化调用测试
  * @author: zs宝
  * @create: 2025-07-25 17:32
  * @Version 1.0
  **/
 ​
 package com.zshunbao.gateway.test;
 ​
 import com.zshunbao.gateway.session.Configuration;
 import com.zshunbao.gateway.session.GenericReferenceSessionFactoryBuilder;
 import com.zshunbao.gateway.session.SessionServer;
 import io.netty.channel.Channel;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 ​
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 ​
 public class ApiTest {
 ​
     private final Logger logger = LoggerFactory.getLogger(ApiTest.class);
 ​
     /**
      * 测试:http://localhost:7397/sayHi
      */
     @Test
     public void test_GenericReference() throws InterruptedException, ExecutionException {
         Configuration configuration = new Configuration();
         configuration.addGenericReference("api-gateway-test", "cn.bugstack.gateway.rpc.IActivityBooth", "sayHi");
 ​
         GenericReferenceSessionFactoryBuilder builder = new GenericReferenceSessionFactoryBuilder();
         Future<Channel> future = builder.build(configuration);
 ​
         logger.info("服务启动完成 {}", future.get().id());
 ​
         Thread.sleep(Long.MAX_VALUE);
     }
 ​
 }
 ​

然后浏览器访问

image-2spk.png

参考文献