├── .gitignore ├── README.md ├── pom.xml ├── rpc-client ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── pjmike │ │ │ └── client │ │ │ ├── ClientApplication.java │ │ │ ├── config │ │ │ └── RpcSpringConfig.java │ │ │ ├── future │ │ │ ├── DefaultFuture.java │ │ │ ├── PendingResult.java │ │ │ └── ResultFuture.java │ │ │ ├── netty │ │ │ ├── ClientHandler.java │ │ │ └── NettyClient.java │ │ │ └── proxy │ │ │ ├── ProxyFactory.java │ │ │ └── RpcClientDynamicProxy.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── pjmike │ └── client │ └── ClientApplicationTests.java ├── rpc-common ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── pjmike │ │ │ └── common │ │ │ ├── CommonApplication.java │ │ │ ├── RpcInterface.java │ │ │ ├── bean │ │ │ └── Constant.java │ │ │ ├── protocol │ │ │ ├── RpcDecoder.java │ │ │ ├── RpcEncoder.java │ │ │ ├── RpcRequest.java │ │ │ ├── RpcResponse.java │ │ │ └── serialize │ │ │ │ ├── HessianSerializer.java │ │ │ │ ├── JSONSerializer.java │ │ │ │ ├── KryoSerializer.java │ │ │ │ └── Serializer.java │ │ │ ├── registry │ │ │ ├── ServiceDiscover.java │ │ │ ├── ServiceRegistry.java │ │ │ └── zookeeper │ │ │ │ ├── ZkServiceDiscover.java │ │ │ │ └── ZkServiceRegistry.java │ │ │ └── service │ │ │ └── HelloService.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── pjmike │ └── common │ └── CommonApplicationTests.java └── rpc-server ├── .gitignore ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── pjmike │ │ └── server │ │ ├── HelloServiceImpl.java │ │ ├── ServerApplication.java │ │ ├── handler │ │ └── ServerHandler.java │ │ └── netty │ │ └── NettyServer.java └── resources │ └── application.properties └── test └── java └── com └── pjmike └── server └── ServerApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | 31 | ### mvnw ### 32 | mvnw 33 | mvnw.cmd 34 | .mvn -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 现在网上有很多关于使用Netty来构建RPC框架的例子,为什么我这里还要写一篇文章进行论述呢,我很清楚我可能没有写得他们那么好。之所以还要写,有两点原因: 3 | - 一是因为学过Netty之后,还需要去不断实践才能更好的把握Netty的用法,显然,基于Netty实现RPC框架是一个很好的做法; 4 | - 二是因为目前市面上有很多RPC框架,比如Dubbo,这些框架通讯底层都是Netty,所以说通过这个例子,也可以更好的去体验RPC的设计。 5 | 6 | 7 | 下面我将从以下几点阐述如何基于Netty实现简易的RPC框架: 8 | - RPC是什么? 9 | - 实现RPC框架需要关注哪些方面 ? 10 | - 使用Netty如何实现? 11 | 12 | 13 | ## RPC是什么? 14 | 15 | RPC,远程过程调用,可以做到像本地调用一样调用远程服务,是一种进程间的通信方式,概念想必大家都很清楚,在看到58沈剑写的[RPC文章](https://www.w3cschool.cn/architectroad/architectroad-rpc-framework.html) 之后,意识到其实我们可以换一种思考方式去理解RPC,也就是从本地调用出发,进而去推导RPC调用 16 | 17 | ![rpc_58](https://pjmike-1253796536.cos.ap-beijing.myqcloud.com/%E5%88%86%E5%B8%83%E5%BC%8F/RPC/rpc_58.png) 18 | 19 | 20 | ### 1. 本地函数调用 21 | 22 | 本地函数是我们经常碰到的,比如下面示例: 23 | ``` 24 | public String sayHello(String name) { 25 | return "hello, " + name; 26 | } 27 | ``` 28 | 我们只需要传入一个参数,调用sayHello方法就可以得到一个输出,也就是输入参数——>方法体——>输出,入参、出参以及方法体都在同一个进程空间中,这就是**本地函数调用** 29 | 30 | ### 2. Socket通信 31 | 那有没有办法实现不同进程之间通信呢?调用方在进程A,需要调用方法A,但是方法A在进程B中 32 | 33 | ![rpc_2](https://pjmike-1253796536.cos.ap-beijing.myqcloud.com/%E5%88%86%E5%B8%83%E5%BC%8F/RPC/rpc_58_2.png) 34 | 35 | 最容易想到的方式就是使用Socket通信,使用Socket可以完成跨进程调用,我们需要约定一个进程通信协议,来进行传参,调用函数,出参。写过Socket应该都知道,Socket是比较原始的方式,我们需要更多的去关注一些细节问题,比如参数和函数需要转换成字节流进行网络传输,也就是序列化操作,然后出参时需要反序列化;使用socket进行底层通讯,代码编程也比较容易出错。 36 | 37 | 如果一个调用方需要关注这么多问题,那无疑是个灾难,所以有没有什么简单方法,让我们的调用方不需要关注细节问题,让调用方像调用本地函数一样,只要传入参数,调用方法,然后坐等返回结果就可以了呢? 38 | 39 | ### 3. RPC框架 40 | 41 | RPC框架就是用来解决上面的问题的,它能够让调用方像调用本地函数一样调用远程服务,底层通讯细节对调用方是透明的,将各种复杂性都给屏蔽掉,给予调用方极致体验。 42 | 43 | ![rpc_3](https://pjmike-1253796536.cos.ap-beijing.myqcloud.com/%E5%88%86%E5%B8%83%E5%BC%8F/RPC/rpc_58_3.png) 44 | 45 | 46 | 47 | ## RPC调用需要关注哪些方面 48 | 49 | 前面就已经说到RPC框架,让调用方像调用本地函数一样调用远程服务,那么如何做到这一点呢? 50 | 51 | 在使用的时候,调用方是直接调用本地函数,传入相应参数,其他细节它不用管,至于通讯细节交给RPC框架来实现。实际上RPC框架采用代理类的方式,具体来说是动态代理的方式,在运行时动态创建新的类,也就是代理类,在该类中实现通讯的细节问题,比如参数**序列化**。 52 | 53 | 54 | 当然不光是序列化,我们还需要**约定一个双方通信的协议格式**,规定好协议格式,比如请求参数的数据类型,请求的参数,请求的方法名等,这样根据格式进行序列化后进行网络传输,然后服务端收到请求对象后按照指定格式进行解码,这样服务端才知道具体该调用哪个方法,传入什么样的参数。 55 | 56 | 57 | 刚才又提到**网络传输**,RPC框架重要的一环也就是网络传输,服务是部署在不同主机上的,如何高效的进行网络传输,尽量不丢包,保证数据完整无误的快速传递出去?实际上,就是利用我们今天的主角——**Netty**,Netty是一个高性能的网络通讯框架,它足以胜任我们的任务。 58 | 59 | 60 | 61 | 前面说了这么多,再次总结下一个RPC框架需要重点关注哪几个点: 62 | - 代理 (动态代理) 63 | - 通讯协议 64 | - 序列化 65 | - 网络传输 66 | 67 | 当然一个优秀的RPC框架需要关注的不止上面几点,只不过本篇文章旨在做一个简易的RPC框架,理解了上面关键的几点就够了 68 | 69 | ![rpc_4](https://pjmike-1253796536.cos.ap-beijing.myqcloud.com/%E5%88%86%E5%B8%83%E5%BC%8F/RPC/rpc_58_4.png) 70 | 71 | 72 | 73 | ## 基于Netty实现RPC框架 74 | 75 | 终于到了本文的重头戏了,我们将根据实现RPC需要关注的几个要点(代理、序列化、协议、编解码),使用Netty进行逐一实现 76 | 77 | ### 1. Protocol(协议) 78 | 首先我们需要确定通信双方的协议格式,请求对象和响应对象 79 | 80 | 81 | 请求对象: 82 | ```java 83 | @Data 84 | @ToString 85 | public class RpcRequest { 86 | /** 87 | * 请求对象的ID 88 | */ 89 | private String requestId; 90 | /** 91 | * 类名 92 | */ 93 | private String className; 94 | /** 95 | * 方法名 96 | */ 97 | private String methodName; 98 | /** 99 | * 参数类型 100 | */ 101 | private Class[] parameterTypes; 102 | /** 103 | * 入参 104 | */ 105 | private Object[] parameters; 106 | } 107 | 108 | ``` 109 | - 请求对象的ID是客户端用来验证服务器请求和响应是否匹配 110 | 111 | 112 | 响应对象: 113 | ```java 114 | @Data 115 | public class RpcResponse { 116 | /** 117 | * 响应ID 118 | */ 119 | private String requestId; 120 | /** 121 | * 错误信息 122 | */ 123 | private String error; 124 | /** 125 | * 返回的结果 126 | */ 127 | private Object result; 128 | } 129 | ``` 130 | 131 | 132 | ### 2. 序列化 133 | 134 | 市面上序列化协议很多,比如jdk自带的,Google的protobuf,kyro、Hessian等,只要不选择jdk自带的序列化方法,(因为其性能太差,序列化后产生的码流太大),其他方式其实都可以,这里为了方便起见,**选用JSON作为序列化协议,使用fastjson作为JSON框架** 135 | 136 | 为了后续扩展方便,先定义序列化接口: 137 | ```java 138 | public interface Serializer { 139 | /** 140 | * java对象转换为二进制 141 | * 142 | * @param object 143 | * @return 144 | */ 145 | byte[] serialize(Object object) throws IOException; 146 | 147 | /** 148 | * 二进制转换成java对象 149 | * 150 | * @param clazz 151 | * @param bytes 152 | * @param 153 | * @return 154 | */ 155 | T deserialize(Class clazz, byte[] bytes) throws IOException; 156 | } 157 | ``` 158 | 因为我们采用JSON的方式,所以定义JSONSerializer的实现类: 159 | ```java 160 | public class JSONSerializer implements Serializer{ 161 | 162 | @Override 163 | public byte[] serialize(Object object) { 164 | return JSON.toJSONBytes(object); 165 | } 166 | 167 | @Override 168 | public T deserialize(Class clazz, byte[] bytes) { 169 | return JSON.parseObject(bytes, clazz); 170 | } 171 | } 172 | ``` 173 | 如果后续要使用其他序列化方式,可以自行实现序列化接口 174 | 175 | 176 | ### 3. 编解码器 177 | 约定好协议格式和序列化方式之后,我们还需要编解码器,编码器将请求对象转换为适合于传输的格式(一般来说是字节流),而对应的解码器是将网络字节流转换回应用程序的消息格式。 178 | 179 | 180 | 编码器实现: 181 | ```java 182 | public class RpcEncoder extends MessageToByteEncoder { 183 | private Class clazz; 184 | private Serializer serializer; 185 | 186 | public RpcEncoder(Class clazz, Serializer serializer) { 187 | this.clazz = clazz; 188 | this.serializer = serializer; 189 | } 190 | 191 | 192 | @Override 193 | protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, ByteBuf byteBuf) throws Exception { 194 | if (clazz != null && clazz.isInstance(msg)) { 195 | byte[] bytes = serializer.serialize(msg); 196 | byteBuf.writeInt(bytes.length); 197 | byteBuf.writeBytes(bytes); 198 | } 199 | } 200 | } 201 | ``` 202 | 解码器实现: 203 | ```java 204 | public class RpcDecoder extends ByteToMessageDecoder { 205 | private Class clazz; 206 | private Serializer serializer; 207 | 208 | public RpcDecoder(Class clazz, Serializer serializer) { 209 | this.clazz = clazz; 210 | this.serializer = serializer; 211 | } 212 | @Override 213 | protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { 214 | //因为之前编码的时候写入一个Int型,4个字节来表示长度 215 | if (byteBuf.readableBytes() < 4) { 216 | return; 217 | } 218 | //标记当前读的位置 219 | byteBuf.markReaderIndex(); 220 | int dataLength = byteBuf.readInt(); 221 | if (byteBuf.readableBytes() < dataLength) { 222 | byteBuf.resetReaderIndex(); 223 | return; 224 | } 225 | byte[] data = new byte[dataLength]; 226 | //将byteBuf中的数据读入data字节数组 227 | byteBuf.readBytes(data); 228 | Object obj = serializer.deserialize(clazz, data); 229 | list.add(obj); 230 | } 231 | } 232 | ``` 233 | 234 | ### 4. Netty 客户端 235 | 236 | 下面来看看Netty客户端是如何实现的,也就是如何使用Netty开启客户端。 237 | 238 | 实际上,熟悉Netty的朋友应该都知道,我们需要注意以下几点: 239 | - 编写启动方法,指定传输使用Channel 240 | - 指定ChannelHandler,对网络传输中的数据进行读写处理 241 | - 添加编解码器 242 | - 添加失败重试机制 243 | - 添加发送请求消息的方法 244 | 245 | 246 | 下面来看具体的实现代码: 247 | ```java 248 | @Slf4j 249 | public class NettyClient { 250 | private EventLoopGroup eventLoopGroup; 251 | private Channel channel; 252 | private ClientHandler clientHandler; 253 | private String host; 254 | private Integer port; 255 | private static final int MAX_RETRY = 5; 256 | public NettyClient(String host, Integer port) { 257 | this.host = host; 258 | this.port = port; 259 | } 260 | public void connect() { 261 | clientHandler = new ClientHandler(); 262 | eventLoopGroup = new NioEventLoopGroup(); 263 | //启动类 264 | Bootstrap bootstrap = new Bootstrap(); 265 | bootstrap.group(eventLoopGroup) 266 | //指定传输使用的Channel 267 | .channel(NioSocketChannel.class) 268 | .option(ChannelOption.SO_KEEPALIVE, true) 269 | .option(ChannelOption.TCP_NODELAY, true) 270 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000) 271 | .handler(new ChannelInitializer() { 272 | @Override 273 | protected void initChannel(SocketChannel ch) throws Exception { 274 | ChannelPipeline pipeline = ch.pipeline(); 275 | //添加编码器 276 | pipeline.addLast(new RpcEncoder(RpcRequest.class, new JSONSerializer())); 277 | //添加解码器 278 | pipeline.addLast(new RpcDecoder(RpcResponse.class, new JSONSerializer())); 279 | //请求处理类 280 | pipeline.addLast(clientHandler); 281 | } 282 | }); 283 | connect(bootstrap, host, port, MAX_RETRY); 284 | } 285 | 286 | /** 287 | * 失败重连机制,参考Netty入门实战掘金小册 288 | * 289 | * @param bootstrap 290 | * @param host 291 | * @param port 292 | * @param retry 293 | */ 294 | private void connect(Bootstrap bootstrap, String host, int port, int retry) { 295 | ChannelFuture channelFuture = bootstrap.connect(host, port).addListener(future -> { 296 | if (future.isSuccess()) { 297 | log.info("连接服务端成功"); 298 | } else if (retry == 0) { 299 | log.error("重试次数已用完,放弃连接"); 300 | } else { 301 | //第几次重连: 302 | int order = (MAX_RETRY - retry) + 1; 303 | //本次重连的间隔 304 | int delay = 1 << order; 305 | log.error("{} : 连接失败,第 {} 重连....", new Date(), order); 306 | bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS); 307 | } 308 | }); 309 | channel = channelFuture.channel(); 310 | } 311 | 312 | /** 313 | * 发送消息 314 | * 315 | * @param request 316 | * @return 317 | */ 318 | public RpcResponse send(final RpcRequest request) { 319 | try { 320 | channel.writeAndFlush(request).await(); 321 | } catch (InterruptedException e) { 322 | e.printStackTrace(); 323 | } 324 | return clientHandler.getRpcResponse(request.getRequestId()); 325 | } 326 | @PreDestroy 327 | public void close() { 328 | eventLoopGroup.shutdownGracefully(); 329 | channel.closeFuture().syncUninterruptibly(); 330 | } 331 | } 332 | 333 | ``` 334 | 335 | 我们对于数据的处理重点在于ClientHandler类上,它继承了ChannelDuplexHandler类,可以对出站和入站的数据进行处理 336 | ```java 337 | public class ClientHandler extends ChannelDuplexHandler { 338 | /** 339 | * 使用Map维护请求对象ID与响应结果Future的映射关系 340 | */ 341 | private final Map futureMap = new ConcurrentHashMap<>(); 342 | @Override 343 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 344 | if (msg instanceof RpcResponse) { 345 | //获取响应对象 346 | RpcResponse response = (RpcResponse) msg; 347 | DefaultFuture defaultFuture = 348 | futureMap.get(response.getRequestId()); 349 | //将结果写入DefaultFuture 350 | defaultFuture.setResponse(response); 351 | } 352 | super.channelRead(ctx,msg); 353 | } 354 | 355 | @Override 356 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 357 | if (msg instanceof RpcRequest) { 358 | RpcRequest request = (RpcRequest) msg; 359 | //发送请求对象之前,先把请求ID保存下来,并构建一个与响应Future的映射关系 360 | futureMap.putIfAbsent(request.getRequestId(), new DefaultFuture()); 361 | } 362 | super.write(ctx, msg, promise); 363 | } 364 | 365 | /** 366 | * 获取响应结果 367 | * 368 | * @param requsetId 369 | * @return 370 | */ 371 | public RpcResponse getRpcResponse(String requsetId) { 372 | try { 373 | DefaultFuture future = futureMap.get(requsetId); 374 | return future.getRpcResponse(5000); 375 | } finally { 376 | //获取成功以后,从map中移除 377 | futureMap.remove(requsetId); 378 | } 379 | } 380 | } 381 | 382 | ``` 383 | > 参考文章: https://xilidou.com/2018/09/26/dourpc-remoting/ 384 | 385 | 386 | 从上面实现可以看出,我们定义了一个Map,维护请求ID与响应结果的映射关系,目的是为了客户端用来验证服务端响应是否与请求相匹配,因为Netty的channel可能被多个线程使用,当结果返回时,你不知道是从哪个线程返回的,所以需要一个映射关系。 387 | 388 | 而我们的结果是封装在DefaultFuture中的,因为Netty是异步框架,所有的返回都是基于Future和Callback机制的,我们这里自定义Future来实现客户端"异步调用" 389 | ```java 390 | public class DefaultFuture { 391 | private RpcResponse rpcResponse; 392 | private volatile boolean isSucceed = false; 393 | private final Object object = new Object(); 394 | 395 | public RpcResponse getRpcResponse(int timeout) { 396 | synchronized (object) { 397 | while (!isSucceed) { 398 | try { 399 | object.wait(timeout); 400 | } catch (InterruptedException e) { 401 | e.printStackTrace(); 402 | } 403 | } 404 | return rpcResponse; 405 | } 406 | } 407 | 408 | public void setResponse(RpcResponse response) { 409 | if (isSucceed) { 410 | return; 411 | } 412 | synchronized (object) { 413 | this.rpcResponse = response; 414 | this.isSucceed = true; 415 | object.notify(); 416 | } 417 | } 418 | } 419 | 420 | ``` 421 | - 实际上用了wait和notify机制,同时使用一个boolean变量做辅助 422 | 423 | 424 | ### 5. Netty服务端 425 | 426 | Netty服务端的实现跟客户端的实现差不多,只不过要注意的是,当对请求进行解码过后,需要通过代理的方式调用本地函数。下面是Server端代码: 427 | ```java 428 | public class NettyServer implements InitializingBean { 429 | private EventLoopGroup boss = null; 430 | private EventLoopGroup worker = null; 431 | @Autowired 432 | private ServerHandler serverHandler; 433 | @Override 434 | public void afterPropertiesSet() throws Exception { 435 | //此处使用了zookeeper做注册中心,本文不涉及,可忽略 436 | ServiceRegistry registry = new ZkServiceRegistry("127.0.0.1:2181"); 437 | start(registry); 438 | } 439 | 440 | public void start(ServiceRegistry registry) throws Exception { 441 | //负责处理客户端连接的线程池 442 | boss = new NioEventLoopGroup(); 443 | //负责处理读写操作的线程池 444 | worker = new NioEventLoopGroup(); 445 | ServerBootstrap serverBootstrap = new ServerBootstrap(); 446 | serverBootstrap.group(boss, worker) 447 | .channel(NioServerSocketChannel.class) 448 | .option(ChannelOption.SO_BACKLOG, 1024) 449 | .childHandler(new ChannelInitializer() { 450 | @Override 451 | protected void initChannel(SocketChannel ch) throws Exception { 452 | ChannelPipeline pipeline = ch.pipeline(); 453 | //添加解码器 454 | pipeline.addLast(new RpcEncoder(RpcResponse.class, new JSONSerializer())); 455 | //添加编码器 456 | pipeline.addLast(new RpcDecoder(RpcRequest.class, new JSONSerializer())); 457 | //添加请求处理器 458 | pipeline.addLast(serverHandler); 459 | 460 | } 461 | }); 462 | bind(serverBootstrap, 8888); 463 | } 464 | 465 | /** 466 | * 如果端口绑定失败,端口数+1,重新绑定 467 | * 468 | * @param serverBootstrap 469 | * @param port 470 | */ 471 | public void bind(final ServerBootstrap serverBootstrap,int port) { 472 | serverBootstrap.bind(port).addListener(future -> { 473 | if (future.isSuccess()) { 474 | log.info("端口[ {} ] 绑定成功",port); 475 | } else { 476 | log.error("端口[ {} ] 绑定失败", port); 477 | bind(serverBootstrap, port + 1); 478 | } 479 | }); 480 | } 481 | 482 | @PreDestroy 483 | public void destory() throws InterruptedException { 484 | boss.shutdownGracefully().sync(); 485 | worker.shutdownGracefully().sync(); 486 | log.info("关闭Netty"); 487 | } 488 | } 489 | ``` 490 | 下面是处理读写操作的Handler类: 491 | ```java 492 | @Component 493 | @Slf4j 494 | @ChannelHandler.Sharable 495 | public class ServerHandler extends SimpleChannelInboundHandler implements ApplicationContextAware { 496 | private ApplicationContext applicationContext; 497 | @Override 498 | protected void channelRead0(ChannelHandlerContext ctx, RpcRequest msg) { 499 | RpcResponse rpcResponse = new RpcResponse(); 500 | rpcResponse.setRequestId(msg.getRequestId()); 501 | try { 502 | Object handler = handler(msg); 503 | log.info("获取返回结果: {} ", handler); 504 | rpcResponse.setResult(handler); 505 | } catch (Throwable throwable) { 506 | rpcResponse.setError(throwable.toString()); 507 | throwable.printStackTrace(); 508 | } 509 | ctx.writeAndFlush(rpcResponse); 510 | } 511 | 512 | /** 513 | * 服务端使用代理处理请求 514 | * 515 | * @param request 516 | * @return 517 | */ 518 | private Object handler(RpcRequest request) throws ClassNotFoundException, InvocationTargetException { 519 | //使用Class.forName进行加载Class文件 520 | Class clazz = Class.forName(request.getClassName()); 521 | Object serviceBean = applicationContext.getBean(clazz); 522 | log.info("serviceBean: {}",serviceBean); 523 | Class serviceClass = serviceBean.getClass(); 524 | log.info("serverClass:{}",serviceClass); 525 | String methodName = request.getMethodName(); 526 | 527 | Class[] parameterTypes = request.getParameterTypes(); 528 | Object[] parameters = request.getParameters(); 529 | 530 | //使用CGLIB Reflect 531 | FastClass fastClass = FastClass.create(serviceClass); 532 | FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes); 533 | log.info("开始调用CGLIB动态代理执行服务端方法..."); 534 | return fastMethod.invoke(serviceBean, parameters); 535 | } 536 | 537 | @Override 538 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 539 | this.applicationContext = applicationContext; 540 | } 541 | } 542 | ``` 543 | 544 | 545 | ### 6. 客户端代理 546 | 客户端使用Java动态代理,在代理类中实现通信细节,众所众知,Java动态代理需要实现InvocationHandler接口 547 | ```java 548 | @Slf4j 549 | public class RpcClientDynamicProxy implements InvocationHandler { 550 | private Class clazz; 551 | public RpcClientDynamicProxy(Class clazz) throws Exception { 552 | this.clazz = clazz; 553 | } 554 | 555 | @Override 556 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 557 | RpcRequest request = new RpcRequest(); 558 | String requestId = UUID.randomUUID().toString(); 559 | 560 | String className = method.getDeclaringClass().getName(); 561 | String methodName = method.getName(); 562 | 563 | Class[] parameterTypes = method.getParameterTypes(); 564 | 565 | request.setRequestId(requestId); 566 | request.setClassName(className); 567 | request.setMethodName(methodName); 568 | request.setParameterTypes(parameterTypes); 569 | request.setParameters(args); 570 | log.info("请求内容: {}",request); 571 | 572 | //开启Netty 客户端,直连 573 | NettyClient nettyClient = new NettyClient("127.0.0.1", 8888); 574 | log.info("开始连接服务端:{}",new Date()); 575 | nettyClient.connect(); 576 | RpcResponse send = nettyClient.send(request); 577 | log.info("请求调用返回结果:{}", send.getResult()); 578 | return send.getResult(); 579 | } 580 | } 581 | 582 | ``` 583 | - 在invoke方法中封装请求对象,构建NettyClient对象,并开启客户端,发送请求消息 584 | 585 | 代理工厂类如下: 586 | ```java 587 | public class ProxyFactory { 588 | public static T create(Class interfaceClass) throws Exception { 589 | return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class[] {interfaceClass}, new RpcClientDynamicProxy(interfaceClass)); 590 | } 591 | } 592 | ``` 593 | - 通过Proxy.newProxyInstance创建接口的代理类 594 | 595 | ### 7. RPC远程调用测试 596 | API: 597 | ```java 598 | public interface HelloService { 599 | String hello(String name); 600 | } 601 | ``` 602 | - 准备一个测试API接口 603 | 604 | 605 | 客户端: 606 | ```java 607 | @SpringBootApplication 608 | @Slf4j 609 | public class ClientApplication { 610 | public static void main(String[] args) throws Exception { 611 | SpringApplication.run(ClientApplication.class, args); 612 | HelloService helloService = ProxyFactory.create(HelloService.class); 613 | log.info("响应结果“: {}",helloService.hello("pjmike")); 614 | } 615 | } 616 | ``` 617 | - 客户端调用接口的方法 618 | 619 | 620 | 服务端: 621 | ```java 622 | //服务端实现 623 | @Service 624 | public class HelloServiceImpl implements HelloService { 625 | @Override 626 | public String hello(String name) { 627 | return "hello, " + name; 628 | } 629 | } 630 | ``` 631 | 632 | 运行结果: 633 | 634 | ![rpc_5](https://pjmike-1253796536.cos.ap-beijing.myqcloud.com/%E5%88%86%E5%B8%83%E5%BC%8F/RPC/rpc_58_5.png) 635 | 636 | 637 | 638 | ## 小结 639 | 640 | 以上我们基于Netty实现了一个非非非常简陋的RPC框架,比起成熟的RPC框架来说相差甚远,甚至说基本的注册中心都没有实现,但是通过本次实践,可以说我对于RPC的理解更深了,了解了一个RPC框架到底需要关注哪些方面,未来当我们使用成熟的RPC框架时,比如Dubbo,能够做到心中有数,能明白其底层不过也是使用Netty作为基础通讯框架。往后,如果更深入翻看开源RPC框架源码时,也相对比较容易 641 | 642 | 643 | ## 参考资料 & 鸣谢 644 | 645 | - https://xilidou.com/2018/09/26/dourpc-remoting/ 646 | - https://www.cnblogs.com/luxiaoxun/p/5272384.html 647 | - https://my.oschina.net/huangyong/blog/361751 648 | - https://www.w3cschool.cn/architectroad/architectroad-rpc-framework.html 649 | - https://www.cnkirito.moe/dubbo27-features/ 650 | - https://juejin.im/post/5ad2a99ff265da238d51264d#heading-6 651 | - https://juejin.im/book/5b4bc28bf265da0f60130116/section/5b4db0e6e51d45191d79e475#heading-3 652 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.4.RELEASE 9 | 10 | 11 | com.pjmike 12 | rpc 13 | 0.0.1-SNAPSHOT 14 | rpc 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | rpc-client 22 | rpc-server 23 | rpc-common 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter 29 | 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | true 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-test 39 | test 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 2.0.1.RELEASE 45 | 46 | 47 | com.alibaba 48 | fastjson 49 | 1.2.47 50 | 51 | 52 | io.netty 53 | netty-all 54 | 4.1.24.Final 55 | 56 | 57 | org.reflections 58 | reflections 59 | 0.9.11 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-maven-plugin 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /rpc-client/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | -------------------------------------------------------------------------------- /rpc-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.4.RELEASE 9 | 10 | 11 | com.pjmike 12 | client 13 | 0.0.1-SNAPSHOT 14 | client 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter 25 | 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | true 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | com.alibaba 39 | fastjson 40 | 1.2.47 41 | 42 | 43 | io.netty 44 | netty-all 45 | 4.1.24.Final 46 | 47 | 48 | org.reflections 49 | reflections 50 | 0.9.11 51 | 52 | 53 | com.pjmike 54 | common 55 | 0.0.1-SNAPSHOT 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-maven-plugin 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/com/pjmike/client/ClientApplication.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.client; 2 | 3 | import com.pjmike.client.proxy.ProxyFactory; 4 | import com.pjmike.common.service.HelloService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | 9 | @SpringBootApplication 10 | @Slf4j 11 | public class ClientApplication { 12 | 13 | public static void main(String[] args) throws Exception { 14 | SpringApplication.run(ClientApplication.class, args); 15 | // ConfigurableApplicationContext context = SpringApplication.run(ClientApplication.class, args); 16 | // HelloService helloService = context.getBean(HelloService.class); 17 | HelloService helloService = ProxyFactory.create(HelloService.class); 18 | log.info("响应结果“: {}",helloService.hello("pjmike")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/com/pjmike/client/config/RpcSpringConfig.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.client.config; 2 | 3 | import com.pjmike.client.proxy.ProxyFactory; 4 | import com.pjmike.common.RpcInterface; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.reflections.Reflections; 7 | import org.springframework.beans.BeansException; 8 | import org.springframework.beans.factory.InitializingBean; 9 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.ApplicationContextAware; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | import java.util.Set; 15 | 16 | /** 17 | * @description: 18 | * @author: pjmike 19 | * @create: 2019/04/07 18:07 20 | */ 21 | //@Configuration 22 | @Slf4j 23 | public class RpcSpringConfig implements ApplicationContextAware, InitializingBean { 24 | private ApplicationContext applicationContext; 25 | @Override 26 | public void afterPropertiesSet() throws Exception { 27 | Reflections reflections = new Reflections("com.pjmike"); 28 | DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory(); 29 | Set> typesAnnotateWith = reflections.getTypesAnnotatedWith(RpcInterface.class); 30 | for (Class clazz : typesAnnotateWith) { 31 | beanFactory.registerSingleton(clazz.getSimpleName(), ProxyFactory.create(clazz)); 32 | } 33 | log.info("afterPropertiesSet is {}",typesAnnotateWith); 34 | 35 | } 36 | 37 | @Override 38 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 39 | this.applicationContext = applicationContext; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/com/pjmike/client/future/DefaultFuture.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.client.future; 2 | 3 | 4 | import com.pjmike.common.protocol.RpcResponse; 5 | 6 | /** 7 | * @description: 自定义实现Future 8 | * 9 | * @author: pjmike 10 | * @create: 2019/04/07 16:34 11 | */ 12 | public class DefaultFuture { 13 | private RpcResponse rpcResponse; 14 | private volatile boolean isSucceed = false; 15 | private final Object object = new Object(); 16 | 17 | public RpcResponse getRpcResponse(int timeout) { 18 | synchronized (object) { 19 | while (!isSucceed) { 20 | try { 21 | object.wait(timeout); 22 | } catch (InterruptedException e) { 23 | e.printStackTrace(); 24 | } 25 | } 26 | return rpcResponse; 27 | } 28 | } 29 | 30 | public void setResponse(RpcResponse response) { 31 | if (isSucceed) { 32 | return; 33 | } 34 | synchronized (object) { 35 | this.rpcResponse = response; 36 | this.isSucceed = true; 37 | object.notify(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/com/pjmike/client/future/PendingResult.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.client.future; 2 | 3 | import com.pjmike.common.protocol.RpcResponse; 4 | 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | /** 9 | * @author: pjmike 10 | * @create: 2019/11/13 11 | */ 12 | public class PendingResult { 13 | private Map map = new ConcurrentHashMap<>(); 14 | 15 | public void add(String id, ResultFuture future) { 16 | this.map.put(id, future); 17 | } 18 | 19 | public void set(String id, RpcResponse response) { 20 | ResultFuture resultFuture = this.map.get(id); 21 | if (resultFuture != null) { 22 | resultFuture.setSuccess(response); 23 | this.map.remove(id); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/com/pjmike/client/future/ResultFuture.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.client.future; 2 | 3 | import com.pjmike.common.protocol.RpcResponse; 4 | import io.netty.util.concurrent.DefaultPromise; 5 | 6 | /** 7 | * Future有几种模式: 8 | * 1. JDK自带的Future: {@link java.util.concurrent.Future} 9 | * 2. 自定义Future: 比如{@link DefaultFuture} 10 | * 3. 利用Netty的DefaultPromise : {@link DefaultPromise} 11 | * 4. Java 8 的 {@link java.util.concurrent.CompletableFuture} 12 | * 13 | * @author: pjmike 14 | * @create: 2019/11/13 15 | */ 16 | public class ResultFuture extends DefaultPromise { 17 | } 18 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/com/pjmike/client/netty/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.client.netty; 2 | 3 | import com.pjmike.client.future.DefaultFuture; 4 | import com.pjmike.common.protocol.RpcRequest; 5 | import com.pjmike.common.protocol.RpcResponse; 6 | import io.netty.channel.ChannelDuplexHandler; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelPromise; 9 | 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | /** 14 | * @description: 15 | * @author: pjmike 16 | * @create: 2019/03/29 11:06 17 | */ 18 | public class ClientHandler extends ChannelDuplexHandler { 19 | /** 20 | * 使用Map维护请求对象ID与响应结果Future的映射关系 21 | */ 22 | private final Map futureMap = new ConcurrentHashMap<>(); 23 | @Override 24 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 25 | if (msg instanceof RpcResponse) { 26 | //获取响应对象 27 | RpcResponse response = (RpcResponse) msg; 28 | DefaultFuture defaultFuture = futureMap.get(response.getRequestId()); 29 | defaultFuture.setResponse(response); 30 | } 31 | super.channelRead(ctx,msg); 32 | } 33 | 34 | @Override 35 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 36 | if (msg instanceof RpcRequest) { 37 | RpcRequest request = (RpcRequest) msg; 38 | //发送请求对象之前,先把请求ID保存下来,并构建一个与响应Future的映射关系 39 | futureMap.putIfAbsent(request.getRequestId(), new DefaultFuture()); 40 | } 41 | super.write(ctx, msg, promise); 42 | } 43 | 44 | /** 45 | * 获取响应结果 46 | * 47 | * @param requestId 48 | * @return 49 | */ 50 | public RpcResponse getRpcResponse(String requestId) { 51 | try { 52 | DefaultFuture future = futureMap.get(requestId); 53 | return future.getRpcResponse(10); 54 | } finally { 55 | //获取成功以后,从map中移除 56 | futureMap.remove(requestId); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/com/pjmike/client/netty/NettyClient.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.client.netty; 2 | 3 | import com.pjmike.common.protocol.RpcDecoder; 4 | import com.pjmike.common.protocol.RpcEncoder; 5 | import com.pjmike.common.protocol.RpcRequest; 6 | import com.pjmike.common.protocol.RpcResponse; 7 | import com.pjmike.common.protocol.serialize.JSONSerializer; 8 | import io.netty.bootstrap.Bootstrap; 9 | import io.netty.channel.*; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.SocketChannel; 12 | import io.netty.channel.socket.nio.NioSocketChannel; 13 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 14 | 15 | import lombok.extern.slf4j.Slf4j; 16 | 17 | import javax.annotation.PreDestroy; 18 | import java.util.Date; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | /** 22 | * @description: 23 | * @author: pjmike 24 | * @create: 2019/03/29 10:58 25 | */ 26 | @Slf4j 27 | public class NettyClient { 28 | private EventLoopGroup eventLoopGroup; 29 | private Channel channel; 30 | private ClientHandler clientHandler; 31 | private String host; 32 | private Integer port; 33 | private static final int MAX_RETRY = 5; 34 | public NettyClient(String host, Integer port) { 35 | this.host = host; 36 | this.port = port; 37 | } 38 | public void connect() { 39 | clientHandler = new ClientHandler(); 40 | eventLoopGroup = new NioEventLoopGroup(); 41 | //启动类 42 | Bootstrap bootstrap = new Bootstrap(); 43 | bootstrap.group(eventLoopGroup) 44 | //指定传输使用的Channel 45 | .channel(NioSocketChannel.class) 46 | .option(ChannelOption.SO_KEEPALIVE, true) 47 | .option(ChannelOption.TCP_NODELAY, true) 48 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000) 49 | .handler(new ChannelInitializer() { 50 | @Override 51 | protected void initChannel(SocketChannel ch) throws Exception { 52 | ChannelPipeline pipeline = ch.pipeline(); 53 | pipeline.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4)); 54 | pipeline.addLast(new RpcEncoder(RpcRequest.class, new JSONSerializer())); 55 | pipeline.addLast(new RpcDecoder(RpcResponse.class, new JSONSerializer())); 56 | pipeline.addLast(clientHandler); 57 | } 58 | }); 59 | connect(bootstrap, host, port, MAX_RETRY); 60 | // ChannelFuture future = bootstrap.connect(host, port).sync(); 61 | // channel = future.channel(); 62 | } 63 | 64 | /** 65 | * 失败重连机制,参考Netty入门实战掘金小册 66 | * 67 | * @param bootstrap 68 | * @param host 69 | * @param port 70 | * @param retry 71 | */ 72 | private void connect(Bootstrap bootstrap, String host, int port, int retry) { 73 | ChannelFuture channelFuture = bootstrap.connect(host, port).addListener(future -> { 74 | if (future.isSuccess()) { 75 | log.info("连接服务端成功"); 76 | } else if (retry == 0) { 77 | log.error("重试次数已用完,放弃连接"); 78 | } else { 79 | //第几次重连: 80 | int order = (MAX_RETRY - retry) + 1; 81 | //本次重连的间隔 82 | int delay = 1 << order; 83 | log.error("{} : 连接失败,第 {} 重连....", new Date(), order); 84 | bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS); 85 | } 86 | }); 87 | channel = channelFuture.channel(); 88 | } 89 | 90 | /** 91 | * 发送消息 92 | * 93 | * @param request 94 | * @return 95 | */ 96 | public RpcResponse send(final RpcRequest request) { 97 | try { 98 | channel.writeAndFlush(request).await(); 99 | } catch (InterruptedException e) { 100 | e.printStackTrace(); 101 | } 102 | return clientHandler.getRpcResponse(request.getRequestId()); 103 | } 104 | @PreDestroy 105 | public void close() { 106 | eventLoopGroup.shutdownGracefully(); 107 | channel.closeFuture().syncUninterruptibly(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/com/pjmike/client/proxy/ProxyFactory.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.client.proxy; 2 | 3 | import java.lang.reflect.Proxy; 4 | 5 | /** 6 | * @description: 7 | * @author: pjmike 8 | * @create: 2019/04/07 17:01 9 | */ 10 | public class ProxyFactory { 11 | public static T create(Class interfaceClass) throws Exception { 12 | return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class[] {interfaceClass}, new RpcClientDynamicProxy(interfaceClass)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/com/pjmike/client/proxy/RpcClientDynamicProxy.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.client.proxy; 2 | 3 | import com.pjmike.client.netty.NettyClient; 4 | import com.pjmike.common.protocol.RpcRequest; 5 | import com.pjmike.common.protocol.RpcResponse; 6 | import com.pjmike.common.registry.ServiceDiscover; 7 | import com.pjmike.common.registry.zookeeper.ZkServiceDiscover; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.lang.reflect.InvocationHandler; 11 | import java.lang.reflect.Method; 12 | import java.util.Date; 13 | import java.util.UUID; 14 | 15 | /** 16 | * @description: 17 | * @author: pjmike 18 | * @create: 2019/04/07 16:53 19 | */ 20 | @Slf4j 21 | public class RpcClientDynamicProxy implements InvocationHandler { 22 | private Class clazz; 23 | // private ServiceDiscover discover = new ZkServiceDiscover("127.0.0.1:2181"); 24 | public RpcClientDynamicProxy(Class clazz) throws Exception { 25 | this.clazz = clazz; 26 | } 27 | 28 | @Override 29 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 30 | 31 | 32 | RpcRequest request = new RpcRequest(); 33 | String requestId = UUID.randomUUID().toString(); 34 | 35 | String className = method.getDeclaringClass().getName(); 36 | String methodName = method.getName(); 37 | 38 | Class[] parameterTypes = method.getParameterTypes(); 39 | 40 | request.setRequestId(requestId); 41 | request.setClassName(className); 42 | request.setMethodName(methodName); 43 | request.setParameterTypes(parameterTypes); 44 | request.setParameters(args); 45 | log.info("请求内容: {}",request); 46 | 47 | // String address = discover.discover(); 48 | // String[] arrays = address.split(":"); 49 | // String host = arrays[0]; 50 | // int port = Integer.parseInt(arrays[1]); 51 | NettyClient nettyClient = new NettyClient("127.0.0.1", 8888); 52 | log.info("开始连接服务端:{}",new Date()); 53 | nettyClient.connect(); 54 | RpcResponse send = nettyClient.send(request); 55 | log.info("请求调用返回结果:{}", send.getResult()); 56 | return send.getResult(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /rpc-client/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8810 -------------------------------------------------------------------------------- /rpc-client/src/test/java/com/pjmike/client/ClientApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.client; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ClientApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /rpc-common/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | -------------------------------------------------------------------------------- /rpc-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.4.RELEASE 9 | 10 | 11 | com.pjmike 12 | common 13 | 0.0.1-SNAPSHOT 14 | common 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter 25 | 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | true 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | com.alibaba 39 | fastjson 40 | 1.2.47 41 | 42 | 43 | io.netty 44 | netty-all 45 | 4.1.24.Final 46 | 47 | 48 | org.reflections 49 | reflections 50 | 0.9.11 51 | 52 | 53 | 54 | com.esotericsoftware 55 | kryo 56 | 4.0.1 57 | 58 | 59 | com.esotericsoftware 60 | kryo-shaded 61 | 4.0.1 62 | 63 | 64 | com.caucho 65 | hessian 66 | 4.0.51 67 | 68 | 69 | org.apache.zookeeper 70 | zookeeper 71 | 3.4.11 72 | 73 | 74 | org.apache.curator 75 | curator-framework 76 | 4.0.0 77 | 78 | 79 | org.apache.curator 80 | curator-recipes 81 | 4.0.0 82 | 83 | 84 | 85 | 86 | 87 | 88 | org.springframework.boot 89 | spring-boot-maven-plugin 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/CommonApplication.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CommonApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(CommonApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/RpcInterface.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface RpcInterface { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/bean/Constant.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.bean; 2 | 3 | /** 4 | * @description: 常量 5 | * @author: pjmike 6 | * @create: 2019/04/29 22:13 7 | */ 8 | public class Constant { 9 | public static final int ZK_SESSION_TIMEOUT = 5000; 10 | public static final int ZK_CONNECTION_TIMEOUT = 5000; 11 | public static final String ZK_REGISTRY_PATH = "/registry"; 12 | public static final String ZK_CHILDREN_PATH = ZK_REGISTRY_PATH + "/data"; 13 | } 14 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/protocol/RpcDecoder.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.protocol; 2 | 3 | import com.alibaba.fastjson.JSONReader; 4 | import com.pjmike.common.protocol.serialize.Serializer; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.ByteToMessageDecoder; 8 | 9 | import java.io.FileInputStream; 10 | import java.io.InputStreamReader; 11 | import java.util.List; 12 | 13 | /** 14 | * @description: 15 | * @author: pjmike 16 | * @create: 2019/03/29 09:57 17 | */ 18 | public class RpcDecoder extends ByteToMessageDecoder { 19 | private Class clazz; 20 | private Serializer serializer; 21 | 22 | public RpcDecoder(Class clazz, Serializer serializer) { 23 | this.clazz = clazz; 24 | this.serializer = serializer; 25 | } 26 | @Override 27 | protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { 28 | //因为之前编码的时候写入一个Int型,4个字节来表示长度 29 | if (byteBuf.readableBytes() < 4) { 30 | return; 31 | } 32 | //标记当前读的位置 33 | byteBuf.markReaderIndex(); 34 | int dataLength = byteBuf.readInt(); 35 | if (byteBuf.readableBytes() < dataLength) { 36 | byteBuf.resetReaderIndex(); 37 | return; 38 | } 39 | byte[] data = new byte[dataLength]; 40 | //将byteBuf中的数据读入data字节数组 41 | byteBuf.readBytes(data); 42 | Object obj = serializer.deserialize(clazz, data); 43 | list.add(obj); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/protocol/RpcEncoder.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.protocol; 2 | 3 | import com.pjmike.common.protocol.serialize.Serializer; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.MessageToByteEncoder; 7 | 8 | 9 | /** 10 | * @description: 11 | * @author: pjmike 12 | * @create: 2019/03/29 09:51 13 | */ 14 | public class RpcEncoder extends MessageToByteEncoder { 15 | private Class clazz; 16 | private Serializer serializer; 17 | 18 | public RpcEncoder(Class clazz, Serializer serializer) { 19 | this.clazz = clazz; 20 | this.serializer = serializer; 21 | } 22 | 23 | 24 | @Override 25 | protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, ByteBuf byteBuf) throws Exception { 26 | if (clazz != null && clazz.isInstance(msg)) { 27 | byte[] bytes = serializer.serialize(msg); 28 | byteBuf.writeInt(bytes.length); 29 | byteBuf.writeBytes(bytes); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/protocol/RpcRequest.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.protocol; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | /** 7 | * @description: RPC Request 8 | * @author: pjmike 9 | * @create: 2019/03/28 22:58 10 | */ 11 | @Data 12 | @ToString 13 | public class RpcRequest { 14 | /** 15 | * 请求对象的ID 16 | */ 17 | private String requestId; 18 | /** 19 | * 类名 20 | */ 21 | private String className; 22 | /** 23 | * 方法名 24 | */ 25 | private String methodName; 26 | /** 27 | * 参数类型 28 | */ 29 | private Class[] parameterTypes; 30 | /** 31 | * 入参 32 | */ 33 | private Object[] parameters; 34 | } 35 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/protocol/RpcResponse.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.protocol; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @description: RPC Response 7 | * @author: pjmike 8 | * @create: 2019/03/28 23:01 9 | */ 10 | @Data 11 | public class RpcResponse { 12 | /** 13 | * 响应ID 14 | */ 15 | private String requestId; 16 | /** 17 | * 错误信息 18 | */ 19 | private String error; 20 | /** 21 | * 返回的结果 22 | */ 23 | private Object result; 24 | } 25 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/protocol/serialize/HessianSerializer.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.protocol.serialize; 2 | 3 | import com.caucho.hessian.io.Hessian2Input; 4 | import com.caucho.hessian.io.Hessian2Output; 5 | 6 | import java.io.ByteArrayInputStream; 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.IOException; 9 | 10 | /** 11 | * @description: Hessian 序列化方案经常被RPC框架用来作为默认的序列化方案,比如dubbo,motan 12 | * 13 | * @author: pjmike 14 | * @create: 2019/04/08 19:23 15 | */ 16 | public class HessianSerializer implements Serializer{ 17 | @Override 18 | public byte[] serialize(Object object) throws IOException { 19 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 20 | Hessian2Output output = new Hessian2Output(byteArrayOutputStream); 21 | output.writeObject(object); 22 | output.flush(); 23 | return byteArrayOutputStream.toByteArray(); 24 | } 25 | 26 | @Override 27 | public T deserialize(Class clazz, byte[] bytes) throws IOException { 28 | Hessian2Input input = new Hessian2Input(new ByteArrayInputStream(bytes)); 29 | return (T) input.readObject(clazz); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/protocol/serialize/JSONSerializer.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.protocol.serialize; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | /** 6 | * @description: 使用fastJson作为序列化框架 7 | * @author: pjmike 8 | * @create: 2019/03/29 09:41 9 | */ 10 | public class JSONSerializer implements Serializer{ 11 | 12 | @Override 13 | public byte[] serialize(Object object) { 14 | return JSON.toJSONBytes(object); 15 | } 16 | 17 | @Override 18 | public T deserialize(Class clazz, byte[] bytes) { 19 | return JSON.parseObject(bytes, clazz); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/protocol/serialize/KryoSerializer.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.protocol.serialize; 2 | 3 | import com.esotericsoftware.kryo.Kryo; 4 | import com.esotericsoftware.kryo.io.Input; 5 | import com.esotericsoftware.kryo.io.Output; 6 | 7 | import java.io.ByteArrayInputStream; 8 | import java.io.ByteArrayOutputStream; 9 | 10 | /** 11 | * @description: 12 | * @author: pjmike 13 | * @create: 2019/04/08 18:57 14 | */ 15 | public class KryoSerializer implements Serializer{ 16 | /** 17 | * Kryo不是线程安全的,使用ThreadLocal来保证其线程安全 18 | * 19 | */ 20 | private static final ThreadLocal kryos = ThreadLocal.withInitial(() -> { 21 | Kryo kryo = new Kryo(); 22 | //支持循环引用 23 | kryo.setReferences(true); 24 | //关闭注册行为 25 | kryo.setRegistrationRequired(false); 26 | return kryo; 27 | }); 28 | @Override 29 | public byte[] serialize(Object object) { 30 | Kryo kryo = kryos.get(); 31 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 32 | Output output = new Output(byteArrayOutputStream); 33 | kryo.writeClassAndObject(output, object); 34 | output.close(); 35 | return byteArrayOutputStream.toByteArray(); 36 | } 37 | 38 | @Override 39 | public T deserialize(Class clazz, byte[] bytes) { 40 | Kryo kryo = kryos.get(); 41 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); 42 | Input input = new Input(byteArrayInputStream); 43 | input.close(); 44 | return (T) kryo.readClassAndObject(input); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/protocol/serialize/Serializer.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.protocol.serialize; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * @description: 序列化接口 7 | * @author: pjmike 8 | * @create: 2019/03/28 23:05 9 | */ 10 | public interface Serializer { 11 | /** 12 | * java对象转换为二进制 13 | * 14 | * @param object 15 | * @return 16 | */ 17 | byte[] serialize(Object object) throws IOException; 18 | 19 | /** 20 | * 二进制转换成java对象 21 | * 22 | * @param clazz 23 | * @param bytes 24 | * @param 25 | * @return 26 | */ 27 | T deserialize(Class clazz, byte[] bytes) throws IOException; 28 | } 29 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/registry/ServiceDiscover.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.registry; 2 | 3 | /** 4 | * @description: 服务发现接口 5 | * @author: 13572 6 | * @create: 2019/04/29 21:46 7 | */ 8 | public interface ServiceDiscover { 9 | String discover(); 10 | } 11 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/registry/ServiceRegistry.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.registry; 2 | 3 | /** 4 | * 服务注册接口 5 | */ 6 | public interface ServiceRegistry { 7 | void registry(String data) throws Exception; 8 | } 9 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/registry/zookeeper/ZkServiceDiscover.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.registry.zookeeper; 2 | 3 | import com.pjmike.common.bean.Constant; 4 | import com.pjmike.common.registry.ServiceDiscover; 5 | import org.apache.curator.framework.CuratorFramework; 6 | import org.apache.curator.framework.CuratorFrameworkFactory; 7 | import org.apache.curator.retry.ExponentialBackoffRetry; 8 | import org.apache.zookeeper.Watcher; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.concurrent.ThreadLocalRandom; 13 | 14 | /** 15 | * @description: 服务发现类 16 | * @author: 13572 17 | * @create: 2019/04/29 22:40 18 | */ 19 | public class ZkServiceDiscover implements ServiceDiscover { 20 | private final CuratorFramework curatorFramework; 21 | private volatile List dataList = new ArrayList<>(); 22 | public ZkServiceDiscover(String zkAddress) throws Exception { 23 | this.curatorFramework = CuratorFrameworkFactory 24 | .builder() 25 | .connectString(zkAddress) 26 | .connectionTimeoutMs(Constant.ZK_CONNECTION_TIMEOUT) 27 | .sessionTimeoutMs(Constant.ZK_SESSION_TIMEOUT) 28 | .retryPolicy(new ExponentialBackoffRetry(1000, 3)) 29 | .build(); 30 | watchChildNode(curatorFramework); 31 | } 32 | 33 | private void watchChildNode(final CuratorFramework client) throws Exception { 34 | client.start(); 35 | List nodeList = client.getChildren().usingWatcher((Watcher) watchedEvent -> { 36 | if (watchedEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged) { 37 | try { 38 | watchChildNode(client); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | }).forPath(Constant.ZK_REGISTRY_PATH); 44 | 45 | List dataList = new ArrayList<>(); 46 | for (String node : nodeList) { 47 | byte[] data = client.getData().forPath(Constant.ZK_REGISTRY_PATH + "/" + node); 48 | dataList.add(new String(data)); 49 | } 50 | this.dataList = dataList; 51 | } 52 | 53 | @Override 54 | public String discover(){ 55 | String data = null; 56 | int size = dataList.size(); 57 | if (size > 0) { 58 | if (size == 1) { 59 | data = dataList.get(0); 60 | } else { 61 | data = dataList.get(ThreadLocalRandom.current().nextInt(size)); 62 | } 63 | } 64 | return data; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/registry/zookeeper/ZkServiceRegistry.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.registry.zookeeper; 2 | 3 | import com.pjmike.common.bean.Constant; 4 | import com.pjmike.common.registry.ServiceRegistry; 5 | import org.apache.curator.framework.CuratorFramework; 6 | import org.apache.curator.framework.CuratorFrameworkFactory; 7 | import org.apache.curator.retry.ExponentialBackoffRetry; 8 | import org.apache.zookeeper.CreateMode; 9 | 10 | import java.util.concurrent.CountDownLatch; 11 | 12 | /** 13 | * @description: 14 | * @author: 13572 15 | * @create: 2019/04/29 22:03 16 | */ 17 | public class ZkServiceRegistry implements ServiceRegistry { 18 | 19 | private final CuratorFramework curatorFramework; 20 | private final CountDownLatch countDownLatch = new CountDownLatch(1); 21 | public ZkServiceRegistry(String address) { 22 | this.curatorFramework = CuratorFrameworkFactory.builder() 23 | .connectString(address) 24 | .sessionTimeoutMs(Constant.ZK_SESSION_TIMEOUT) 25 | .retryPolicy(new ExponentialBackoffRetry(1000, 3)) 26 | .build(); 27 | } 28 | 29 | @Override 30 | public void registry(String data) throws Exception { 31 | curatorFramework.start(); 32 | 33 | String path = Constant.ZK_CHILDREN_PATH; 34 | curatorFramework.create() 35 | .creatingParentsIfNeeded() 36 | .withMode(CreateMode.EPHEMERAL_SEQUENTIAL) 37 | .forPath(path, data.getBytes()); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/com/pjmike/common/service/HelloService.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common.service; 2 | 3 | 4 | public interface HelloService { 5 | String hello(String name); 6 | } 7 | -------------------------------------------------------------------------------- /rpc-common/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rpc-common/src/test/java/com/pjmike/common/CommonApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.common; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class CommonApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /rpc-server/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | -------------------------------------------------------------------------------- /rpc-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.4.RELEASE 9 | 10 | 11 | com.pjmike 12 | server 13 | 0.0.1-SNAPSHOT 14 | server 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter 25 | 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | true 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | com.alibaba 39 | fastjson 40 | 1.2.47 41 | 42 | 43 | io.netty 44 | netty-all 45 | 4.1.24.Final 46 | 47 | 48 | org.reflections 49 | reflections 50 | 0.9.11 51 | 52 | 53 | com.pjmike 54 | common 55 | 0.0.1-SNAPSHOT 56 | compile 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-maven-plugin 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/com/pjmike/server/HelloServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.server; 2 | 3 | import com.pjmike.common.service.HelloService; 4 | import org.springframework.stereotype.Service; 5 | 6 | /** 7 | * @description: 8 | * @author: pjmike 9 | * @create: 2019/04/08 16:44 10 | */ 11 | @Service 12 | public class HelloServiceImpl implements HelloService { 13 | @Override 14 | public String hello(String name) { 15 | return "hello, " + name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/com/pjmike/server/ServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.server; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ServerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ServerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/com/pjmike/server/handler/ServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.server.handler; 2 | 3 | import com.pjmike.common.protocol.RpcRequest; 4 | import com.pjmike.common.protocol.RpcResponse; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.BeansException; 10 | import org.springframework.cglib.reflect.FastClass; 11 | import org.springframework.cglib.reflect.FastMethod; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.context.ApplicationContextAware; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.lang.reflect.InvocationTargetException; 17 | 18 | /** 19 | * @description: 20 | * @author: pjmike 21 | * @create: 2019/04/07 17:41 22 | */ 23 | @Component 24 | @Slf4j 25 | @ChannelHandler.Sharable 26 | public class ServerHandler extends SimpleChannelInboundHandler implements ApplicationContextAware { 27 | private ApplicationContext applicationContext; 28 | @Override 29 | protected void channelRead0(ChannelHandlerContext ctx, RpcRequest msg) { 30 | RpcResponse rpcResponse = new RpcResponse(); 31 | rpcResponse.setRequestId(msg.getRequestId()); 32 | try { 33 | Object handler = handler(msg); 34 | log.info("获取返回结果: {} ", handler); 35 | rpcResponse.setResult(handler); 36 | } catch (Throwable throwable) { 37 | rpcResponse.setError(throwable.toString()); 38 | throwable.printStackTrace(); 39 | } 40 | ctx.writeAndFlush(rpcResponse); 41 | } 42 | 43 | /** 44 | * 服务端使用代理处理请求 45 | * 46 | * @param request 47 | * @return 48 | */ 49 | private Object handler(RpcRequest request) throws ClassNotFoundException, InvocationTargetException { 50 | //使用Class.forName进行加载Class文件 51 | Class clazz = Class.forName(request.getClassName()); 52 | Object serviceBean = applicationContext.getBean(clazz); 53 | log.info("serviceBean: {}",serviceBean); 54 | Class serviceClass = serviceBean.getClass(); 55 | log.info("serverClass:{}",serviceClass); 56 | String methodName = request.getMethodName(); 57 | 58 | Class[] parameterTypes = request.getParameterTypes(); 59 | Object[] parameters = request.getParameters(); 60 | 61 | //使用CGLIB Reflect 62 | FastClass fastClass = FastClass.create(serviceClass); 63 | FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes); 64 | log.info("开始调用CGLIB动态代理执行服务端方法..."); 65 | return fastMethod.invoke(serviceBean, parameters); 66 | } 67 | 68 | @Override 69 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 70 | this.applicationContext = applicationContext; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/com/pjmike/server/netty/NettyServer.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.server.netty; 2 | 3 | 4 | import com.pjmike.common.protocol.RpcDecoder; 5 | import com.pjmike.common.protocol.RpcEncoder; 6 | import com.pjmike.common.protocol.RpcRequest; 7 | import com.pjmike.common.protocol.RpcResponse; 8 | import com.pjmike.common.protocol.serialize.JSONSerializer; 9 | import com.pjmike.common.protocol.serialize.KryoSerializer; 10 | import com.pjmike.common.registry.ServiceDiscover; 11 | import com.pjmike.common.registry.ServiceRegistry; 12 | import com.pjmike.common.registry.zookeeper.ZkServiceDiscover; 13 | import com.pjmike.common.registry.zookeeper.ZkServiceRegistry; 14 | import com.pjmike.server.handler.ServerHandler; 15 | import io.netty.bootstrap.ServerBootstrap; 16 | import io.netty.channel.*; 17 | import io.netty.channel.nio.NioEventLoopGroup; 18 | import io.netty.channel.socket.SocketChannel; 19 | import io.netty.channel.socket.nio.NioServerSocketChannel; 20 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 21 | import io.netty.util.concurrent.Future; 22 | import io.netty.util.concurrent.GenericFutureListener; 23 | import lombok.extern.slf4j.Slf4j; 24 | import org.springframework.beans.factory.InitializingBean; 25 | import org.springframework.beans.factory.annotation.Autowired; 26 | import org.springframework.stereotype.Component; 27 | 28 | import javax.annotation.PreDestroy; 29 | import java.net.InetSocketAddress; 30 | 31 | /** 32 | * @description: 33 | * @author: pjmike 34 | * @create: 2019/04/07 17:21 35 | */ 36 | @Component 37 | @Slf4j 38 | public class NettyServer implements InitializingBean { 39 | private EventLoopGroup boss = null; 40 | private EventLoopGroup worker = null; 41 | @Autowired 42 | private ServerHandler serverHandler; 43 | @Override 44 | public void afterPropertiesSet() throws Exception { 45 | ServiceRegistry registry = new ZkServiceRegistry("127.0.0.1:2181"); 46 | start(registry); 47 | } 48 | 49 | public void start(ServiceRegistry registry) throws Exception { 50 | boss = new NioEventLoopGroup(); 51 | worker = new NioEventLoopGroup(); 52 | ServerBootstrap serverBootstrap = new ServerBootstrap(); 53 | serverBootstrap.group(boss, worker) 54 | .channel(NioServerSocketChannel.class) 55 | .option(ChannelOption.SO_BACKLOG, 1024) 56 | .childHandler(new ChannelInitializer() { 57 | @Override 58 | protected void initChannel(SocketChannel ch) throws Exception { 59 | ChannelPipeline pipeline = ch.pipeline(); 60 | pipeline.addLast(new LengthFieldBasedFrameDecoder(65535, 0, 4)); 61 | pipeline.addLast(new RpcEncoder(RpcResponse.class, new JSONSerializer())); 62 | pipeline.addLast(new RpcDecoder(RpcRequest.class, new JSONSerializer())); 63 | pipeline.addLast(serverHandler); 64 | 65 | } 66 | }); 67 | bind(serverBootstrap, 8888); 68 | // registry.registry("127.0.0.1:8888"); 69 | } 70 | 71 | /** 72 | * 如果端口绑定失败,端口数+1,重新绑定 73 | * 74 | * @param serverBootstrap 75 | * @param port 76 | */ 77 | public void bind(final ServerBootstrap serverBootstrap,int port) { 78 | serverBootstrap.bind(port).addListener(future -> { 79 | if (future.isSuccess()) { 80 | log.info("端口[ {} ] 绑定成功",port); 81 | } else { 82 | log.error("端口[ {} ] 绑定失败", port); 83 | bind(serverBootstrap, port + 1); 84 | } 85 | }); 86 | } 87 | 88 | @PreDestroy 89 | public void destory() throws InterruptedException { 90 | boss.shutdownGracefully().sync(); 91 | worker.shutdownGracefully().sync(); 92 | log.info("关闭Netty"); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /rpc-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rpc-server/src/test/java/com/pjmike/server/ServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.pjmike.server; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ServerApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------