Java 从零开始手写 一 Reflect 反射实现通用调用之客户端

开发 后端
上一篇我们介绍了,如何实现基于反射的通用服务端。这一节我们来一起学习下如何实现通用客户端。

上一篇我们介绍了,如何实现基于反射的通用服务端。

这一节我们来一起学习下如何实现通用客户端。

因为内容较多,所以拆分为 2 个部分。

基本思路

所有的方法调用,基于反射进行相关处理实现。

[[431417]]

核心类

为了便于拓展,我们把核心类调整如下:

  1. package com.github.houbb.rpc.client.core; 
  2.  
  3.  
  4. import com.github.houbb.heaven.annotation.ThreadSafe; 
  5. import com.github.houbb.log.integration.core.Log; 
  6. import com.github.houbb.log.integration.core.LogFactory; 
  7. import com.github.houbb.rpc.client.core.context.RpcClientContext; 
  8. import com.github.houbb.rpc.client.handler.RpcClientHandler; 
  9. import com.github.houbb.rpc.common.constant.RpcConstant; 
  10. import io.netty.bootstrap.Bootstrap; 
  11. import io.netty.channel.*; 
  12. import io.netty.channel.nio.NioEventLoopGroup; 
  13. import io.netty.channel.socket.nio.NioSocketChannel; 
  14. import io.netty.handler.codec.serialization.ClassResolvers; 
  15. import io.netty.handler.codec.serialization.ObjectDecoder; 
  16. import io.netty.handler.codec.serialization.ObjectEncoder; 
  17. import io.netty.handler.logging.LogLevel; 
  18. import io.netty.handler.logging.LoggingHandler; 
  19.  
  20.  
  21. /** 
  22.  * <p> rpc 客户端 </p> 
  23.  * 
  24.  * <pre> Created: 2019/10/16 11:21 下午  </pre> 
  25.  * <pre> Project: rpc  </pre> 
  26.  * 
  27.  * @author houbinbin 
  28.  * @since 0.0.2 
  29.  */ 
  30. @ThreadSafe 
  31. public class RpcClient { 
  32.  
  33.  
  34.     private static final Log log = LogFactory.getLog(RpcClient.class); 
  35.  
  36.  
  37.     /** 
  38.      * 地址信息 
  39.      * @since 0.0.6 
  40.      */ 
  41.     private final String address; 
  42.  
  43.  
  44.     /** 
  45.      * 监听端口号 
  46.      * @since 0.0.6 
  47.      */ 
  48.     private final int port; 
  49.  
  50.  
  51.     /** 
  52.      * 客户端处理 handler 
  53.      * 作用:用于获取请求信息 
  54.      * @since 0.0.4 
  55.      */ 
  56.     private final ChannelHandler channelHandler; 
  57.  
  58.  
  59.     public RpcClient(final RpcClientContext clientContext) { 
  60.         this.address = clientContext.address(); 
  61.         this.port = clientContext.port(); 
  62.         this.channelHandler = clientContext.channelHandler(); 
  63.     } 
  64.  
  65.  
  66.     /** 
  67.      * 进行连接 
  68.      * @since 0.0.6 
  69.      */ 
  70.     public ChannelFuture connect() { 
  71.         // 启动服务端 
  72.         log.info("RPC 服务开始启动客户端"); 
  73.  
  74.  
  75.         EventLoopGroup workerGroup = new NioEventLoopGroup(); 
  76.  
  77.  
  78.         /** 
  79.          * channel future 信息 
  80.          * 作用:用于写入请求信息 
  81.          * @since 0.0.6 
  82.          */ 
  83.         ChannelFuture channelFuture; 
  84.         try { 
  85.             Bootstrap bootstrap = new Bootstrap(); 
  86.             channelFuture = bootstrap.group(workerGroup) 
  87.                     .channel(NioSocketChannel.class) 
  88.                     .option(ChannelOption.SO_KEEPALIVE, true
  89.                     .handler(new ChannelInitializer<Channel>(){ 
  90.                         @Override 
  91.                         protected void initChannel(Channel ch) throws Exception { 
  92.                             ch.pipeline() 
  93.                                     // 解码 bytes=>resp 
  94.                                     .addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null))) 
  95.                                     // request=>bytes 
  96.                                     .addLast(new ObjectEncoder()) 
  97.                                     // 日志输出 
  98.                                     .addLast(new LoggingHandler(LogLevel.INFO)) 
  99.                                     .addLast(channelHandler); 
  100.                         } 
  101.                     }) 
  102.                     .connect(address, port) 
  103.                     .syncUninterruptibly(); 
  104.             log.info("RPC 服务启动客户端完成,监听地址 {}:{}", address, port); 
  105.         } catch (Exception e) { 
  106.             log.error("RPC 客户端遇到异常", e); 
  107.             throw new RuntimeException(e); 
  108.         } 
  109.         // 不要关闭线程池!!! 
  110.  
  111.  
  112.         return channelFuture; 
  113.     } 
  114.  
  115.  

可以灵活指定对应的服务端地址、端口信息。

ChannelHandler 作为处理参数传入。

ObjectDecoder、ObjectEncoder、LoggingHandler 都和服务端类似,是 netty 的内置实现。

RpcClientHandler

客户端的 handler 实现如下:

  1. /* 
  2.  * Copyright (c)  2019. houbinbin Inc. 
  3.  * rpc All rights reserved. 
  4.  */ 
  5.  
  6.  
  7. package com.github.houbb.rpc.client.handler; 
  8.  
  9.  
  10. import com.github.houbb.log.integration.core.Log; 
  11. import com.github.houbb.log.integration.core.LogFactory; 
  12. import com.github.houbb.rpc.client.core.RpcClient; 
  13. import com.github.houbb.rpc.client.invoke.InvokeService; 
  14. import com.github.houbb.rpc.common.rpc.domain.RpcResponse; 
  15. import io.netty.channel.ChannelHandlerContext; 
  16. import io.netty.channel.SimpleChannelInboundHandler; 
  17.  
  18.  
  19. /** 
  20.  * <p> 客户端处理类 </p> 
  21.  * 
  22.  * <pre> Created: 2019/10/16 11:30 下午  </pre> 
  23.  * <pre> Project: rpc  </pre> 
  24.  * 
  25.  * @author houbinbin 
  26.  * @since 0.0.2 
  27.  */ 
  28. public class RpcClientHandler extends SimpleChannelInboundHandler { 
  29.  
  30.  
  31.     private static final Log log = LogFactory.getLog(RpcClient.class); 
  32.  
  33.  
  34.     /** 
  35.      * 调用服务管理类 
  36.      * 
  37.      * @since 0.0.6 
  38.      */ 
  39.     private final InvokeService invokeService; 
  40.  
  41.  
  42.     public RpcClientHandler(InvokeService invokeService) { 
  43.         this.invokeService = invokeService; 
  44.     } 
  45.  
  46.  
  47.     @Override 
  48.     protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 
  49.         RpcResponse rpcResponse = (RpcResponse)msg; 
  50.         invokeService.addResponse(rpcResponse.seqId(), rpcResponse); 
  51.         log.info("[Client] response is :{}", rpcResponse); 
  52.     } 
  53.  
  54.  
  55.     @Override 
  56.     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 
  57.         // 每次用完要关闭,不然拿不到response,我也不知道为啥(目测得了解netty才行) 
  58.         // 个人理解:如果不关闭,则永远会被阻塞。 
  59.         ctx.flush(); 
  60.         ctx.close(); 
  61.     } 
  62.  
  63.  
  64.     @Override 
  65.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
  66.         cause.printStackTrace(); 
  67.         ctx.close(); 
  68.     } 

 只有 channelRead0 做了调整,基于 InvokeService 对结果进行处理。

InvokeService

接口

  1. package com.github.houbb.rpc.client.invoke; 
  2.  
  3.  
  4. import com.github.houbb.rpc.common.rpc.domain.RpcResponse; 
  5.  
  6.  
  7. /** 
  8.  * 调用服务接口 
  9.  * @author binbin.hou 
  10.  * @since 0.0.6 
  11.  */ 
  12. public interface InvokeService { 
  13.  
  14.  
  15.     /** 
  16.      * 添加请求信息 
  17.      * @param seqId 序列号 
  18.      * @return this 
  19.      * @since 0.0.6 
  20.      */ 
  21.     InvokeService addRequest(final String seqId); 
  22.  
  23.  
  24.     /** 
  25.      * 放入结果 
  26.      * @param seqId 唯一标识 
  27.      * @param rpcResponse 响应结果 
  28.      * @return this 
  29.      * @since 0.0.6 
  30.      */ 
  31.     InvokeService addResponse(final String seqId, final RpcResponse rpcResponse); 
  32.  
  33.  
  34.     /** 
  35.      * 获取标志信息对应的结果 
  36.      * @param seqId 序列号 
  37.      * @return 结果 
  38.      * @since 0.0.6 
  39.      */ 
  40.     RpcResponse getResponse(final String seqId); 
  41.  
  42.  

 主要是对入参、出参的设置,以及出参的获取。

实现

  1. package com.github.houbb.rpc.client.invoke.impl; 
  2.  
  3.  
  4. import com.github.houbb.heaven.util.guava.Guavas; 
  5. import com.github.houbb.heaven.util.lang.ObjectUtil; 
  6. import com.github.houbb.log.integration.core.Log; 
  7. import com.github.houbb.log.integration.core.LogFactory; 
  8. import com.github.houbb.rpc.client.core.RpcClient; 
  9. import com.github.houbb.rpc.client.invoke.InvokeService; 
  10. import com.github.houbb.rpc.common.exception.RpcRuntimeException; 
  11. import com.github.houbb.rpc.common.rpc.domain.RpcResponse; 
  12.  
  13.  
  14. import java.util.Set
  15. import java.util.concurrent.ConcurrentHashMap; 
  16.  
  17.  
  18. /** 
  19.  * 调用服务接口 
  20.  * @author binbin.hou 
  21.  * @since 0.0.6 
  22.  */ 
  23. public class DefaultInvokeService implements InvokeService { 
  24.  
  25.  
  26.     private static final Log LOG = LogFactory.getLog(DefaultInvokeService.class); 
  27.  
  28.  
  29.     /** 
  30.      * 请求序列号集合 
  31.      * (1)这里后期如果要添加超时检测,可以添加对应的超时时间。 
  32.      * 可以把这里调整为 map 
  33.      * @since 0.0.6 
  34.      */ 
  35.     private final Set<String> requestSet; 
  36.  
  37.  
  38.     /** 
  39.      * 响应结果 
  40.      * @since 0.0.6 
  41.      */ 
  42.     private final ConcurrentHashMap<String, RpcResponse> responseMap; 
  43.  
  44.  
  45.     public DefaultInvokeService() { 
  46.         requestSet = Guavas.newHashSet(); 
  47.         responseMap = new ConcurrentHashMap<>(); 
  48.     } 
  49.  
  50.  
  51.     @Override 
  52.     public InvokeService addRequest(String seqId) { 
  53.         LOG.info("[Client] start add request for seqId: {}", seqId); 
  54.         requestSet.add(seqId); 
  55.         return this; 
  56.     } 
  57.  
  58.  
  59.     @Override 
  60.     public InvokeService addResponse(String seqId, RpcResponse rpcResponse) { 
  61.         // 这里放入之前,可以添加判断。 
  62.         // 如果 seqId 必须处理请求集合中,才允许放入。或者直接忽略丢弃。 
  63.         LOG.info("[Client] 获取结果信息,seq: {}, rpcResponse: {}", seqId, rpcResponse); 
  64.         responseMap.putIfAbsent(seqId, rpcResponse); 
  65.  
  66.  
  67.         // 通知所有等待方 
  68.         LOG.info("[Client] seq 信息已经放入,通知所有等待方", seqId); 
  69.  
  70.  
  71.         synchronized (this) { 
  72.             this.notifyAll(); 
  73.         } 
  74.  
  75.  
  76.         return this; 
  77.     } 
  78.  
  79.  
  80.     @Override 
  81.     public RpcResponse getResponse(String seqId) { 
  82.         try { 
  83.             RpcResponse rpcResponse = this.responseMap.get(seqId); 
  84.             if(ObjectUtil.isNotNull(rpcResponse)) { 
  85.                 LOG.info("[Client] seq {} 对应结果已经获取: {}", seqId, rpcResponse); 
  86.                 return rpcResponse; 
  87.             } 
  88.  
  89.  
  90.             // 进入等待 
  91.             while (rpcResponse == null) { 
  92.                 LOG.info("[Client] seq {} 对应结果为空,进入等待", seqId); 
  93.                 // 同步等待锁 
  94.                 synchronized (this) { 
  95.                     this.wait(); 
  96.                 } 
  97.  
  98.  
  99.                 rpcResponse = this.responseMap.get(seqId); 
  100.                 LOG.info("[Client] seq {} 对应结果已经获取: {}", seqId, rpcResponse); 
  101.             } 
  102.  
  103.  
  104.             return rpcResponse; 
  105.         } catch (InterruptedException e) { 
  106.             throw new RpcRuntimeException(e); 
  107.         } 
  108.     } 

 使用 requestSet 存储对应的请求入参。

使用 responseMap 存储对应的请求出参,在获取的时候通过同步 while 循环等待,获取结果。

此处,通过 notifyAll() 和 wait() 进行等待和唤醒。

ReferenceConfig-服务端配置

说明

我们想调用服务端,首先肯定要定义好要调用的对象。

ReferenceConfig 就是要告诉 rpc 框架,调用的服务端信息。

接口

  1. package com.github.houbb.rpc.client.config.reference; 
  2.  
  3.  
  4. import com.github.houbb.rpc.common.config.component.RpcAddress; 
  5.  
  6.  
  7. import java.util.List; 
  8.  
  9.  
  10. /** 
  11.  * 引用配置类 
  12.  * 
  13.  * 后期配置: 
  14.  * (1)timeout 调用超时时间 
  15.  * (2)version 服务版本处理 
  16.  * (3)callType 调用方式 oneWay/sync/async 
  17.  * (4)check 是否必须要求服务启动。 
  18.  * 
  19.  * spi: 
  20.  * (1)codec 序列化方式 
  21.  * (2)netty 网络通讯架构 
  22.  * (3)load-balance 负载均衡 
  23.  * (4)失败策略 fail-over/fail-fast 
  24.  * 
  25.  * filter: 
  26.  * (1)路由 
  27.  * (2)耗时统计 monitor 服务治理 
  28.  * 
  29.  * 优化思考: 
  30.  * (1)对于唯一的 serviceId,其实其 interface 是固定的,是否可以省去? 
  31.  * @author binbin.hou 
  32.  * @since 0.0.6 
  33.  * @param <T> 接口泛型 
  34.  */ 
  35. public interface ReferenceConfig<T> { 
  36.  
  37.  
  38.     /** 
  39.      * 设置服务标识 
  40.      * @param serviceId 服务标识 
  41.      * @return this 
  42.      * @since 0.0.6 
  43.      */ 
  44.     ReferenceConfig<T> serviceId(final String serviceId); 
  45.  
  46.  
  47.     /** 
  48.      * 服务唯一标识 
  49.      * @since 0.0.6 
  50.      */ 
  51.     String serviceId(); 
  52.  
  53.  
  54.     /** 
  55.      * 服务接口 
  56.      * @since 0.0.6 
  57.      * @return 接口信息 
  58.      */ 
  59.     Class<T> serviceInterface(); 
  60.  
  61.  
  62.     /** 
  63.      * 设置服务接口信息 
  64.      * @param serviceInterface 服务接口信息 
  65.      * @return this 
  66.      * @since 0.0.6 
  67.      */ 
  68.     ReferenceConfig<T> serviceInterface(final Class<T> serviceInterface); 
  69.  
  70.  
  71.     /** 
  72.      * 设置服务地址信息 
  73.      * (1)单个写法:ip:port:weight 
  74.      * (2)集群写法:ip1:port1:weight1,ip2:port2:weight2 
  75.      * 
  76.      * 其中 weight 权重可以不写,默认为1. 
  77.      * 
  78.      * @param addresses 地址列表信息 
  79.      * @return this 
  80.      * @since 0.0.6 
  81.      */ 
  82.     ReferenceConfig<T> addresses(final String addresses); 
  83.  
  84.  
  85.     /** 
  86.      * 获取对应的引用实现 
  87.      * @return 引用代理类 
  88.      * @since 0.0.6 
  89.      */ 
  90.     T reference(); 
  91.  
  92.  

实现

  1. package com.github.houbb.rpc.client.config.reference.impl; 
  2.  
  3.  
  4. import com.github.houbb.heaven.constant.PunctuationConst; 
  5. import com.github.houbb.heaven.util.common.ArgUtil; 
  6. import com.github.houbb.heaven.util.guava.Guavas; 
  7. import com.github.houbb.heaven.util.lang.NumUtil; 
  8. import com.github.houbb.rpc.client.config.reference.ReferenceConfig; 
  9. import com.github.houbb.rpc.client.core.RpcClient; 
  10. import com.github.houbb.rpc.client.core.context.impl.DefaultRpcClientContext; 
  11. import com.github.houbb.rpc.client.handler.RpcClientHandler; 
  12. import com.github.houbb.rpc.client.invoke.InvokeService; 
  13. import com.github.houbb.rpc.client.invoke.impl.DefaultInvokeService; 
  14. import com.github.houbb.rpc.client.proxy.ReferenceProxy; 
  15. import com.github.houbb.rpc.client.proxy.context.ProxyContext; 
  16. import com.github.houbb.rpc.client.proxy.context.impl.DefaultProxyContext; 
  17. import com.github.houbb.rpc.common.config.component.RpcAddress; 
  18. import io.netty.channel.ChannelFuture; 
  19. import io.netty.channel.ChannelHandler; 
  20.  
  21.  
  22. import java.util.List; 
  23.  
  24.  
  25. /** 
  26.  * 引用配置类默认实现 
  27.  * 
  28.  * @author binbin.hou 
  29.  * @since 0.0.6 
  30.  * @param <T> 接口泛型 
  31.  */ 
  32. public class DefaultReferenceConfig<T> implements ReferenceConfig<T> { 
  33.  
  34.  
  35.     /** 
  36.      * 服务唯一标识 
  37.      * @since 0.0.6 
  38.      */ 
  39.     private String serviceId; 
  40.  
  41.  
  42.     /** 
  43.      * 服务接口 
  44.      * @since 0.0.6 
  45.      */ 
  46.     private Class<T> serviceInterface; 
  47.  
  48.  
  49.     /** 
  50.      * 服务地址信息 
  51.      * (1)如果不为空,则直接根据地址获取 
  52.      * (2)如果为空,则采用自动发现的方式 
  53.      * 
  54.      * TODO: 这里调整为 set 更加合理。 
  55.      * 
  56.      * 如果为 subscribe 可以自动发现,然后填充这个字段信息。 
  57.      * @since 0.0.6 
  58.      */ 
  59.     private List<RpcAddress> rpcAddresses; 
  60.  
  61.  
  62.     /** 
  63.      * 用于写入信息 
  64.      * (1)client 连接 server 端的 channel future 
  65.      * (2)后期进行 Load-balance 路由等操作。可以放在这里执行。 
  66.      * @since 0.0.6 
  67.      */ 
  68.     private List<ChannelFuture> channelFutures; 
  69.  
  70.  
  71.     /** 
  72.      * 客户端处理信息 
  73.      * @since 0.0.6 
  74.      */ 
  75.     @Deprecated 
  76.     private RpcClientHandler channelHandler; 
  77.  
  78.  
  79.     /** 
  80.      * 调用服务管理类 
  81.      * @since 0.0.6 
  82.      */ 
  83.     private InvokeService invokeService; 
  84.  
  85.  
  86.     public DefaultReferenceConfig() { 
  87.         // 初始化信息 
  88.         this.rpcAddresses = Guavas.newArrayList(); 
  89.         this.channelFutures = Guavas.newArrayList(); 
  90.         this.invokeService = new DefaultInvokeService(); 
  91.     } 
  92.  
  93.  
  94.     @Override 
  95.     public String serviceId() { 
  96.         return serviceId; 
  97.     } 
  98.  
  99.  
  100.     @Override 
  101.     public DefaultReferenceConfig<T> serviceId(String serviceId) { 
  102.         this.serviceId = serviceId; 
  103.         return this; 
  104.     } 
  105.  
  106.  
  107.     @Override 
  108.     public Class<T> serviceInterface() { 
  109.         return serviceInterface; 
  110.     } 
  111.  
  112.  
  113.     @Override 
  114.     public DefaultReferenceConfig<T> serviceInterface(Class<T> serviceInterface) { 
  115.         this.serviceInterface = serviceInterface; 
  116.         return this; 
  117.     } 
  118.  
  119.  
  120.     @Override 
  121.     public ReferenceConfig<T> addresses(String addresses) { 
  122.         ArgUtil.notEmpty(addresses, "addresses"); 
  123.  
  124.  
  125.         String[] addressArray = addresses.split(PunctuationConst.COMMA); 
  126.         ArgUtil.notEmpty(addressArray, "addresses"); 
  127.  
  128.  
  129.         for(String address : addressArray) { 
  130.             String[] addressSplits = address.split(PunctuationConst.COLON); 
  131.             if(addressSplits.length < 2) { 
  132.                 throw new IllegalArgumentException("Address must be has ip and port, like 127.0.0.1:9527"); 
  133.             } 
  134.             String ip = addressSplits[0]; 
  135.             int port = NumUtil.toIntegerThrows(addressSplits[1]); 
  136.             // 包含权重信息 
  137.             int weight = 1; 
  138.             if(addressSplits.length >= 3) { 
  139.                 weight = NumUtil.toInteger(addressSplits[2], 1); 
  140.             } 
  141.  
  142.  
  143.             RpcAddress rpcAddress = new RpcAddress(ip, port, weight); 
  144.             this.rpcAddresses.add(rpcAddress); 
  145.         } 
  146.  
  147.  
  148.         return this; 
  149.     } 
  150.  
  151.  
  152.     /** 
  153.      * 获取对应的引用实现 
  154.      * (1)处理所有的反射代理信息-方法可以抽离,启动各自独立即可。 
  155.      * (2)启动对应的长连接 
  156.      * @return 引用代理类 
  157.      * @since 0.0.6 
  158.      */ 
  159.     @Override 
  160.     public T reference() { 
  161.         // 1. 启动 client 端到 server 端的连接信息 
  162.         // 1.1 为了提升性能,可以将所有的 client=>server 的连接都调整为一个 thread。 
  163.         // 1.2 初期为了简单,直接使用同步循环的方式。 
  164.         // 创建 handler 
  165.         // 循环连接 
  166.         for(RpcAddress rpcAddress : rpcAddresses) { 
  167.             final ChannelHandler channelHandler = new RpcClientHandler(invokeService); 
  168.             final DefaultRpcClientContext context = new DefaultRpcClientContext(); 
  169.             context.address(rpcAddress.address()).port(rpcAddress.port()).channelHandler(channelHandler); 
  170.             ChannelFuture channelFuture = new RpcClient(context).connect(); 
  171.             // 循环同步等待 
  172.             // 如果出现异常,直接中断?捕获异常继续进行?? 
  173.             channelFutures.add(channelFuture); 
  174.         } 
  175.  
  176.  
  177.         // 2. 接口动态代理 
  178.         ProxyContext<T> proxyContext = buildReferenceProxyContext(); 
  179.         return ReferenceProxy.newProxyInstance(proxyContext); 
  180.     } 
  181.  
  182.  
  183.     /** 
  184.      * 构建调用上下文 
  185.      * @return 引用代理上下文 
  186.      * @since 0.0.6 
  187.      */ 
  188.     private ProxyContext<T> buildReferenceProxyContext() { 
  189.         DefaultProxyContext<T> proxyContext = new DefaultProxyContext<>(); 
  190.         proxyContext.serviceId(this.serviceId); 
  191.         proxyContext.serviceInterface(this.serviceInterface); 
  192.         proxyContext.channelFutures(this.channelFutures); 
  193.         proxyContext.invokeService(this.invokeService); 
  194.         return proxyContext; 
  195.     } 
  196.  
  197.  

 这里主要根据指定的服务端信息,初始化对应的代理实现。

这里还可以拓展指定权重,便于后期负载均衡拓展,本期暂时不做实现。

ReferenceProxy

说明

所有的 rpc 调用,客户端只有服务端的接口。

那么,怎么才能和调用本地方法一样调用远程方法呢?

答案就是动态代理。

实现

实现如下:

  1. package com.github.houbb.rpc.client.proxy; 
  2.  
  3.  
  4. import com.github.houbb.heaven.util.lang.ObjectUtil; 
  5. import com.github.houbb.heaven.util.lang.reflect.ReflectMethodUtil; 
  6. import com.github.houbb.log.integration.core.Log; 
  7. import com.github.houbb.log.integration.core.LogFactory; 
  8. import com.github.houbb.rpc.client.proxy.context.ProxyContext; 
  9. import com.github.houbb.rpc.common.rpc.domain.RpcResponse; 
  10. import com.github.houbb.rpc.common.rpc.domain.impl.DefaultRpcRequest; 
  11. import com.github.houbb.rpc.common.support.id.impl.Uuid; 
  12. import com.github.houbb.rpc.common.support.time.impl.DefaultSystemTime; 
  13. import io.netty.channel.Channel; 
  14.  
  15.  
  16. import java.lang.reflect.InvocationHandler; 
  17. import java.lang.reflect.Method; 
  18. import java.lang.reflect.Proxy; 
  19.  
  20.  
  21. /** 
  22.  * 参考:https://blog.csdn.net/u012240455/article/details/79210250 
  23.  * 
  24.  * (1)方法执行并不需要一定要有实现类。 
  25.  * (2)直接根据反射即可处理相关信息。 
  26.  * (3)rpc 是一种强制根据接口进行编程的实现方式。 
  27.  * @author binbin.hou 
  28.  * @since 0.0.6 
  29.  */ 
  30. public class ReferenceProxy<T> implements InvocationHandler { 
  31.  
  32.  
  33.     private static final Log LOG = LogFactory.getLog(ReferenceProxy.class); 
  34.  
  35.  
  36.     /** 
  37.      * 服务标识 
  38.      * @since 0.0.6 
  39.      */ 
  40.     private final ProxyContext<T> proxyContext; 
  41.  
  42.  
  43.     /** 
  44.      * 暂时私有化该构造器 
  45.      * @param proxyContext 代理上下文 
  46.      * @since 0.0.6 
  47.      */ 
  48.     private ReferenceProxy(ProxyContext<T> proxyContext) { 
  49.         this.proxyContext = proxyContext; 
  50.     } 
  51.  
  52.  
  53.     /** 
  54.      * 反射调用 
  55.      * @param proxy 代理 
  56.      * @param method 方法 
  57.      * @param args 参数 
  58.      * @return 结果 
  59.      * @throws Throwable 异常 
  60.      * @since 0.0.6 
  61.      * @see Method#getGenericSignature() 通用标识,可以根据这个来优化代码。 
  62.      */ 
  63.     @Override 
  64.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  65.         // 反射信息处理成为 rpcRequest 
  66.         final String seqId = Uuid.getInstance().id(); 
  67.         final long createTime = DefaultSystemTime.getInstance().time(); 
  68.         DefaultRpcRequest rpcRequest = new DefaultRpcRequest(); 
  69.         rpcRequest.serviceId(proxyContext.serviceId()); 
  70.         rpcRequest.seqId(seqId); 
  71.         rpcRequest.createTime(createTime); 
  72.         rpcRequest.paramValues(args); 
  73.         rpcRequest.paramTypeNames(ReflectMethodUtil.getParamTypeNames(method)); 
  74.         rpcRequest.methodName(method.getName()); 
  75.  
  76.  
  77.         // 调用远程 
  78.         LOG.info("[Client] start call remote with request: {}", rpcRequest); 
  79.         proxyContext.invokeService().addRequest(seqId); 
  80.  
  81.  
  82.         // 这里使用 load-balance 进行选择 channel 写入。 
  83.         final Channel channel = getChannel(); 
  84.         LOG.info("[Client] start call channel id: {}", channel.id().asLongText()); 
  85.  
  86.  
  87.         // 对于信息的写入,实际上有着严格的要求。 
  88.         // writeAndFlush 实际是一个异步的操作,直接使用 sync() 可以看到异常信息。 
  89.         // 支持的必须是 ByteBuf 
  90.         channel.writeAndFlush(rpcRequest).sync(); 
  91.  
  92.  
  93.         // 循环获取结果 
  94.         // 通过 Loop+match  wait/notifyAll 来获取 
  95.         // 分布式根据 redis+queue+loop 
  96.         LOG.info("[Client] start get resp for seqId: {}", seqId); 
  97.         RpcResponse rpcResponse = proxyContext.invokeService().getResponse(seqId); 
  98.         LOG.info("[Client] start get resp for seqId: {}", seqId); 
  99.         Throwable error = rpcResponse.error(); 
  100.         if(ObjectUtil.isNotNull(error)) { 
  101.             throw error; 
  102.         } 
  103.         return rpcResponse.result(); 
  104.     } 
  105.  
  106.  
  107.     /** 
  108.      * 获取对应的 channel 
  109.      * (1)暂时使用写死的第一个 
  110.      * (2)后期这里需要调整,ChannelFuture 加上权重信息。 
  111.      * @return 对应的 channel 信息。 
  112.      * @since 0.0.6 
  113.      */ 
  114.     private Channel getChannel() { 
  115.         return proxyContext.channelFutures().get(0).channel(); 
  116.     } 
  117.  
  118.  
  119.     /** 
  120.      * 获取代理实例 
  121.      * (1)接口只是为了代理。 
  122.      * (2)实际调用中更加关心 的是 serviceId 
  123.      * @param proxyContext 代理上下文 
  124.      * @param <T> 泛型 
  125.      * @return 代理实例 
  126.      * @since 0.0.6 
  127.      */ 
  128.     @SuppressWarnings("unchecked"
  129.     public static <T> T newProxyInstance(ProxyContext<T> proxyContext) { 
  130.         final Class<T> interfaceClass = proxyContext.serviceInterface(); 
  131.         ClassLoader classLoader = interfaceClass.getClassLoader(); 
  132.         Class<?>[] interfaces = new Class[]{interfaceClass}; 
  133.         ReferenceProxy proxy = new ReferenceProxy(proxyContext); 
  134.         return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy); 
  135.     } 
  136.  
  137.  

客户端初始化 newProxyInstance 的就是创建的代理的过程。

客户端调用远程方法,实际上是调用 invoke 的过程。

(1)构建反射 invoke 请求信息,添加 reqId

(2)netty 远程调用服务端

(3)同步获取响应信息

测试

引入 maven

  1. <dependency> 
  2.     <groupId>com.github.houbb</groupId> 
  3.     <artifactId>rpc-client</artifactId> 
  4.     <version>0.0.6</version> 
  5. </dependency> 

测试代码

  1. public static void main(String[] args) { 
  2.     // 服务配置信息 
  3.     ReferenceConfig<CalculatorService> config = new DefaultReferenceConfig<CalculatorService>(); 
  4.     config.serviceId(ServiceIdConst.CALC); 
  5.     config.serviceInterface(CalculatorService.class); 
  6.     config.addresses("localhost:9527"); 
  7.  
  8.  
  9.     CalculatorService calculatorService = config.reference(); 
  10.     CalculateRequest request = new CalculateRequest(); 
  11.     request.setOne(10); 
  12.     request.setTwo(20); 
  13.  
  14.  
  15.     CalculateResponse response = calculatorService.sum(request); 
  16.     System.out.println(response); 

 测试日志:

  1. [DEBUG] [2021-10-05 14:16:17.534] [main] [c.g.h.l.i.c.LogFactory.setImplementation] - Logging initialized using 'class com.github.houbb.log.integration.adaptors.stdout.StdOutExImpl' adapter. 
  2. [INFO] [2021-10-05 14:16:17.625] [main] [c.g.h.r.c.c.RpcClient.connect] - RPC 服务开始启动客户端 
  3. ... 
  4. [INFO] [2021-10-05 14:16:19.328] [main] [c.g.h.r.c.c.RpcClient.connect] - RPC 服务启动客户端完成,监听地址 localhost:9527 
  5. [INFO] [2021-10-05 14:16:19.346] [main] [c.g.h.r.c.p.ReferenceProxy.invoke] - [Client] start call remote with request: DefaultRpcRequest{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', createTime=1633414579339, serviceId='calc', methodName='sum', paramTypeNames=[com.github.houbb.rpc.server.facade.model.CalculateRequest], paramValues=[CalculateRequest{one=10, two=20}]} 
  6. [INFO] [2021-10-05 14:16:19.347] [main] [c.g.h.r.c.i.i.DefaultInvokeService.addRequest] - [Client] start add request for seqId: a525c5a6196545f5a5241b2cdc2ec2c2 
  7. [INFO] [2021-10-05 14:16:19.348] [main] [c.g.h.r.c.p.ReferenceProxy.invoke] - [Client] start call channel id: 00e04cfffe360988-000017bc-00000000-399b9d7e1b88839d-5ccc4a29 
  8. 十月 05, 2021 2:16:19 下午 io.netty.handler.logging.LoggingHandler write 
  9. 信息: [id: 0x5ccc4a29, L:/127.0.0.1:50596 - R:localhost/127.0.0.1:9527] WRITE: DefaultRpcRequest{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', createTime=1633414579339, serviceId='calc', methodName='sum', paramTypeNames=[com.github.houbb.rpc.server.facade.model.CalculateRequest], paramValues=[CalculateRequest{one=10, two=20}]} 
  10. 十月 05, 2021 2:16:19 下午 io.netty.handler.logging.LoggingHandler flush 
  11. 信息: [id: 0x5ccc4a29, L:/127.0.0.1:50596 - R:localhost/127.0.0.1:9527] FLUSH 
  12. [INFO] [2021-10-05 14:16:19.412] [main] [c.g.h.r.c.p.ReferenceProxy.invoke] - [Client] start get resp for seqId: a525c5a6196545f5a5241b2cdc2ec2c2 
  13. [INFO] [2021-10-05 14:16:19.413] [main] [c.g.h.r.c.i.i.DefaultInvokeService.getResponse] - [Client] seq a525c5a6196545f5a5241b2cdc2ec2c2 对应结果为空,进入等待 
  14. 十月 05, 2021 2:16:19 下午 io.netty.handler.logging.LoggingHandler channelRead 
  15. 信息: [id: 0x5ccc4a29, L:/127.0.0.1:50596 - R:localhost/127.0.0.1:9527] READ: DefaultRpcResponse{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', error=null, result=CalculateResponse{success=truesum=30}} 
  16. ... 
  17. [INFO] [2021-10-05 14:16:19.505] [nioEventLoopGroup-2-1] [c.g.h.r.c.i.i.DefaultInvokeService.addResponse] - [Client] 获取结果信息,seq: a525c5a6196545f5a5241b2cdc2ec2c2, rpcResponse: DefaultRpcResponse{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', error=null, result=CalculateResponse{success=truesum=30}} 
  18. [INFO] [2021-10-05 14:16:19.505] [nioEventLoopGroup-2-1] [c.g.h.r.c.i.i.DefaultInvokeService.addResponse] - [Client] seq 信息已经放入,通知所有等待方 
  19. [INFO] [2021-10-05 14:16:19.506] [nioEventLoopGroup-2-1] [c.g.h.r.c.c.RpcClient.channelRead0] - [Client] response is :DefaultRpcResponse{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', error=null, result=CalculateResponse{success=truesum=30}} 
  20. [INFO] [2021-10-05 14:16:19.506] [main] [c.g.h.r.c.i.i.DefaultInvokeService.getResponse] - [Client] seq a525c5a6196545f5a5241b2cdc2ec2c2 对应结果已经获取: DefaultRpcResponse{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', error=null, result=CalculateResponse{success=truesum=30}} 
  21. [INFO] [2021-10-05 14:16:19.507] [main] [c.g.h.r.c.p.ReferenceProxy.invoke] - [Client] start get resp for seqId: a525c5a6196545f5a5241b2cdc2ec2c2 
  22. CalculateResponse{success=truesum=30} 

小结

现在看来有一个小问题,要求服务端必须指定 port,这有点不太合理,比如代理域名,后续需要优化。

这里的启动声明方式也比较基础,后续可以考虑和 spring 进行整合。

 

责任编辑:姜华 来源: 今日头条
相关推荐

2021-10-21 08:21:10

Java Reflect Java 基础

2021-10-19 08:58:48

Java 语言 Java 基础

2021-10-14 08:39:17

Java Netty Java 基础

2021-10-13 08:21:52

Java websocket Java 基础

2021-10-20 08:05:18

Java 序列化 Java 基础

2019-09-23 19:30:27

reduxreact.js前端

2021-10-29 08:07:30

Java timeout Java 基础

2023-11-17 09:13:36

2010-05-31 10:11:32

瘦客户端

2020-12-04 19:18:03

LinuxMySQLDBeaver

2009-07-21 13:03:06

桌面虚拟化虚拟PC数据中心

2013-05-09 09:33:59

2010-02-22 09:03:22

零客户端瘦客户端VDI终端

2022-11-08 15:14:17

MyBatis插件

2019-01-18 12:39:45

云计算PaaS公有云

2018-04-18 07:01:59

Docker容器虚拟机

2015-11-17 16:11:07

Code Review

2020-07-02 15:32:23

Kubernetes容器架构

2024-12-06 17:02:26

2009-08-07 13:55:35

Java客户端类调用C# WebServi
点赞
收藏

51CTO技术栈公众号