From f571f06780a4093419f9cbf952bd91aedca5cdd2 Mon Sep 17 00:00:00 2001 From: yukun Date: Sun, 30 May 2021 18:35:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=8Egitee=E6=90=AC=E8=BF=90=E8=BF=87?= =?UTF-8?q?=E6=9D=A5=EF=BC=8C=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 69 ++++++- pom.xml | 164 +++++++++++++++ rpc-client/pom.xml | 48 +++++ .../com/appleyk/rpc/client/RpcClient.java | 103 ++++++++++ .../client/RpcClientChannelInitializer.java | 34 ++++ .../rpc/client/annotion/EnableSeven.java | 26 +++ .../rpc/client/annotion/RpcAutowired.java | 22 ++ .../meta/RpcServiceAnnotationMetaData.java | 24 +++ .../com/appleyk/rpc/client/aop/RpcAop.java | 58 ++++++ .../autoconfigure/RpcClientAutoConfigure.java | 69 +++++++ .../proxy/RpcServiceInvocationHandler.java | 101 ++++++++++ .../factorybean/RpcServiceFactoryBean.java | 65 ++++++ .../servicebean/RpcServiceBeanProxy.java | 41 ++++ .../client/registrar/InstantiationAware.java | 71 +++++++ .../RpcServiceBeanDefinitionRegistrar.java | 62 ++++++ .../sanner/RpcServiceBeanPathScanner.java | 56 ++++++ .../rpc/client/util/SpringContextUtils.java | 35 ++++ .../main/resources/META-INF/spring.factories | 2 + rpc-common/pom.xml | 68 +++++++ .../appleyk/rpc/common/codec/RpcDecoder.java | 61 ++++++ .../appleyk/rpc/common/codec/RpcEncoder.java | 37 ++++ .../common/excp/RpcApiNotFoundException.java | 15 ++ .../excp/RpcServiceBeanNotFoundException.java | 15 ++ .../common/protocol/RpcMessageProtocol.java | 33 +++ .../appleyk/rpc/common/result/HttpResult.java | 129 ++++++++++++ .../appleyk/rpc/common/util/DateUtils.java | 26 +++ .../appleyk/rpc/common/util/GeneralUtils.java | 65 ++++++ .../com/appleyk/rpc/common/util/IdUtils.java | 15 ++ .../appleyk/rpc/common/util/JsonUtils.java | 167 +++++++++++++++ .../appleyk/rpc/common/util/LoggerUtils.java | 54 +++++ .../common/util/PbfSerializationUtils.java | 87 ++++++++ rpc-core/pom.xml | 42 ++++ .../rpc/core/annotation/RpcService.java | 48 +++++ .../java/com/appleyk/rpc/core/model/User.java | 13 ++ .../rpc/core/model/result/RpcRequest.java | 30 +++ .../rpc/core/model/result/RpcResponse.java | 30 +++ rpc-core/src/test/java/FastClassTest.java | 59 ++++++ rpc-registry/pom.xml | 23 +++ rpc-registry/rpc-registry-api/pom.xml | 31 +++ .../rpc/registry/ServiceDiscovery.java | 20 ++ .../appleyk/rpc/registry/ServiceRegistry.java | 22 ++ rpc-registry/rpc-registry-zookeeper/pom.xml | 71 +++++++ .../zk/config/ZookeeperAutoConfig.java | 53 +++++ .../registry/zk/config/ZookeeperProperty.java | 55 +++++ .../registry/zk/impl/ZookeeperCenter.java | 74 +++++++ .../appleyk/registry/zk/util/ZkApiUtils.java | 190 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 1 + rpc-server/pom.xml | 44 ++++ .../com/appleyk/rpc/server/RpcServer.java | 159 +++++++++++++++ .../server/RpcServerChannelInitializer.java | 46 +++++ .../appleyk/rpc/server/RpcServerHandler.java | 116 +++++++++++ .../autoconfigure/RpcServerAutoConfigure.java | 20 ++ .../main/resources/META-INF/spring.factories | 1 + rpc-use-case/pom.xml | 24 +++ rpc-use-case/rpc-demo-api/pom.xml | 29 +++ .../com/appleyk/rpc/api/CacheService.java | 11 + .../com/appleyk/rpc/api/OrderService.java | 21 ++ .../java/com/appleyk/rpc/model/Order.java | 69 +++++++ rpc-use-case/rpc-demo-consumer/pom.xml | 80 ++++++++ .../appleyk/rpc/sample/client/ClientApp.java | 28 +++ .../rpc/sample/client/config/RpcConfig.java | 48 +++++ .../client/controller/AopController.java | 32 +++ .../client/controller/CacheController.java | 90 +++++++++ .../client/controller/OrderController.java | 76 +++++++ .../excep/ExceptionControllerAdvice.java | 25 +++ .../src/main/resources/application.yml | 9 + .../src/main/resources/banner.txt | 12 ++ ...46\225\210\346\236\234\345\233\276_01.jpg" | Bin 0 -> 214776 bytes .../src/main/resources/logback-spring.xml | 46 +++++ .../src/test/java/NettyByteBufTest.java | 104 ++++++++++ .../src/test/java/RpcServiceBeanTest.java | 62 ++++++ .../src/test/java/ZkCenterTest.java | 55 +++++ rpc-use-case/rpc-demo-provider/pom.xml | 62 ++++++ .../appleyk/rpc/sample/server/ServerApp.java | 28 +++ .../server/impl/MongoDbCacheServiceImpl.java | 49 +++++ .../sample/server/impl/OrderServiceImpl.java | 55 +++++ .../server/impl/RedisCacheServiceImpl.java | 46 +++++ .../src/main/resources/application.yml | 9 + .../src/main/resources/banner.txt | 11 + .../src/main/resources/logback-spring.xml | 46 +++++ .../src/test/java/ClassInterfacesTest.java | 16 ++ 81 files changed, 4112 insertions(+), 1 deletion(-) create mode 100644 pom.xml create mode 100644 rpc-client/pom.xml create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/RpcClient.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/RpcClientChannelInitializer.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/annotion/EnableSeven.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/annotion/RpcAutowired.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/annotion/meta/RpcServiceAnnotationMetaData.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/aop/RpcAop.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/autoconfigure/RpcClientAutoConfigure.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/proxy/RpcServiceInvocationHandler.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/proxy/factorybean/RpcServiceFactoryBean.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/proxy/servicebean/RpcServiceBeanProxy.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/registrar/InstantiationAware.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/registrar/RpcServiceBeanDefinitionRegistrar.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/sanner/RpcServiceBeanPathScanner.java create mode 100644 rpc-client/src/main/java/com/appleyk/rpc/client/util/SpringContextUtils.java create mode 100644 rpc-client/src/main/resources/META-INF/spring.factories create mode 100644 rpc-common/pom.xml create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/codec/RpcDecoder.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/codec/RpcEncoder.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/excp/RpcApiNotFoundException.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/excp/RpcServiceBeanNotFoundException.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/protocol/RpcMessageProtocol.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/result/HttpResult.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/util/DateUtils.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/util/GeneralUtils.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/util/IdUtils.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/util/JsonUtils.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/util/LoggerUtils.java create mode 100644 rpc-common/src/main/java/com/appleyk/rpc/common/util/PbfSerializationUtils.java create mode 100644 rpc-core/pom.xml create mode 100644 rpc-core/src/main/java/com/appleyk/rpc/core/annotation/RpcService.java create mode 100644 rpc-core/src/main/java/com/appleyk/rpc/core/model/User.java create mode 100644 rpc-core/src/main/java/com/appleyk/rpc/core/model/result/RpcRequest.java create mode 100644 rpc-core/src/main/java/com/appleyk/rpc/core/model/result/RpcResponse.java create mode 100644 rpc-core/src/test/java/FastClassTest.java create mode 100644 rpc-registry/pom.xml create mode 100644 rpc-registry/rpc-registry-api/pom.xml create mode 100644 rpc-registry/rpc-registry-api/src/main/java/com/appleyk/rpc/registry/ServiceDiscovery.java create mode 100644 rpc-registry/rpc-registry-api/src/main/java/com/appleyk/rpc/registry/ServiceRegistry.java create mode 100644 rpc-registry/rpc-registry-zookeeper/pom.xml create mode 100644 rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/config/ZookeeperAutoConfig.java create mode 100644 rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/config/ZookeeperProperty.java create mode 100644 rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/impl/ZookeeperCenter.java create mode 100644 rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/util/ZkApiUtils.java create mode 100644 rpc-registry/rpc-registry-zookeeper/src/main/resources/META-INF/spring.factories create mode 100644 rpc-server/pom.xml create mode 100644 rpc-server/src/main/java/com/appleyk/rpc/server/RpcServer.java create mode 100644 rpc-server/src/main/java/com/appleyk/rpc/server/RpcServerChannelInitializer.java create mode 100644 rpc-server/src/main/java/com/appleyk/rpc/server/RpcServerHandler.java create mode 100644 rpc-server/src/main/java/com/appleyk/rpc/server/autoconfigure/RpcServerAutoConfigure.java create mode 100644 rpc-server/src/main/resources/META-INF/spring.factories create mode 100644 rpc-use-case/pom.xml create mode 100644 rpc-use-case/rpc-demo-api/pom.xml create mode 100644 rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/api/CacheService.java create mode 100644 rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/api/OrderService.java create mode 100644 rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/model/Order.java create mode 100644 rpc-use-case/rpc-demo-consumer/pom.xml create mode 100644 rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/ClientApp.java create mode 100644 rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/config/RpcConfig.java create mode 100644 rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/AopController.java create mode 100644 rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/CacheController.java create mode 100644 rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/OrderController.java create mode 100644 rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/excep/ExceptionControllerAdvice.java create mode 100644 rpc-use-case/rpc-demo-consumer/src/main/resources/application.yml create mode 100644 rpc-use-case/rpc-demo-consumer/src/main/resources/banner.txt create mode 100644 "rpc-use-case/rpc-demo-consumer/src/main/resources/images/\350\247\243\347\240\201\345\231\250\345\267\245\344\275\234\302\267\346\225\210\346\236\234\345\233\276_01.jpg" create mode 100644 rpc-use-case/rpc-demo-consumer/src/main/resources/logback-spring.xml create mode 100644 rpc-use-case/rpc-demo-consumer/src/test/java/NettyByteBufTest.java create mode 100644 rpc-use-case/rpc-demo-consumer/src/test/java/RpcServiceBeanTest.java create mode 100644 rpc-use-case/rpc-demo-consumer/src/test/java/ZkCenterTest.java create mode 100644 rpc-use-case/rpc-demo-provider/pom.xml create mode 100644 rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/ServerApp.java create mode 100644 rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/MongoDbCacheServiceImpl.java create mode 100644 rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/OrderServiceImpl.java create mode 100644 rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/RedisCacheServiceImpl.java create mode 100644 rpc-use-case/rpc-demo-provider/src/main/resources/application.yml create mode 100644 rpc-use-case/rpc-demo-provider/src/main/resources/banner.txt create mode 100644 rpc-use-case/rpc-demo-provider/src/main/resources/logback-spring.xml create mode 100644 rpc-use-case/rpc-demo-provider/src/test/java/ClassInterfacesTest.java diff --git a/README.md b/README.md index 5d3eb06..7df844b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,69 @@ # seven-rpc -基于Spring+Netty框架构建的简版RPC个人框架 + +#### 介绍 + +``` + 用7这个数字,是因为我儿子是七夕生的。开源的rpc框架有很多,公司的、个人的,优秀的很多。那为什么我又要写个呢? +因为spring、springboot、因为分布式服务协调器zk、因为nio,或者说是因为netty。看了spring的源码,也clone了项目 +调了很长一段时间,总想上手一个"框架"来练练手,期间也手写过简易版的mybaits、spring-mvc框架,很不过瘾。用netty +也编码了一段时间,总感觉少了点什么,于是乎,便决定利用平时空闲之余,串联下几大技术,自己也写一个rpc框架,切身感受 +一下站在巨人肩膀上撸代码的兴奋和快感! + + 首先一个rpc框架基本该有的功能都得有吧,其次模块得鲜明、代码思路得清晰,让人一看就懂:“哦吼,原来 +Spring的BeanPostProcessor可以用的这么嗨皮啊;原来反射写起来也可以让人爱不释手啊;原来动态代理的时机可以结合bean的 +生命周期来玩啊;原来接口类型的bean注入时也是可以往ioc容器里注册beandefinition的啊;原来FactoryBean是这样用的啊; +原来netty的ByteBuf居然这么好用的啊;原来消息编·解码器这么写就可以解决TCP的拆包或粘包的问题啊;原来netty api用起来 +是如此轻松嗨皮的啊;原来用zk搭建一个rpc服务的注册与发现中心只需一个工具类和为数不多行的代码就可以轻松办到的啊...etc” + +``` + +#### 软件架构 + +![Seven-Rpc架构图-简](https://gitee.com/appleyk/seven-rpc/raw/master/src/main/resources/static/images/%E6%9E%B6%E6%9E%841.png) + +![Seven-Rpc架构图-详](https://gitee.com/appleyk/seven-rpc/raw/master/src/main/resources/static/images/%E6%9E%B6%E6%9E%842.jpg) + + + +#### 安装`使用教程 + +1. git clone https://gitee.com/appleyk/seven-rpc +2. idea导入父pom,配置maven,并下载相关依赖 +3. idea编译项目,检查是否报错 +4. 运行rpc-use-case模块中对应的案例启动类即可 +5. 具体使用可参考博客:https://blog.csdn.net/Appleyk/article/details/117392129 + +#### 模块说明 + +1. rpc-common : 定义通用工具类和netty消息编`解码器 +2. rpc-core : 定义核心业务模型和公共自定义注解re +3. rpc-registry: 服务注册与发现中心模块,主要是基于zookeeper curator框架实现的 +4. rpc-client : rpc-netty客户端模块,主要对接口进行动态代理,并封装请求,与rpc-netty服务端建立连接并进行通信 +5. rpc-server : rpc-netty服务端模块,主要对接口实现模块中的可用服务进行一个注册,信息写入到zk中,同时启动一个netty服务,对客户端连接进行监听 +6. rpc-use-case : rpc功能测试用例模块,其又分为3个模块 + 6.1. rpc-demo-api: 定义公共接口 + 6.2. rpc-demo-consumer: 服务消费方,主要用来测试rpc服务能力的,依赖rpc-demo-api模块 + 6.3. rpc-demo-provider: 服务提供方,用来满足rpc接口功能实现的,依赖rpc-demo-api模块 + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 +4. 新建 Pull Request +5. 如果你提交的request对项目有用,我会merge的 + + +#### 番外 + +1. Spring·Bean全生命周期(简图) + +![Spring·Bean完整生命周期](https://gitee.com/appleyk/seven-rpc/raw/master/src/main/resources/static/images/Spring%C2%B7Bean%E5%AE%8C%E6%95%B4%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.png) + +2. ZK注册的服务和API节点 + +![ZK服务节点](https://gitee.com/appleyk/seven-rpc/raw/master/src/main/resources/static/images/ZK%E6%B3%A8%E5%86%8C%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%92%8CAPI%E8%8A%82%E7%82%B9.jpg) + +3. Netty Reactor模型 + +![Netty Reactor设计模型](https://gitee.com/appleyk/seven-rpc/raw/master/src/main/resources/static/images/netty%C2%B7reactor.png) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ced066b --- /dev/null +++ b/pom.xml @@ -0,0 +1,164 @@ + + + + 4.0.0 + com.appleyk + seven-rpc + pom + 0.1.1-SNAPSHOT + 手写RPC框架·父工程 + + rpc-common + rpc-registry + rpc-client + rpc-server + rpc-core + rpc-use-case + + + + + 1.8 + 1.8 + UTF-8 + UTF-8 + 1.8 + true + + + 2.4.0 + 5.3.1 + 2.11.0 + 4.1.42.Final + 1.18.6 + 8.0.22 + 5.1.0 + 1.1.6 + 3.2 + + + + + + + + + + + org.springframework + spring-context + ${spring.framework.version} + + + + + org.springframework.boot + spring-boot-starter + ${springboot.version} + + + + + org.springframework.boot + spring-boot-starter-web + ${springboot.version} + + + + + + org.springframework.boot + spring-boot-autoconfigure + ${springboot.version} + + + + + org.springframework.boot + spring-boot-configuration-processor + ${springboot.version} + + + + + org.springframework.boot + spring-boot-starter-validation + ${springboot.version} + + + + + org.springframework.boot + spring-boot-starter-test + ${springboot.version} + test + + + + + org.springframework.boot + spring-boot-starter-aop + ${springboot.version} + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + io.netty + netty-all + ${netty.version} + + + + + org.projectlombok + lombok + ${lombok.version} + + + + + mysql + mysql-connector-java + ${mysql8.version} + + + + + org.apache.curator + curator-framework + ${curator.version} + + + + + com.dyuproject.protostuff + protostuff-core + ${protostuff.version} + + + + com.dyuproject.protostuff + protostuff-runtime + ${protostuff.version} + + + + + org.objenesis + objenesis + ${objenesis.version} + + + + + + \ No newline at end of file diff --git a/rpc-client/pom.xml b/rpc-client/pom.xml new file mode 100644 index 0000000..0cb0c59 --- /dev/null +++ b/rpc-client/pom.xml @@ -0,0 +1,48 @@ + + + + + seven-rpc + com.appleyk + 0.1.1-SNAPSHOT + + + 4.0.0 + rpc-client + rpc客户端,实现代理转发、连接(客户端也可以做服务注册) + + + + com.appleyk + rpc-common + ${project.version} + + + com.appleyk + rpc-registry-api + ${project.version} + + + org.springframework.boot + spring-boot-starter-aop + + + + + ${artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/RpcClient.java b/rpc-client/src/main/java/com/appleyk/rpc/client/RpcClient.java new file mode 100644 index 0000000..1e20882 --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/RpcClient.java @@ -0,0 +1,103 @@ +package com.appleyk.rpc.client; + +import com.appleyk.rpc.common.util.*; +import com.appleyk.rpc.core.model.result.RpcRequest; +import com.appleyk.rpc.core.model.result.RpcResponse; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; + +import java.net.InetAddress; +import java.util.Date; + +/** + *

Rpc客户端 (负责构建netty客户端启动类,向rpc服务端发送请求)

+ * Rpc客户端通道处理器,主要负责和服务端建立通信,发送请求然后接收服务端发回来的响应对象并进行处理 + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 16:19 2021/5/19 + */ +public class RpcClient extends SimpleChannelInboundHandler { + + private String ip; + private int port; + private RpcResponse response; + + public RpcClient(String ip, int port) { + this.ip = ip; + this.port = port; + } + + public RpcClient(int port) { + this.ip = "127.0.0.1"; + this.port = port; + } + + /** + * 发送(API)请求 + * @param request rpc封装的请求对象 + * @return rpc封装的请求响应对象 + */ + public RpcResponse send(RpcRequest request){ + /*默认cpu核数*2*/ + EventLoopGroup worker = new NioEventLoopGroup(); + try{ + Bootstrap client = new Bootstrap(); + client.group(worker) + .channel(NioSocketChannel.class) + .handler(new RpcClientChannelInitializer(this)); + /*等待异步执行完,拿到异步结果(变异步为同步)*/ + ChannelFuture channelFuture = client.connect(ip, port).sync(); + System.out.println("Rpc-Client,已与服务器建立连接,接下来准备发送数据..."); + /*获取客户端和服务端的通道*/ + Channel channel = channelFuture.channel(); + /*往通道里发送数据 -- RpcRequest对象*/ + channel.writeAndFlush(request); + channel.closeFuture().sync(); + }catch (InterruptedException e){ + response.setE(e); + response.setDate(new Date()); + }finally { + System.out.println("#响应的结果:"+ JsonUtils.parserJson(response)); + /*关闭线程池及清理内存空间,只发一次请求,不管失败还是异常,结果return之前,都要把线程池给关闭了*/ + worker.shutdownGracefully(); + return response; + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + Channel serverChannel = ctx.channel(); + LoggerUtils.error("服务端:{}发送来的响应处理异常,{}\n",serverChannel.remoteAddress(),cause.getMessage()); + /*关闭通道处理器上下文*/ + ctx.close(); + } + + /**接收解码器的结果,并对响应做出处理 -- 读取*/ + @Override + protected void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception { + System.out.printf("接收来自服务端:{%s}的响应,时间:%s\n",ctx.channel().remoteAddress(), DateUtils.dateNow2Str()); + this.response = response; + /*如果响应成功返回,则断开与server端的通道*/ + ctx.close(); + } + + /**简单测试*/ + public static void main(String[] args) throws Exception{ + InetAddress addr = InetAddress.getLocalHost(); + String ip = addr.getHostAddress(); + RpcClient client = new RpcClient(GeneralUtils.isEmpty(ip) ? "127.0.0.1":ip,8077); + RpcRequest request = RpcRequest.builder() + .requestId(IdUtils.getId()) + .interfaceName("com.appleyk.rpc.api.CacheService") + .type("mongodb") + .methodName("save") + .parameterTypes(new Class[]{String.class, Object.class}) + .parameters(new Object[]{"name", "Appleyk"}) + .build(); + client.send(request); + } +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/RpcClientChannelInitializer.java b/rpc-client/src/main/java/com/appleyk/rpc/client/RpcClientChannelInitializer.java new file mode 100644 index 0000000..0f4d3c0 --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/RpcClientChannelInitializer.java @@ -0,0 +1,34 @@ +package com.appleyk.rpc.client; + +import com.appleyk.rpc.common.codec.RpcDecoder; +import com.appleyk.rpc.common.codec.RpcEncoder; +import com.appleyk.rpc.core.model.result.RpcRequest; +import com.appleyk.rpc.core.model.result.RpcResponse; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; + +/** + *

Rpc客户端通道初始化器,用来给通信管道设置各种处理器的

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @date created on 下午9:37 2021/5/22 + */ +public class RpcClientChannelInitializer extends ChannelInitializer { + + private final RpcClient client; + + public RpcClientChannelInitializer(RpcClient client) { + this.client = client; + } + + @Override + protected void initChannel(SocketChannel sc) throws Exception { + ChannelPipeline pipeline = sc.pipeline(); + pipeline.addLast(new RpcEncoder(RpcRequest.class)); + pipeline.addLast(new RpcDecoder(RpcResponse.class)); + pipeline.addLast(client); + } +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/annotion/EnableSeven.java b/rpc-client/src/main/java/com/appleyk/rpc/client/annotion/EnableSeven.java new file mode 100644 index 0000000..a51ac17 --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/annotion/EnableSeven.java @@ -0,0 +1,26 @@ +package com.appleyk.rpc.client.annotion; + +import com.appleyk.rpc.client.registrar.RpcServiceBeanDefinitionRegistrar; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

Rpc接口包扫描·注解

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 17:35 2021/5/19 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Import(RpcServiceBeanDefinitionRegistrar.class) // 在这个bean定义注册类里,实现接口的动态代理 +public @interface EnableSeven { + /*你需要扫描的beans所在的根包*/ + String scanBasePackages() default ""; +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/annotion/RpcAutowired.java b/rpc-client/src/main/java/com/appleyk/rpc/client/annotion/RpcAutowired.java new file mode 100644 index 0000000..ec8a63e --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/annotion/RpcAutowired.java @@ -0,0 +1,22 @@ +package com.appleyk.rpc.client.annotion; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

Rpc 自动注入 注解

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @date created on 下午10:23 2021/5/20 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface RpcAutowired { + String value() default ""; // 注入的bean名称,比如cache可以是mongodb或redis实现 + String version() default ""; // 注入的bean(服务、接口)的版本 + String group() default ""; // 注入bean(服务、接口)的组 +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/annotion/meta/RpcServiceAnnotationMetaData.java b/rpc-client/src/main/java/com/appleyk/rpc/client/annotion/meta/RpcServiceAnnotationMetaData.java new file mode 100644 index 0000000..bd7eefa --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/annotion/meta/RpcServiceAnnotationMetaData.java @@ -0,0 +1,24 @@ +package com.appleyk.rpc.client.annotion.meta; + +import lombok.Builder; +import lombok.Data; + +/** + *

rpc service bean 注入元数据

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 14:19 2021/5/26 + */ +@Data +@Builder +public class RpcServiceAnnotationMetaData { + /**bean的类型*/ + private String type; + /**bean的版本*/ + private String version; + /**bean的(服务)组*/ + private String group; +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/aop/RpcAop.java b/rpc-client/src/main/java/com/appleyk/rpc/client/aop/RpcAop.java new file mode 100644 index 0000000..c9a122b --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/aop/RpcAop.java @@ -0,0 +1,58 @@ +package com.appleyk.rpc.client.aop; + +import com.appleyk.rpc.common.result.HttpResult; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; +import org.springframework.util.StopWatch; + +/** + *

AOP

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 16:23 2021/5/19 + */ +@Component +@Aspect // 标记这个类是切面 +public class RpcAop { + + /** + * 定义切点 + * 第一个星号,表示返回方法任返回意类型 + * 第一个..*,表示匹配appleyk包下面的任意个包 + * 第二个星号,表示匹配appleyk下面的任意包 + * 第三个星号,表示匹配controller包下面,以Controller结尾的任意前缀匹配 + * 第四个星号,表示匹配XXXController类下面的任意方法 + * 最后括号里面的两个..,表示任意参数 + */ + @Pointcut("execution(* com.appleyk.rpc..*.controller.*Controller.*(..))") + public void rpcPointcut(){} + + @Around("rpcPointcut()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ + /*获取切入点签名(一个类的一个方法就是一个切点)*/ + Signature signature = joinPoint.getSignature(); + /*获取切入点签名的类型,就是方法所在的类*/ + Class declaringType = signature.getDeclaringType(); + System.out.printf("AOP - {%s}.方法{%s}执行前\n",declaringType,signature.getName()); + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + Object proceed = joinPoint.proceed(); + stopWatch.stop(); + System.out.printf("AOP - {%s}.方法{%s}执行耗时统计:{%d}ms\n",declaringType,signature.getName(), + stopWatch.getTotalTimeMillis()); + + if (declaringType.getName().endsWith("AopController")) { + return HttpResult.ok(String.format("方法{%s-%s}执行耗时统计:{%d}ms",declaringType,signature.getName(), + stopWatch.getTotalTimeMillis())); + } + + return proceed; + } +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/autoconfigure/RpcClientAutoConfigure.java b/rpc-client/src/main/java/com/appleyk/rpc/client/autoconfigure/RpcClientAutoConfigure.java new file mode 100644 index 0000000..ddc86bc --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/autoconfigure/RpcClientAutoConfigure.java @@ -0,0 +1,69 @@ +package com.appleyk.rpc.client.autoconfigure; + +import com.appleyk.rpc.client.annotion.RpcAutowired; +import com.appleyk.rpc.client.annotion.meta.RpcServiceAnnotationMetaData; +import com.appleyk.rpc.client.proxy.servicebean.RpcServiceBeanProxy; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.ReflectionUtils; + +/** + *

自动装配rpc-client包中的bean

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 17:13 2021/5/19 + */ +@Configuration +@ComponentScan("com.appleyk.rpc.client") +public class RpcClientAutoConfigure { + public RpcClientAutoConfigure() { + System.out.println("=== RPC-CLIENT complete automatic assembly !==="); + } + + @Bean + public BeanPostProcessor beanPostProcessor(){ + return new BeanPostProcessor() { + + /**在bean完成初始化之前(注意,这时候bean已经完成实例化、且已经状态属性了)*/ + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + Class objClz; + /*先判断下bean是不是Spring生成的代理对象*/ + if (AopUtils.isAopProxy(bean)){ + /*获取目标对象*/ + objClz = AopUtils.getTargetClass(bean); + }else { + /*如果不是proxy对象,那就获取bean的class,主要用于下面的反射*/ + objClz = bean.getClass(); + } + try{ + ReflectionUtils.doWithLocalFields(objClz,field -> { + RpcAutowired rpcAutowired = field.getAnnotation(RpcAutowired.class); + /*如果标注了这个注解,则表示bean注入的方式可以多样化*/ + if (rpcAutowired != null){ + field.setAccessible(true); + Class type = field.getType(); + RpcServiceAnnotationMetaData metaData = RpcServiceAnnotationMetaData.builder() + .type(rpcAutowired.value()) + .group(rpcAutowired.group()) + .version(rpcAutowired.version()).build(); + field.set(bean, RpcServiceBeanProxy.getObject(type,metaData)); + } + }); + }catch (Exception e){ + throw new BeanCreationException(beanName, e); + } + + return bean; + } + }; + } +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/proxy/RpcServiceInvocationHandler.java b/rpc-client/src/main/java/com/appleyk/rpc/client/proxy/RpcServiceInvocationHandler.java new file mode 100644 index 0000000..b2cdd66 --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/proxy/RpcServiceInvocationHandler.java @@ -0,0 +1,101 @@ +package com.appleyk.rpc.client.proxy; + + +import com.appleyk.rpc.client.RpcClient; +import com.appleyk.rpc.client.annotion.meta.RpcServiceAnnotationMetaData; +import com.appleyk.rpc.client.util.SpringContextUtils; +import com.appleyk.rpc.common.util.GeneralUtils; +import com.appleyk.rpc.common.util.IdUtils; +import com.appleyk.rpc.common.util.LoggerUtils; +import com.appleyk.rpc.core.model.result.RpcRequest; +import com.appleyk.rpc.core.model.result.RpcResponse; +import com.appleyk.rpc.registry.ServiceDiscovery; +import org.springframework.context.ApplicationContext; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +/** + *

动态代理,真正实现被代理"对象"的方法调用的处理器

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @date created on 下午9:30 2021/5/19 + */ +public class RpcServiceInvocationHandler implements InvocationHandler{ + + /*服务发现对象,从ioc容器里取*/ + private ServiceDiscovery serviceDiscovery; + + private RpcServiceAnnotationMetaData metaData; + + public RpcServiceInvocationHandler(){ + } + + public RpcServiceInvocationHandler(RpcServiceAnnotationMetaData metaData){ + this.metaData = metaData; + } + + public RpcServiceInvocationHandler(ApplicationContext context){ + ServiceDiscovery discovery = context.getBean(ServiceDiscovery.class); + if (discovery==null){ + throw new RuntimeException("serviceRegistry bean not found !"); + } + this.serviceDiscovery = discovery; + } + + /** + * 整个RPC服务消费方·最最最重要的地方了,也是接口"实例"被代理后,就具有了"本地调用"的奥秘所在 + * @param proxy 代理对象 + * @param method 方法 + * @param args 参数 + * @return 方法执行结果 + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + /*如果是当前类的实例自己调用自己的invoke方法的话,那就直接返回*/ + if (method.getDeclaringClass().equals(this)){ + return method.invoke(proxy,args); + } + + if (serviceDiscovery == null){ + this.serviceDiscovery = (ServiceDiscovery) SpringContextUtils.getBean(ServiceDiscovery.class); + } + + /*1、获取方法所在类(其实是接口)的名称(这里是全限定名,即包名+类名)*/ + String apiName= method.getDeclaringClass().getName(); + String serverAddress = serviceDiscovery.discover(apiName); + /*2、验证服务地址合法性*/ + if (GeneralUtils.isEmpty(serverAddress)|| serverAddress.split(":").length != 2){ + LoggerUtils.error("未发现{}对应有可用的服务!,请稍后再试",apiName); + return null; + } + /*3、获取接口类型*/ + String apiType = metaData != null ? metaData.getType() : ""; + /*4、分割服务地址,分别获取ip和port*/ + String[] host = serverAddress.split(":"); + String ip = host[0]; + int port = Integer.parseInt(host[1]); + /*5、基于ip和port,构建client(netty)*/ + RpcClient client = new RpcClient(ip,port); + /*6、基于被代理的对象的信息构建请求请求对象*/ + long requestId = IdUtils.getId(); + RpcRequest request = RpcRequest.builder() + .requestId(requestId) + .interfaceName(apiName) + .methodName(method.getName()) + .type(apiType) // 这个很关键,因为接口可能有多个实现类,ZK上一个节点就是一个服务,区别接口实现类的唯一标识就是这个type + .parameterTypes(method.getParameterTypes()) + .parameters(args) + .build(); + /*7、发送请求(与netty服务端建立连接,并往双方的通信通道里传输数据,然后服务端解码处理后返回响应结果)*/ + RpcResponse response = client.send(request); + /*8、验证请求ID是不是合法的,就好比,你要点一杯咖啡,服务员却给你来了一杯冰红茶*/ + if (requestId != response.getRequestId()){ + throw new RuntimeException("请求与响应的requestId不对应,无法确定结果是不是合法!"); + } + /*9、被代理的对象是接口类型的bean,其无法实例化,更谈不上有返回值了,所以只能从rpc调用的结果里取出返回值并返回出去*/ + return response.getResult(); + } +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/proxy/factorybean/RpcServiceFactoryBean.java b/rpc-client/src/main/java/com/appleyk/rpc/client/proxy/factorybean/RpcServiceFactoryBean.java new file mode 100644 index 0000000..19220a9 --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/proxy/factorybean/RpcServiceFactoryBean.java @@ -0,0 +1,65 @@ +package com.appleyk.rpc.client.proxy.factorybean; + +import com.appleyk.rpc.client.proxy.RpcServiceInvocationHandler; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +import java.lang.reflect.Proxy; + +/** + *

+ * + * 第一种代理方式: 通过FactoryBean的方式,代理时机,在注册bean定义的时候,"篡改"接口的class + * + * 个性化定制rpc service bean + * (动态代理接口,使得客户端可以在本地直接使用接口调用远程的方法,让用户完全感觉不出来!!) + * + * 这种使用方式有个局限性,就是如果接口有多个实现, + *

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @date created on 下午9:21 2021/5/19 + */ +public class RpcServiceFactoryBean implements FactoryBean,ApplicationContextAware { + + private final Class targetClass; + private ApplicationContext context; + + public RpcServiceFactoryBean(Class targetClass) { + this.targetClass = targetClass; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.context = applicationContext; + } + + /** + * 使用jdk动态代理创建一个对象,并返回 + * @return 代理对象 + */ + @Override + public T getObject() { + Object o = Proxy.newProxyInstance( + targetClass.getClassLoader(), + new Class[]{targetClass}, + /*把spring ioc容器带过去*/ + new RpcServiceInvocationHandler(context)); + return (T) o; + } + + @Override + public Class getObjectType() { + return targetClass; + } + + @Override + public boolean isSingleton() { + return true; + } + +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/proxy/servicebean/RpcServiceBeanProxy.java b/rpc-client/src/main/java/com/appleyk/rpc/client/proxy/servicebean/RpcServiceBeanProxy.java new file mode 100644 index 0000000..c8d1b94 --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/proxy/servicebean/RpcServiceBeanProxy.java @@ -0,0 +1,41 @@ +package com.appleyk.rpc.client.proxy.servicebean; + +import com.appleyk.rpc.client.annotion.meta.RpcServiceAnnotationMetaData; +import com.appleyk.rpc.client.proxy.RpcServiceInvocationHandler; + +import java.lang.reflect.Proxy; + +/** + *

+ * 第二种代理方法:使用jdk自带的Proxy类来创建接口类型bean的代理对象 + * 代理时机:借助我们自定义的注入注解@RpcAutowired,复写bean后置处理器(实现BeanPostProcessor接口) + * 的postProcessBeforeInitialization方法,找到带有@RpcAutowired的field,然后对field进行动态代理 + *

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 14:01 2021/5/26 + */ +public class RpcServiceBeanProxy { + + public static Object getObject(Class targetClass){ + Object o = Proxy.newProxyInstance( + targetClass.getClassLoader(), + new Class[]{targetClass}, + new RpcServiceInvocationHandler()); + return o; + } + + public static Object getObject(Class targetClass, RpcServiceAnnotationMetaData metaData){ + Object o = Proxy.newProxyInstance( + targetClass.getClassLoader(), + new Class[]{targetClass}, + /*把bean的注解元数据带过去*/ + new RpcServiceInvocationHandler(metaData)); + return o; + } + + +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/registrar/InstantiationAware.java b/rpc-client/src/main/java/com/appleyk/rpc/client/registrar/InstantiationAware.java new file mode 100644 index 0000000..27d1266 --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/registrar/InstantiationAware.java @@ -0,0 +1,71 @@ +package com.appleyk.rpc.client.registrar; + +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.stereotype.Component; + +/** + *

bean实例化/初始化前后感知类增强器,这个类在这个框架里可以忽略,是基于Spring的测试类,就是玩的

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 15:50 2021/5/21 + */ +@Component +public class InstantiationAware implements InstantiationAwareBeanPostProcessor { + + /** + * bean实例化之前(增强器,这个地方其实也可以对beanClass做代理) + * @param beanClass bean类类型 + * @param beanName bean名称 + * @return 增强后的对象(如果不空,就是"偷天换日"了,成功代理了源bean) + */ + @Override + public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { + if ("cacheService".equals(beanName)){ + System.out.printf("InstantiationAware#postProcessBeforeInstantiation,bean{%s}实例化之前\n",beanName); + } + return null; // 空的话,继续交给spring容器对原有bean进行实例化 + } + + /** + * bean完成实例化后的增强器 + * @param bean bean对象 + * @param beanName bean名称 + */ + @Override + public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { + if ("cacheService".equals(beanName)){ + System.out.printf("InstantiationAware#postProcessBeforeInstantiation,bean{%s}实例化之后\n",beanName); + } + return true; + } + + /** + * 对属性值进行修改,如果postProcessAfterInstantiation方法返回false, + * 该方法不会被调用。可以在该方法内对属性值进行修改 + */ + @Override + public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException { + return null; + } + + /** + * 在Bean的自定义初始化方法执行完成之前执行 + */ + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + /** + * 在Bean的自定义初始化方法执行完成之后执行 + */ + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/registrar/RpcServiceBeanDefinitionRegistrar.java b/rpc-client/src/main/java/com/appleyk/rpc/client/registrar/RpcServiceBeanDefinitionRegistrar.java new file mode 100644 index 0000000..0bffe61 --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/registrar/RpcServiceBeanDefinitionRegistrar.java @@ -0,0 +1,62 @@ +package com.appleyk.rpc.client.registrar; + +import com.appleyk.rpc.client.annotion.EnableSeven; +import com.appleyk.rpc.client.proxy.factorybean.RpcServiceFactoryBean; +import com.appleyk.rpc.client.sanner.RpcServiceBeanPathScanner; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +/** + *

+ * 动态注册BeanDefinition到ioc容器:定制化 rpc service bean definition + * 里面很关键的一点就是:利用我们自定义的bean包扫描类,对候选bean定义进行条件放行, + * 比如对接口类型的bean定义进行放行,同时,对放行后的bean定义进行修改, + * 将其原有的接口类型指向为一个FactoryBean类型,完成bean的华丽转身!!! + *

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 17:39 2021/5/19 + */ +public class RpcServiceBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { + /*代理类*/ + private static Class targetClass = RpcServiceFactoryBean.class; + private static final String BASE_PACKAGE = "scanBasePackages"; + @Override + public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { + Map attributes = metadata.getAnnotationAttributes(EnableSeven.class.getName()); + String basePackage = attributes.get(BASE_PACKAGE).toString(); + RpcServiceBeanPathScanner beanPathScanner = new RpcServiceBeanPathScanner(registry); + beanPathScanner.addIncludeFilter(new TypeFilter() { + @Override + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { + /*管他三七二十一,条件放开*/ + return true; + } + }); + /*扫描公共接口包,获取其下的所有的接口bean定义*/ + Set beanDefinitionHolders = beanPathScanner.doScan(basePackage); + for (BeanDefinitionHolder definitionHolder : beanDefinitionHolders) { + GenericBeanDefinition bd = (GenericBeanDefinition) definitionHolder.getBeanDefinition(); + /*拿到service(接口)的类名*/ + String sourceClass = bd.getBeanClassName(); + System.out.printf("在bean{%s}还没有实例化之前,先对bean的class进行替换(狸猫换太子)\n",sourceClass); + /*在service (接口) 还没有开始bean的实例化之前,"篡改"它的类型*/ + bd.setBeanClass(targetClass); + /*类型都改了,下面顺带给targetClass类的有参构造器传参数,将bd的源类传进去,方便后续代理用*/ + bd.getConstructorArgumentValues().addIndexedArgumentValue(0,sourceClass); + } + } +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/sanner/RpcServiceBeanPathScanner.java b/rpc-client/src/main/java/com/appleyk/rpc/client/sanner/RpcServiceBeanPathScanner.java new file mode 100644 index 0000000..52eae86 --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/sanner/RpcServiceBeanPathScanner.java @@ -0,0 +1,56 @@ +package com.appleyk.rpc.client.sanner; + +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.filter.TypeFilter; + +import java.io.IOException; +import java.util.Set; + +/** + *

自定义rpc service bean definition 扫描器(主要放开接口不能被作为bd的条件)

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @date created on 下午7:57 2021/5/19 + */ +public class RpcServiceBeanPathScanner extends ClassPathBeanDefinitionScanner { + + public RpcServiceBeanPathScanner(BeanDefinitionRegistry registry) { + super(registry); + } + + @Override + public Set doScan(String... basePackages) { + return super.doScan(basePackages); + } + + @Override + public void addIncludeFilter(TypeFilter includeFilter) { + super.addIncludeFilter(includeFilter); + } + + /** + * 重写父类方法,让接口类型的bean通过候选组件"扫描" + */ + @Override + protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { + boolean bInterface = metadataReader.getClassMetadata().isInterface(); + System.out.printf("{%s}是否是接口:%b\n",metadataReader.getClassMetadata().getClassName(),bInterface); + return bInterface; + } + + /** + * 重写父类方法,让接口类型的bean通过候选组件"扫描" + */ + @Override + protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { + boolean bInterface = beanDefinition.getMetadata().isInterface(); + System.out.printf("{%s}是否是接口:%b\n",beanDefinition.getMetadata().getClassName(),bInterface); + return bInterface; + } +} diff --git a/rpc-client/src/main/java/com/appleyk/rpc/client/util/SpringContextUtils.java b/rpc-client/src/main/java/com/appleyk/rpc/client/util/SpringContextUtils.java new file mode 100644 index 0000000..61c9c46 --- /dev/null +++ b/rpc-client/src/main/java/com/appleyk/rpc/client/util/SpringContextUtils.java @@ -0,0 +1,35 @@ +package com.appleyk.rpc.client.util; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +/** + *

+ * Spring 上下文工具类(简单版):利用Spring(观察者模式)上下文监听器, + * 对上下文刷新完事件进行监听,以回调onApplicationEvent方法顺便拿到全局的Spring的context对象 + *

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 14:05 2021/5/26 + */ +@Component +public class SpringContextUtils implements ApplicationListener { + + public static ApplicationContext context; + + private SpringContextUtils(){} + + @Override + public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { + context = contextRefreshedEvent.getApplicationContext(); + } + + public static Object getBean(Class beanClz){ + return context.getBean(beanClz); + } +} diff --git a/rpc-client/src/main/resources/META-INF/spring.factories b/rpc-client/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..17aa5f1 --- /dev/null +++ b/rpc-client/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +# Rpc Client 自动装配bean配置类 +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.appleyk.rpc.client.autoconfigure.RpcClientAutoConfigure \ No newline at end of file diff --git a/rpc-common/pom.xml b/rpc-common/pom.xml new file mode 100644 index 0000000..12b9984 --- /dev/null +++ b/rpc-common/pom.xml @@ -0,0 +1,68 @@ + + + + + seven-rpc + com.appleyk + 0.1.1-SNAPSHOT + + + 4.0.0 + rpc-common + 提供统一的消息编解码器及一些常用的工具类..etc + + + + io.netty + netty-all + + + com.appleyk + rpc-core + ${project.version} + + + org.slf4j + slf4j-api + 1.7.26 + + + org.projectlombok + lombok + + + com.dyuproject.protostuff + protostuff-core + + + com.dyuproject.protostuff + protostuff-runtime + + + org.objenesis + objenesis + + + com.fasterxml.jackson.core + jackson-databind + + + + + ${artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/codec/RpcDecoder.java b/rpc-common/src/main/java/com/appleyk/rpc/common/codec/RpcDecoder.java new file mode 100644 index 0000000..fde1035 --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/codec/RpcDecoder.java @@ -0,0 +1,61 @@ +package com.appleyk.rpc.common.codec; + +import com.appleyk.rpc.common.util.PbfSerializationUtils; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + +import java.util.List; + +/** + *

消息解码器(字节流转消息)

+ * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 16:43 2021/5/18 + */ +public class RpcDecoder extends ByteToMessageDecoder { + + /**字节流对应的源对象的类型*/ + private Class sourceClass; + + public RpcDecoder(Class sourceClass) { + this.sourceClass = sourceClass; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + System.out.println("=== RpcDecoder#decode 方法被调用 ==="); + /*实际buf中可读取的内容长度*/ + int readableBytes = in.readableBytes(); + if (readableBytes < 4){ + // 如果不满4个字节,就返回(最低要求都达不到,浪费感情,你不配继续解码!!!) + return; + } + /*标记下开始读的位置*/ + in.markReaderIndex(); + /*由于编码器,将len写进了buf中,所以解码器这边读取下,获取下远端发过来的数据的长度(是否可信有待验证)*/ + int len = in.readInt(); + /*注意,实际缓冲区可读的(readableBytes)一定会大于buf的真实数据的长度,所以下面不要用等号判断*/ + if (readableBytes < len){ + /*重置下读指针,这时候如果不相等,只能说明数据没按要求传过来*/ + in.resetReaderIndex(); + byte[] content = new byte[readableBytes]; + in.readBytes(content); + System.out.printf("抱歉,监测到你传过来的数据{%s}有问题(" + + "可读字节内容长度和指定buf中的字节长度不匹配),本次请求视为无效!\n",new String(content)); + /*别忘了再重置下,万一有其他handler需要再读取数据处理呢*/ + in.resetReaderIndex(); + /*清空*/ + in.clear(); + return; + } + // 通过长度获取对应字节(序列化直接诶)数组 + byte[] data = new byte[len]; + // 将缓冲中的数据读取到字节数组中(注意,这里读取的是特定长度的,所以不会出现拆包或粘包问题) + in.readBytes(data); + // 将读取到的流按类型反序列化成对象并放入到list中传给下一个handler进行处理 + out.add(PbfSerializationUtils.deserialize(data,sourceClass)); + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/codec/RpcEncoder.java b/rpc-common/src/main/java/com/appleyk/rpc/common/codec/RpcEncoder.java new file mode 100644 index 0000000..3fc41e4 --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/codec/RpcEncoder.java @@ -0,0 +1,37 @@ +package com.appleyk.rpc.common.codec; + +import com.appleyk.rpc.common.util.PbfSerializationUtils; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +/** + *

消息编码器(消息对象转byte)

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 16:44 2021/5/18 + */ +public class RpcEncoder extends MessageToByteEncoder { + + /**字节流对应的源对象的类型*/ + private Class sourceClass; + + public RpcEncoder(Class sourceClass) { + this.sourceClass = sourceClass; + } + + @Override + protected void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception { + if (sourceClass.isInstance(in)){ + System.out.println("=== RpcEncoder#encode 方法被调用 ==="); + byte[] data = PbfSerializationUtils.serialize(in); + // 把长度写进去 + out.writeInt(data.length); + // 把内容写进去(注意写进去的长度是固定的,只要消息解码器按长度读取buf中的内容,就不会出现拆包or粘包问题) + out.writeBytes(data); + } + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/excp/RpcApiNotFoundException.java b/rpc-common/src/main/java/com/appleyk/rpc/common/excp/RpcApiNotFoundException.java new file mode 100644 index 0000000..fd00987 --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/excp/RpcApiNotFoundException.java @@ -0,0 +1,15 @@ +package com.appleyk.rpc.common.excp; + +/** + *

接口未发现

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @date created on 下午9:45 2021/5/23 + */ +public class RpcApiNotFoundException extends Exception{ + public RpcApiNotFoundException(String message){ + super(message); + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/excp/RpcServiceBeanNotFoundException.java b/rpc-common/src/main/java/com/appleyk/rpc/common/excp/RpcServiceBeanNotFoundException.java new file mode 100644 index 0000000..96fb693 --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/excp/RpcServiceBeanNotFoundException.java @@ -0,0 +1,15 @@ +package com.appleyk.rpc.common.excp; + +/** + *

Rpc服务bean未发现异常

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @date created on 下午10:15 2021/5/23 + */ +public class RpcServiceBeanNotFoundException extends Exception{ + public RpcServiceBeanNotFoundException(String message){ + super(message); + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/protocol/RpcMessageProtocol.java b/rpc-common/src/main/java/com/appleyk/rpc/common/protocol/RpcMessageProtocol.java new file mode 100644 index 0000000..9c368ae --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/protocol/RpcMessageProtocol.java @@ -0,0 +1,33 @@ +package com.appleyk.rpc.common.protocol; +import lombok.Data; + +/** + *

+ * RPC消息传输协议,防止客户端与服务端通信时,获取tcp数据包时,出现粘包和拆包的问题 + * 出现原因: + * 1、TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块, + * 但TCP却把这些数据块仅仅看成是一连串无结构的字节流,没有边界; + * 2、从TCP自身的帧结构上可以看出来,在TCP的首部是没有表示数据长度的字段的 + * 解决办法:那就在传输消息的时候,人为(手动)的带上字节流的长度len,编解码器每次按"需"处理即可 + * (白话:兄弟,我给你寄了2箱苹果,里面总过有125个,我给你放在物业了哈) + *

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @processOn示意图地址:https://processon.com/view/60364b14e0b34d1244395440 + * @date created on 16:25 2021/5/18 + */ +@Data +public class RpcMessageProtocol { + /**消息长度*/ + private int len; + /**消息内容*/ + private byte[] data; + public RpcMessageProtocol(){} + public RpcMessageProtocol(byte[] data){ + this.data = data; + this.len = data.length; + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/result/HttpResult.java b/rpc-common/src/main/java/com/appleyk/rpc/common/result/HttpResult.java new file mode 100644 index 0000000..e5c30cb --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/result/HttpResult.java @@ -0,0 +1,129 @@ +package com.appleyk.rpc.common.result; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.Date; + +@Data +public class HttpResult { + private int status; + private String message; + @JsonInclude(JsonInclude.Include.NON_NULL) + private Object data; + @JsonFormat( + pattern = "yyyy-MM-dd HH:mm:ss", + timezone = "GMT+8" + ) + private Date timestamp; + + public HttpResult() { + this(200, ""); + } + + public HttpResult(int status, String message) { + this(status, message, (Object)null); + } + + public HttpResult(HttpResult.Builder builder) { + this(builder.status, builder.message, builder.data); + } + + public HttpResult(int status, String message, Object data) { + this.timestamp = new Date(); + this.status = status; + this.message = message; + this.data = data; + } + + public static HttpResult ok() { + return (new HttpResult.Builder()).status(HttpResult.ResultCode.SUCCESS.getCode()).build(); + } + + public static HttpResult ok(String message) { + return (new HttpResult.Builder()).status(HttpResult.ResultCode.SUCCESS.getCode()).message(message).build(); + } + + public static HttpResult ok(Object data) { + return (new Builder()).status(ResultCode.SUCCESS.getCode()).message("成功").data(data).build(); + } + + public static HttpResult ok(String message, Object data) { + return (new HttpResult.Builder()).status(HttpResult.ResultCode.SUCCESS.getCode()).message(message).data(data).build(); + } + + public static HttpResult fail(int status, String message, Object data) { + return (new HttpResult.Builder()).status(status).message(message).data(data).build(); + } + + public static HttpResult fail(int status, String message) { + return (new HttpResult.Builder()).status(status).message(message).build(); + } + + public static HttpResult.Builder builder() { + return new HttpResult.Builder(); + } + + public static class Builder { + private int status; + private String message; + private Object data; + + public Builder() { + } + + public HttpResult.Builder status(int status) { + this.status = status; + return this; + } + + public HttpResult.Builder message(String message) { + this.message = message; + return this; + } + + public HttpResult.Builder data(Object data) { + this.data = data; + return this; + } + + public HttpResult build() { + return new HttpResult(this); + } + } + + public static enum ResultCode { + SUCCESS(200, "成功"), + MISSING_FIELD(10000, "字段缺失"), + INVALID_FIELD(10001, "无效字段"), + RESOURCE_NOT_FOUND(10101, "实体不存在"), + RESOURCE_ALREADY_EXIST(10102, "实体已存在"), + RESOURCE_UNAVAILABLE(10103, "实体不可用"), + RESOURCE_UNOPERATED(10104, "资源不可被操作"), + ACCESS_DENIED(10201, "拒绝访问"), + IDENTIFICATION_FAILED(10202, "用户认证失败"), + ILLEGAL_AUTH_INFO(10203, "非法的用户认证信息"), + TOKEN_EXPIRED(10204, "令牌过期"), + UNAUTHORIZED(10205, "权限不足"), + FILE_NOT_FOUND(10301, "文件不存在"), + JSON_PARSE_ERROR(10302, "JSON解析失败"), + SERVICE_UNAVAILABLE(10401, "服务不可用"), + TIME_OUT(10402, "服务超时"); + + private final Integer code; + private final String description; + + private ResultCode(Integer code, String description) { + this.code = code; + this.description = description; + } + + public Integer getCode() { + return this.code; + } + + public String getDescription() { + return this.description; + } + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/util/DateUtils.java b/rpc-common/src/main/java/com/appleyk/rpc/common/util/DateUtils.java new file mode 100644 index 0000000..6fc3d00 --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/util/DateUtils.java @@ -0,0 +1,26 @@ +package com.appleyk.rpc.common.util; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + *

日期格式化工具类/p> + * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @date created on 下午10:03 2021/5/22 + */ +public class DateUtils { + public static final String DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss";// 时间格式 + public static String date2Str(Date date){ + SimpleDateFormat format = new SimpleDateFormat(DEFAULT_FORMAT); + return format.format(date); + } + public static String dateNow2Str(){ + return date2Str(new Date()); + } + public static void main(String[] args) { + System.out.println(date2Str(new Date())); + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/util/GeneralUtils.java b/rpc-common/src/main/java/com/appleyk/rpc/common/util/GeneralUtils.java new file mode 100644 index 0000000..7716ddb --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/util/GeneralUtils.java @@ -0,0 +1,65 @@ +package com.appleyk.rpc.common.util; + +import java.util.*; + +/*** + * 通用(判断数据是否空)工具类 + */ +public class GeneralUtils { + + public static boolean isNotEmpty(Object object) { + if (object == null) { + return false; + } else if (object instanceof Integer) { + return Integer.valueOf(object.toString()) > 0; + } else if (object instanceof Long) { + return Long.valueOf(object.toString()) > 0L; + } else if (object instanceof String) { + return ((String)object).trim().length() > 0; + } else if (object instanceof StringBuffer) { + return ((StringBuffer)object).toString().trim().length() > 0; + } else if (object instanceof Boolean) { + return Boolean.valueOf(object.toString()); + } else if (object instanceof List) { + return ((List)object).size() > 0; + } else if (object instanceof Set) { + return ((Set)object).size() > 0; + } else if (object instanceof Map) { + return ((Map)object).size() > 0; + } else if (object instanceof Iterator) { + return ((Iterator)object).hasNext(); + } else if (object.getClass().isArray()) { + return Arrays.asList(object).size() > 0; + } else { + return true; + } + } + + public static boolean isEmpty(Object object) { + if (object == null) { + return true; + } else if (object instanceof Integer) { + return Integer.valueOf(object.toString()) == 0; + } else if (object instanceof Long) { + return Long.valueOf(object.toString()) == 0L; + } else if (object instanceof String) { + return ((String)object).trim().length() == 0; + } else if (object instanceof StringBuffer) { + return ((StringBuffer)object).toString().trim().length() == 0; + } else if (object instanceof Boolean) { + return Boolean.valueOf(object.toString()); + } else if (object instanceof List) { + return ((List)object).size() == 0; + } else if (object instanceof Set) { + return ((Set)object).size() == 0; + } else if (object instanceof Map) { + return ((Map)object).size() == 0; + } else if (object instanceof Iterator) { + return !((Iterator)object).hasNext(); + } else if (object.getClass().isArray()) { + return Arrays.asList(object).size() == 0; + } else { + return false; + } + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/util/IdUtils.java b/rpc-common/src/main/java/com/appleyk/rpc/common/util/IdUtils.java new file mode 100644 index 0000000..27baf27 --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/util/IdUtils.java @@ -0,0 +1,15 @@ +package com.appleyk.rpc.common.util; + +/** + *

ID工具类,简单起见,就返回时间戳吧,不想搞什么雪花分布式ID了

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @date created on 下午10:10 2021/5/22 + */ +public class IdUtils { + public static long getId(){ + return System.currentTimeMillis(); + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/util/JsonUtils.java b/rpc-common/src/main/java/com/appleyk/rpc/common/util/JsonUtils.java new file mode 100644 index 0000000..d3444eb --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/util/JsonUtils.java @@ -0,0 +1,167 @@ +package com.appleyk.rpc.common.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.ArrayType; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.fasterxml.jackson.databind.type.MapType; + +import java.io.IOException; +import java.util.*; + +/** + * JSON正反序列化工具类 + */ +public class JsonUtils { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + public JsonUtils() { + } + + public static String parserJson(T target) { + if (GeneralUtils.isEmpty(target)) { + return null; + } else { + try { + return MAPPER.writeValueAsString(target); + } catch (JsonProcessingException var2) { + var2.printStackTrace(); + LoggerUtils.error("json序列化失败, " + var2.getMessage()); + return null; + } + } + } + + public static T parserBean(String json, Class clazz) { + if (GeneralUtils.isEmpty(json)) { + return null; + } else { + try { + return MAPPER.readValue(json, clazz); + } catch (Exception var3) { + var3.printStackTrace(); + LoggerUtils.error("json反序列化失败, " + var3.getMessage()); + return null; + } + } + } + + public static T[] parserArray(String json, Class clazz) { + if (GeneralUtils.isEmpty(json)) { + return null; + } else { + ArrayType arrayType = MAPPER.getTypeFactory().constructArrayType(clazz); + try { + return (T[])MAPPER.readValue(json, arrayType); + } catch (Exception var4) { + var4.printStackTrace(); + LoggerUtils.error("json反序列化失败, " + var4.getMessage()); + return null; + } + } + } + + public static List parserList(String json, CollectionType listType) { + if (GeneralUtils.isEmpty(json)) { + return null; + } else { + try { + return (List)MAPPER.readValue(json, listType); + } catch (Exception var3) { + var3.printStackTrace(); + LoggerUtils.error("json反序列化失败, " + var3.getMessage()); + return Collections.emptyList(); + } + } + } + + public static List parserList(String json, Class parserClazz, Class clazz) { + CollectionType listType = MAPPER.getTypeFactory().constructCollectionType(parserClazz, clazz); + return parserList(json, listType); + } + + public static List parserList(String json, Class clazz) { + return parserList(json, List.class, clazz); + } + + public static Set parserSet(String json, CollectionType setType) { + if (GeneralUtils.isEmpty(json)) { + return null; + } else { + try { + return (Set)MAPPER.readValue(json, setType); + } catch (Exception var3) { + var3.printStackTrace(); + LoggerUtils.error("json反序列化失败, " + var3.getMessage()); + return Collections.emptySet(); + } + } + } + + public static Set parserSet(String json, Class parserClazz, Class clazz) { + CollectionType setType = MAPPER.getTypeFactory().constructCollectionType(parserClazz, clazz); + return parserSet(json, setType); + } + + public static Set parserSet(String json, Class clazz) { + return parserSet(json, Set.class, clazz); + } + + public static Map parserMap(String json, MapType mapType) { + if (GeneralUtils.isEmpty(json)) { + return null; + } else { + try { + return (Map)MAPPER.readValue(json, mapType); + } catch (Exception var3) { + var3.printStackTrace(); + LoggerUtils.error("json反序列化失败, " + var3.getMessage()); + return Collections.emptyMap(); + } + } + } + + public static Map parserMap(String json, Class parserClazz, Class keyClazz, Class valueClazz) { + MapType mapType = MAPPER.getTypeFactory().constructMapType(parserClazz, keyClazz, valueClazz); + return parserMap(json, mapType); + } + + public static Map parserMap(String json, Class keyClazz, Class valueClazz) { + return parserMap(json, Map.class, keyClazz, valueClazz); + } + + public static Hashtable parserTable(String json, Class keyClazz, Class valueClazz) { + MapType mapType = MAPPER.getTypeFactory().constructMapType(Hashtable.class, keyClazz, valueClazz); + if (GeneralUtils.isEmpty(json)) { + return null; + } else { + try { + return (Hashtable)MAPPER.readValue(json, mapType); + } catch (IOException var5) { + var5.printStackTrace(); + LoggerUtils.error("json反序列化失败, " + var5.getMessage()); + return new Hashtable(); + } + } + } + + public static Map> parserMapList(String json, Class parserListClazz, Class parserMapClazz, Class keyClazz, Class valueClazz) { + CollectionType collectionType = MAPPER.getTypeFactory().constructCollectionType(parserListClazz, valueClazz); + MapType mapType = MAPPER.getTypeFactory().constructMapType(parserMapClazz, keyClazz, collectionType.getRawClass()); + return parserMap(json, mapType); + } + + public static Map> parserMapList(String json, Class keyClazz, Class valueClazz) { + return parserMapList(json, List.class, Map.class, keyClazz, valueClazz); + } + + public static Map> parserMapMap(String json, Class wrapMapClazz, Class contentMapClazz, Class wrapKeyClazz, Class contentKeyClazz, Class contentValueClazz) { + MapType contentType = MAPPER.getTypeFactory().constructMapType(contentMapClazz, contentKeyClazz, contentValueClazz); + MapType mapType = MAPPER.getTypeFactory().constructMapType(wrapMapClazz, wrapKeyClazz, contentType.getRawClass()); + return parserMap(json, mapType); + } + + public static Map> parserMapMap(String json, Class wrapKeyClazz, Class contentKeyClazz, Class contentValueClazz) { + return parserMapMap(json, Map.class, Map.class, wrapKeyClazz, contentKeyClazz, contentValueClazz); + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/util/LoggerUtils.java b/rpc-common/src/main/java/com/appleyk/rpc/common/util/LoggerUtils.java new file mode 100644 index 0000000..b07e66a --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/util/LoggerUtils.java @@ -0,0 +1,54 @@ +package com.appleyk.rpc.common.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.helpers.FormattingTuple; +import org.slf4j.helpers.MessageFormatter; + +/** + * 日志(各种级别)记录工具类 + */ +public class LoggerUtils { + private static Logger logger = LoggerFactory.getLogger(LoggerUtils.class); + public static void info(String message) { + logger.info(message); + } + public static void info(String message, Object ... append) { + FormattingTuple formattingTuple = MessageFormatter.arrayFormat(message, append); + String format = formattingTuple.getMessage(); + logger.info(format); + } + public static void warn(String message) { + logger.warn(message); + } + public static void warn(String message, Object ... append) { + FormattingTuple formattingTuple = MessageFormatter.arrayFormat(message, append); + String format = formattingTuple.getMessage(); + logger.warn(format); + } + public static void debug(String message) { + logger.debug(message); + } + public static void debug(String message, Object ... append) { + FormattingTuple formattingTuple = MessageFormatter.arrayFormat(message, append); + String format = formattingTuple.getMessage(); + logger.debug(format); + } + public static void error(String message, Exception ex) { + logger.error(message, ex); + } + public static void error(Integer errCode, String message) { + logger.error("错误码:" + errCode + ",错误消息:" + message); + } + public static void error(String message) { + logger.error("错误消息:" + message); + } + public static void error(Integer errCode, String message, Exception ex) { + logger.error("错误码:" + errCode + ",错误消息:" + message + ",异常信息:" + ex.getMessage()); + } + public static void error(String message, Object ... append) { + FormattingTuple formattingTuple = MessageFormatter.arrayFormat(message, append); + String format = formattingTuple.getMessage(); + logger.error(format); + } +} diff --git a/rpc-common/src/main/java/com/appleyk/rpc/common/util/PbfSerializationUtils.java b/rpc-common/src/main/java/com/appleyk/rpc/common/util/PbfSerializationUtils.java new file mode 100644 index 0000000..fb5b900 --- /dev/null +++ b/rpc-common/src/main/java/com/appleyk/rpc/common/util/PbfSerializationUtils.java @@ -0,0 +1,87 @@ +package com.appleyk.rpc.common.util; + +import com.appleyk.rpc.core.model.result.RpcRequest; +import com.dyuproject.protostuff.LinkedBuffer; +import com.dyuproject.protostuff.ProtostuffIOUtil; +import com.dyuproject.protostuff.Schema; +import com.dyuproject.protostuff.runtime.RuntimeSchema; +import org.objenesis.Objenesis; +import org.objenesis.ObjenesisStd; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

基于protostuff进行对象的序列化和反序列化

+ * 需要注意的地方: + * protostuff的序列化过程是按照数据模型中的字段顺序依次序列化字段的值, + * 反序列化时会按顺序解析数据然后赋给对应的字段,所以数据模型要遵循以下原则,避免数据错乱: + * + * 1、数据模型中的字段一旦使用不可再删除,除非先清除存储的所有序列化数据(但是在线上项目中不推荐这么干) + * 2、数据模型中新增字段必须放在最后面!!! + * 3、如果是在内部对象新增字段,直接在内部对象数据模型中最后一个字段的后面增加新字段即可 + * 4、protobuff只能序列化pojo类,不能直接序列化List 或者Map,如果要序列化list或者map的话,需要用一个wrapper类包装一下 + * 5、最后一条,在rpc框架中,其功能是够用了,实在不行,我用proto文件自己编译好吧 + */ +public class PbfSerializationUtils { + + private static Map, Schema> cachedSchema = new ConcurrentHashMap<>(); + private static Objenesis objenesis = new ObjenesisStd(true); + private PbfSerializationUtils() {} + + /** + * 序列化(对象 -> 字节数组) + */ + @SuppressWarnings("unchecked") + public static byte[] serialize(T obj) { + Class cls = (Class) obj.getClass(); + LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); + try { + Schema schema = getSchema(cls); + return ProtostuffIOUtil.toByteArray(obj, schema, buffer); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } finally { + buffer.clear(); + } + } + + /** + * 反序列化(字节数组 -> 对象) + */ + public static T deserialize(byte[] data, Class cls) { + try { + T message = objenesis.newInstance(cls); + Schema schema = getSchema(cls); + ProtostuffIOUtil.mergeFrom(data, message, schema); + return message; + } catch (Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + private static Schema getSchema(Class cls) { + Schema schema = (Schema) cachedSchema.get(cls); + if (schema == null) { + schema = RuntimeSchema.createFrom(cls); + cachedSchema.put(cls, schema); + } + return schema; + } + + public static void main(String[] args) { + /*pbf序列化 和 json序列化 数据"体积"对比*/ + RpcRequest request = RpcRequest.builder() + .methodName("hello") + .interfaceName("HelloService") + .requestId(IdUtils.getId()) + .parameters(new Object[]{"111", 22222, 2.0}).build(); + byte[] serialize = PbfSerializationUtils.serialize(request); + System.out.println(serialize.length); + String json = JsonUtils.parserJson(request); + assert json != null; + System.out.println(json.length()); + RpcRequest res = PbfSerializationUtils.deserialize(serialize,RpcRequest.class); + System.out.println(res.getMethodName()); + } +} diff --git a/rpc-core/pom.xml b/rpc-core/pom.xml new file mode 100644 index 0000000..d1935cc --- /dev/null +++ b/rpc-core/pom.xml @@ -0,0 +1,42 @@ + + + + + seven-rpc + com.appleyk + 0.1.1-SNAPSHOT + + + 4.0.0 + rpc-core + 定义核心模型、注解等 + + + + org.projectlombok + lombok + + + org.springframework + spring-context + + + + + ${artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/rpc-core/src/main/java/com/appleyk/rpc/core/annotation/RpcService.java b/rpc-core/src/main/java/com/appleyk/rpc/core/annotation/RpcService.java new file mode 100644 index 0000000..4a379e8 --- /dev/null +++ b/rpc-core/src/main/java/com/appleyk/rpc/core/annotation/RpcService.java @@ -0,0 +1,48 @@ +package com.appleyk.rpc.core.annotation; + +import org.springframework.stereotype.Service; + +import java.lang.annotation.*; + +/** + *

在接口实现类上加上该注解,使其具有rpc和spring bean的功能

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 10:40 2021/5/18 + */ +@Target(ElementType.TYPE) // 标注在类上 +@Retention(RetentionPolicy.RUNTIME) // 保留到JVM运行时,即字节码加载到内存后,注解依然"存活" +@Documented // 是否生成javadoc时,一并把注解属性显示在标记的类上,加了就带,不加就不带 +@Service// 可以带这个,也可以不带这个,带这个是为了简化开发人员的"工作量",每次都少写一个@Service,它不香吗 +public @interface RpcService { + + /* 先空着,暂时没想好放什么,后续功能扩充了,会补一些方法 2021年5月19日 15:53:20*/ + + /* + * 2021年05月20日20:49:43 + * 想了想,接口只有一个啊,实现类可是有多个啊,面向对象的三大特性、封装、继承、多态 + * 如果你平时在coding练习或者在公司做项目时,连接口的多态性都没有听过或实操过的话, + * 显然是让人无法接受的,话说,我们这个rpc项目里,干嘛要提一嘴这个呢,原因很简单: + * 1、有一个公共的API模块 + * 2、客户端(服务消费者)只需要知道公共的API怎么用就行,无需关心它是怎么实现的 + * ,也不要给我整什么让我手动去创建代理对象,然后再rpc,我就想简单、简单、简单调用!!! + * 3、服务端(服务提供者)需要实现公共API接口中的方法,比如,缓存接口的增删改查, + * 实现方式可以是redis、mongodb、甚至是memberCache..etc + * 4、那客户端在只知道有个缓存接口,比如XXXCacheService的情况下,要怎么知道该调用(服务端)哪个接口的实现(多态体现)呢? + * 5、索性,我们在每个接口实现类上主动的标注下它的接口类型即可,同时带上它的实现方式,如下: + * 5.1 + * @RpcService(ICacheService.class,name="mongodb") + * public MongoDbCacheServiceImpl implements ICacheService{ + * byte[] getData(String key); + * } + * 6、如果不显示标注它的接口类型的话,那就由程序通过xxxServiceImpl.getClass().getGenericInterfaces()的方式获取 + * (简单起见,本rpc框架中,不涉及一个类实现多个接口的场景,单一职责,业务接口实现类很专一,一个实现类就对应实现一个接口) + * + */ + Class value() default Void.class; + String name() default ""; + String version() default "0.1.1"; +} diff --git a/rpc-core/src/main/java/com/appleyk/rpc/core/model/User.java b/rpc-core/src/main/java/com/appleyk/rpc/core/model/User.java new file mode 100644 index 0000000..53c8f11 --- /dev/null +++ b/rpc-core/src/main/java/com/appleyk/rpc/core/model/User.java @@ -0,0 +1,13 @@ +package com.appleyk.rpc.core.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class User{ + private Long id ; + private String name; + private String nickname; + private Integer age; +} diff --git a/rpc-core/src/main/java/com/appleyk/rpc/core/model/result/RpcRequest.java b/rpc-core/src/main/java/com/appleyk/rpc/core/model/result/RpcRequest.java new file mode 100644 index 0000000..b86a899 --- /dev/null +++ b/rpc-core/src/main/java/com/appleyk/rpc/core/model/result/RpcRequest.java @@ -0,0 +1,30 @@ +package com.appleyk.rpc.core.model.result; + +import lombok.Builder; +import lombok.Data; + +/** + *

封装RPC请求对象

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 10:25 2021/5/18 + */ +@Data +@Builder +public class RpcRequest { + /**请求唯一标识*/ + private Long requestId; + /**接口名称*/ + private String interfaceName; + /**接口实现类的类型(接口的多态:如一个接口有多个实现类,则要标注下实现类的类型,类型要唯一)*/ + private String type; + /**需要(rc,remote call)调用的方法名称*/ + private String methodName; + /**方法参数类型数组*/ + private Class[] parameterTypes; + /**方法参数值数组*/ + private Object[] parameters; +} diff --git a/rpc-core/src/main/java/com/appleyk/rpc/core/model/result/RpcResponse.java b/rpc-core/src/main/java/com/appleyk/rpc/core/model/result/RpcResponse.java new file mode 100644 index 0000000..fd215eb --- /dev/null +++ b/rpc-core/src/main/java/com/appleyk/rpc/core/model/result/RpcResponse.java @@ -0,0 +1,30 @@ +package com.appleyk.rpc.core.model.result; + +import lombok.Builder; +import lombok.Data; + +import java.util.Date; + +/** + *

封装RPC响应对象

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 10:25 2021/5/18 + */ +@Data +@Builder +public class RpcResponse { + + /**一个响应对应一个客户端请求(连接)标识*/ + private Long requestId; + /**万一异常了,记录下*/ + private Exception e; + /**响应结果*/ + private Object result; + /**响应时间*/ + private Date date; + +} diff --git a/rpc-core/src/test/java/FastClassTest.java b/rpc-core/src/test/java/FastClassTest.java new file mode 100644 index 0000000..61da324 --- /dev/null +++ b/rpc-core/src/test/java/FastClassTest.java @@ -0,0 +1,59 @@ +import org.springframework.cglib.reflect.FastClass; +import org.springframework.cglib.reflect.FastMethod; +import org.springframework.util.StopWatch; + +import java.lang.reflect.Method; + +/** + *

+ * + * CGLib中提供的FastClass增强功能, + * FastClass顾名思义是一个能让被增强类更快调用的Class, + * 主要针对调用方法是变量的场景,用于替代反射调用 + * + *

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 9:43 2021/3/16 + */ +public class FastClassTest { + + static class Student{ + public String hello(String name){ + return "hello,"+name; + } + } + + public static void main(String[] args) throws Exception{ + Student student = new Student(); + Class sClass = student.getClass(); + + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + /**1、直接调用*/ + System.out.println(student.hello("直接调用")); + stopWatch.stop(); + System.out.println("耗时:"+stopWatch.getTotalTimeNanos()+"纳秒"); + + /**2、使用jdk反射调用*/ + stopWatch.start(); + Method reflectMethod = sClass.getMethod("hello", String.class); + Class[] parameterTypes = reflectMethod.getParameterTypes(); + System.out.println(reflectMethod.invoke(student, "反射调用")); + stopWatch.stop(); + System.out.println("耗时:"+stopWatch.getTotalTimeNanos()+"纳秒"); + + stopWatch.start(); + /**3、使用cglib增强类调用*/ + FastClass fastClass = FastClass.create(sClass); + FastMethod fastMethod = fastClass.getMethod("hello", parameterTypes); + System.out.println(fastMethod.invoke(student,new String[]{"增强调用"})); + stopWatch.stop(); + System.out.println("耗时:"+stopWatch.getTotalTimeNanos()+"纳秒"); + } +} + + diff --git a/rpc-registry/pom.xml b/rpc-registry/pom.xml new file mode 100644 index 0000000..172be80 --- /dev/null +++ b/rpc-registry/pom.xml @@ -0,0 +1,23 @@ + + + + + seven-rpc + com.appleyk + 0.1.1-SNAPSHOT + + + 4.0.0 + rpc-registry + 服务发现与注册中父工程 + pom + + + rpc-registry-api + rpc-registry-zookeeper + + + + \ No newline at end of file diff --git a/rpc-registry/rpc-registry-api/pom.xml b/rpc-registry/rpc-registry-api/pom.xml new file mode 100644 index 0000000..0e93636 --- /dev/null +++ b/rpc-registry/rpc-registry-api/pom.xml @@ -0,0 +1,31 @@ + + + + + rpc-registry + com.appleyk + 0.1.1-SNAPSHOT + + + 4.0.0 + rpc-registry-api + 服务注册与发现中心功能接口规范与定义 + + + ${artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/rpc-registry/rpc-registry-api/src/main/java/com/appleyk/rpc/registry/ServiceDiscovery.java b/rpc-registry/rpc-registry-api/src/main/java/com/appleyk/rpc/registry/ServiceDiscovery.java new file mode 100644 index 0000000..0d9acbd --- /dev/null +++ b/rpc-registry/rpc-registry-api/src/main/java/com/appleyk/rpc/registry/ServiceDiscovery.java @@ -0,0 +1,20 @@ +package com.appleyk.rpc.registry; + +/** + *

服务发现接口

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 12:28 2021/3/12 + */ +public interface ServiceDiscovery { + + /** + * 根据服务名,获取服务地址 + * @param serviceName 服务名称 + * @return 服务地址 + */ + String discover(String serviceName); +} diff --git a/rpc-registry/rpc-registry-api/src/main/java/com/appleyk/rpc/registry/ServiceRegistry.java b/rpc-registry/rpc-registry-api/src/main/java/com/appleyk/rpc/registry/ServiceRegistry.java new file mode 100644 index 0000000..aea067f --- /dev/null +++ b/rpc-registry/rpc-registry-api/src/main/java/com/appleyk/rpc/registry/ServiceRegistry.java @@ -0,0 +1,22 @@ +package com.appleyk.rpc.registry; + +/** + *

服务注册接口

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 12:39 2021/3/12 + */ +public interface ServiceRegistry { + + /** + * 注册服务名称和服务地址 + * + * @param serviceName 服务名称 + * @param serviceAddress 服务地址 + */ + void register(String serviceName,String serviceAddress); + +} diff --git a/rpc-registry/rpc-registry-zookeeper/pom.xml b/rpc-registry/rpc-registry-zookeeper/pom.xml new file mode 100644 index 0000000..6b28cc7 --- /dev/null +++ b/rpc-registry/rpc-registry-zookeeper/pom.xml @@ -0,0 +1,71 @@ + + + + + rpc-registry + com.appleyk + 0.1.1-SNAPSHOT + + + 4.0.0 + rpc-registry-zookeeper + 基于zk实现的服务注册与发现中心(自动装配) + + + + + com.appleyk + rpc-registry-api + ${project.version} + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + + + org.springframework.boot + spring-boot-configuration-processor + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.curator + curator-framework + + + + + + rpc-registry-zookeeper-springboot-starter + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/config/ZookeeperAutoConfig.java b/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/config/ZookeeperAutoConfig.java new file mode 100644 index 0000000..54767d8 --- /dev/null +++ b/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/config/ZookeeperAutoConfig.java @@ -0,0 +1,53 @@ +package com.appleyk.registry.zk.config; + +import com.appleyk.registry.zk.impl.ZookeeperCenter; +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + *

zk客户端自动配置类

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 12:48 2021/3/12 + */ +@Configuration +@ComponentScan("com.appleyk.registry.zk") +@ConditionalOnClass({ZookeeperCenter.class}) // 当给定的类名在类路径上存在,则实例化当前Bean +@EnableConfigurationProperties(ZookeeperProperty.class) +public class ZookeeperAutoConfig { + + private final ZookeeperProperty property; + + public ZookeeperAutoConfig(ZookeeperProperty property){ + this.property = property; + } + + @Bean(name = "client") + @ConditionalOnMissingBean // 如果当前bean不存在的时候,则实例化当前bean + public CuratorFramework curatorClient() { + RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10); + CuratorFramework client = + CuratorFrameworkFactory.builder() + .connectString(property.getHost()+":"+property.getPort()) + .sessionTimeoutMs(property.getTimeout()) + .connectionTimeoutMs(60 * 1000) + // 设置命名空间 + .namespace(property.getNameSpace()) + .retryPolicy(retryPolicy).build(); + client.start(); + System.out.println("========== ZkServer Starting ... =========="); + return client; + } + +} diff --git a/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/config/ZookeeperProperty.java b/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/config/ZookeeperProperty.java new file mode 100644 index 0000000..13519f5 --- /dev/null +++ b/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/config/ZookeeperProperty.java @@ -0,0 +1,55 @@ +package com.appleyk.registry.zk.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.Max; +import javax.validation.constraints.NotBlank; + +@Validated // 属性类上必须加这个注解,开启验证,不加的话,不会对属性字段的值进行校验 +@ConfigurationProperties(prefix = "seven.rpc.zookeeper") +public class ZookeeperProperty { + + /*ZK命名空间,就是默认根节点*/ + private String nameSpace = "seven-rpc-registry"; + @NotBlank(message = "ZK服务地址不能为空!") + private String host; + @Max(value = 65536) + private Integer port = 2181; + /*(临时节点)会话过期时间*/ + private Integer timeout; + + public ZookeeperProperty(){} + + public String getNameSpace() { + return nameSpace; + } + + public void setNameSpace(String nameSpace) { + this.nameSpace = nameSpace; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } +} diff --git a/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/impl/ZookeeperCenter.java b/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/impl/ZookeeperCenter.java new file mode 100644 index 0000000..02922f3 --- /dev/null +++ b/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/impl/ZookeeperCenter.java @@ -0,0 +1,74 @@ +package com.appleyk.registry.zk.impl; + +import com.appleyk.registry.zk.util.ZkApiUtils; +import com.appleyk.rpc.registry.ServiceDiscovery; +import com.appleyk.rpc.registry.ServiceRegistry; +import org.apache.curator.framework.CuratorFramework; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Random; + +/** + *

基于ZK实现的服务注册与发现中心

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 12:51 2021/3/12 + */ +@Service +public class ZookeeperCenter implements ServiceRegistry, ServiceDiscovery { + + private static final Logger logger = LoggerFactory.getLogger(ZookeeperCenter.class); + + @Autowired + private CuratorFramework client; + + public String discover(String serviceName) { + String serviceNamePath = "/"+serviceName; + /*获取服务目录节点下面的API子节点(多个)*/ + List children = ZkApiUtils.getPathChildren(client, serviceNamePath); + if (children == null || children.size() == 0){ + /*空就意味着注册中心没有任何服务接口提供给服务消费方*/ + return ""; + } + String childPath = children.get(choose(children.size())); + String childFullPath = serviceNamePath+"/"+childPath; + return ZkApiUtils.getNodeValue(client, childFullPath); + } + + /** + * 随机返回服务接口列表中的某一个地址的索引下标 + * @param len 列表长度 + */ + public int choose(int len){ + return new Random().nextInt(len); + } + + /** + * 实现服务注册功能,关键字(永久、顺序) + * @param serviceName 服务名称 + * @param serviceAddress 服务地址 + */ + public void register(String serviceName, String serviceAddress) { + String serviceNamePath = "/"+serviceName; + /**1、先判断节点存不存在,存在就删除,不存在就创建*/ + if (!ZkApiUtils.checkNodeExist(client,serviceNamePath)){ + logger.info("service node {} not existing,You don't have to create it again!",serviceNamePath); + /**2、再创建服务节点(永久)*/ + ZkApiUtils.create(client,serviceNamePath); + System.out.printf("Rpc-Server,成功注册服务节点:%s\n",serviceNamePath); + }else{ + System.out.printf("Rpc-Server,服务节点:%s已存在\n",serviceNamePath); + } + // 3、为服务(接口)地址创建临时顺序节点(如果服务断开,临时节点在就删除) + String serviceAddressPath = serviceNamePath+"/service-"; + String addressNode = ZkApiUtils.createEphemeralSequential(client, serviceAddressPath, serviceAddress); + logger.info("create address node : {}" ,addressNode); + } +} diff --git a/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/util/ZkApiUtils.java b/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/util/ZkApiUtils.java new file mode 100644 index 0000000..63b6f08 --- /dev/null +++ b/rpc-registry/rpc-registry-zookeeper/src/main/java/com/appleyk/registry/zk/util/ZkApiUtils.java @@ -0,0 +1,190 @@ +package com.appleyk.registry.zk.util; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.data.Stat; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + *

+ * CuratorFramework 是Netflix公司开发的一款连接zookeeper服务的框架, + * 提供了比较全面的功能,除了基础的节点的操作,节点的监听,还有集群的连接以及重试。 + *

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 12:36 2021/3/16 + */ +public class ZkApiUtils{ + + /** + * (默认)创建持久化节点,并附带内容 + * this will create the given ZNode with the given data + */ + public static void create(CuratorFramework client, String path, byte[] payload) { + try { + if(checkNodeExist(client,path)){return;} + client.create().forPath(path, payload); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * (默认)创建持久化节点 + * this will create the given ZNode with the given data + */ + public static String create(CuratorFramework client,String path,String data) { + try { + if(checkNodeExist(client,path)){return path;} + return client.create().forPath(path,data.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * (默认)创建持久化节点 + * this will create the given ZNode with the given data + */ + public static void create(CuratorFramework client, String path) { + try { + if(checkNodeExist(client,path)){return;} + client.create().forPath(path); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /** + * this will create the given EPHEMERAL ZNode with the given data + */ + public static void createEphemeral(CuratorFramework client, String path, byte[] payload) { + try { + client.create().withMode(CreateMode.EPHEMERAL).forPath(path, payload); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 创建临时顺序节点(注意:临时节点下面不能再创建子节点) + * this will create the given EPHEMERAL-SEQUENTIAL ZNode with + * the given data using Curator protection. + */ + public static String createEphemeralSequential(CuratorFramework client, String path, String data) { + try { + return client.create().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path,data.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 创建临时顺序节点 + * this will create the given EPHEMERAL-SEQUENTIAL ZNode with + * the given data using Curator protection. + */ + public static String createEphemeralSequential(CuratorFramework client, String path, byte[] payload) { + try { + return client.create().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, payload); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * set data for the given node + */ + public static void setData(CuratorFramework client, String path, byte[] payload) { + try { + client.setData().forPath(path, payload); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * + * delete the given node + */ + public static void delete(CuratorFramework client, String path) { + try { + client.delete().forPath(path); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 保证节点一定被删除 + * delete the given node and guarantee that it completes + */ + public static void guaranteedDelete(CuratorFramework client, String path) { + try { + client.delete().guaranteed().forPath(path); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Get children and set a watcher on the node. The watcher notification + */ + public static List watchedGetChildren(CuratorFramework client, String path) { + try { + return client.getChildren().watched().forPath(path); + } catch (Exception e) { + return null; + } + } + + /** + * Get children from the path (获取path上的孩子节点) + */ + public static List getPathChildren(CuratorFramework client, String path) { + try { + return client.getChildren().forPath(path); + } catch (Exception e) { + return null; + } + } + + /** + * 获取节点的值 + */ + public static String getNodeValue(CuratorFramework client, String path) { + try { + byte[] value = client.getData().forPath(path); + return value == null ? null : new String(value); + } catch (Exception e) { + return null; + } + } + + /** + * 判断节点是否存在 + */ + public static boolean checkNodeExist(CuratorFramework client, String path) { + boolean flag = true; + try { + Stat stat = client.checkExists().forPath(path); + // Stat就是对zNode所有属性的一个映射, stat=null表示节点不存在! + if (stat == null) { + flag = false; + } + } catch (Exception e) { + e.printStackTrace(); + flag = false; + } + return flag; + } +} diff --git a/rpc-registry/rpc-registry-zookeeper/src/main/resources/META-INF/spring.factories b/rpc-registry/rpc-registry-zookeeper/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..c435856 --- /dev/null +++ b/rpc-registry/rpc-registry-zookeeper/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.appleyk.registry.zk.config.ZookeeperAutoConfig \ No newline at end of file diff --git a/rpc-server/pom.xml b/rpc-server/pom.xml new file mode 100644 index 0000000..e905d16 --- /dev/null +++ b/rpc-server/pom.xml @@ -0,0 +1,44 @@ + + + + + seven-rpc + com.appleyk + 0.1.1-SNAPSHOT + + + 4.0.0 + rpc-server + rpc服务端 + + + + com.appleyk + rpc-common + ${project.version} + + + com.appleyk + rpc-registry-zookeeper + ${project.version} + + + + + ${artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/rpc-server/src/main/java/com/appleyk/rpc/server/RpcServer.java b/rpc-server/src/main/java/com/appleyk/rpc/server/RpcServer.java new file mode 100644 index 0000000..5c40390 --- /dev/null +++ b/rpc-server/src/main/java/com/appleyk/rpc/server/RpcServer.java @@ -0,0 +1,159 @@ +package com.appleyk.rpc.server; + +import com.appleyk.rpc.core.annotation.RpcService; +import com.appleyk.rpc.common.util.GeneralUtils; +import com.appleyk.rpc.common.util.LoggerUtils; +import com.appleyk.rpc.registry.ServiceRegistry; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.util.NettyRuntime; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Type; +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; + +/** + *

RPC 服务端 ==> 绑定ip、端口、配置、启动netty服务...etc

+ * + * 思路: + * 1、服务启动前,要先拿到服务端所有的接口实现类(bean) + * 2、要想获得所有的service,一是需要借助@RpcService注解以便Spring bean扫描, + * 二是需要借助Spring上下文容器,也就是IOC Container,因为扫描后的bean都在它里面 + * 3、添加@RpcService注解很easy,直接在接口实现类上添加标记即可,那如何获取Spring的applicationContext呢? + * 3.1 稍微看过Spring源码(BeanFactory顶层接口最上面的注释有关于bean的生命周期的各种类的功能描述)的人应该都清楚, + * 借助ApplicationContextAware这个类,可以很轻松的在当前实现类中注入我们需要的context + * 3.2 拿到context后,我们就拿到了bean的容器啊,里面不仅有各种bean的定义,还有各种bean(类的实例化对象)...etc + * 4、有了Spring上下文后,结合@RpcService注解和Java反射技术,可以很容易的获取到bean的成员变量信息 + * 5、有了关于bean的一切后,我们就可以向ZK(zookeeper)注册我们的服务(API)了 + * 6、服务都注册好后,剩下的就是墨守陈规,使用netty api 配置和启动我们的rpc服务器了 + * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 10:36 2021/5/18 + */ +@Component +public class RpcServer implements ApplicationContextAware, InitializingBean { + + /** + * DI:注入服务注册接口(bean),项目中内置了zk的实现方式,当然还可以用redis、consul、nacos等 + * 注意:多个实现类,注入的时候记得区分下 + */ + @Autowired + private ServiceRegistry serviceRegistry; + + @Value("${server.port}") + private int port; + private String ip; + private ApplicationContext applicationContext; + /*key:接口名+实现接口的类的类型(两个合起来可以唯一确定一个bean),value:服务实现对应的bean*/ + private Map serviceMappings = new HashMap<>(16); + + /*** + * 扫描RPC服务包中所有带@RpcService注解的bean,然后将beanName和bean添加到服务映射中 + * @param context Spring上下文 == IOC Container + */ + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + LoggerUtils.debug("RPC-SERVER,开始扫描Service"); + this.applicationContext = context; + /**通过spring ioc容器,获取所有加了@RpcService对应的beansMap */ + Map serviceBeansMap = context.getBeansWithAnnotation(RpcService.class); + /** 解析beansMap,一一向(分布式服务治理)zk注册服务(创建节点) */ + if (GeneralUtils.isEmpty(serviceBeansMap) || GeneralUtils.isEmpty(serviceBeansMap)){ + return; + } + try{ + /*获取本机IP*/ + InetAddress addr = InetAddress.getLocalHost(); + this.ip = addr.getHostAddress(); + }catch (Exception e){ + this.ip = "127.0.0.1"; + } + String serviceAddress = ip +":"+port; + for (Object bean : serviceBeansMap.values()) { + String apiName = getServiceName(bean); + /*往rpc服务端全局接口bean映射map中添加k-v*/ + this.serviceMappings.put(apiName,bean); + /*向zk注册服务(创建服务节点)*/ + serviceRegistry.register(apiName,serviceAddress); + } + } + + private String getServiceName(Object bean) { + String apiName; + /*获取(接口)bean的注解*/ + RpcService rpcService = bean.getClass().getAnnotation(RpcService.class); + /*分别获取注解的value(实现的接口类型)和name(接口实现类的名称或称作实现方式)值*/ + Class value = rpcService.value(); + String name = rpcService.name(); + if (value.getName().equals("java.lang.Void")){ + /*如果value默认是Void类型的话,说明接口实现类上没有主动标记接口Type*/ + Type[] genericInterfaces = bean.getClass().getGenericInterfaces(); + if (genericInterfaces != null && genericInterfaces[0] != null){ + apiName = genericInterfaces[0].getTypeName(); + }else { + throw new RuntimeException(String.format("无法获取{%s}接口类型!",bean.getClass().getName())); + } + }else{ + apiName = value.getName(); + } + if (StringUtils.isNotEmpty(name)){ + apiName +="-"+name; + } + return apiName; + } + + /** + * 开始搞netty + */ + @Override + public void afterPropertiesSet() throws Exception { + + LoggerUtils.debug("RPC-SERVER,开始配置"); + /*1、首先分别创建"老板"和"工人"两个线程池(组),老板负责接项目(请求),工人负责干活(处理请求)*/ + EventLoopGroup boss = new NioEventLoopGroup(1); + EventLoopGroup worker = new NioEventLoopGroup(NettyRuntime.availableProcessors() * 2); + try{ + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap + .group(boss,worker) + // 配置服务端IO通道类型 (客户端类型是:NioSocketChannel,注意区分) + .channel(NioServerSocketChannel.class) + // 服务端处理的请求线程满负荷时,用于临时存放tcp请求的队列最大长度 + .option(ChannelOption.SO_BACKLOG,128) + // 开启客户端心跳监测机制,当服务端监测到客户端长时间"不坑声"时,就会发送一个ack探测包给对方,已确认 + .childOption(ChannelOption.SO_KEEPALIVE,true) + // 配置"工人"线程组所需要的各种处理器(继承通道初始化器类即可) + .childHandler(new RpcServerChannelInitializer(serviceMappings)); + /*sync()能确保channelFuture一定是已经完成的*/ + ChannelFuture channelFuture = serverBootstrap.bind(ip, port).sync(); + System.out.printf("Rpc-Server,已启动,监听地址:%s:%d 目前正等待服务消费者请求....\n",ip,port); + channelFuture.channel().closeFuture().sync(); + }catch (Exception ex){ + LoggerUtils.error("Rpc-Server服务器启动异常:{}", ex.getMessage()); + }finally { + /*优雅的关闭线程池(断开连接 + 清理内存)*/ + boss.shutdownGracefully(); + worker.shutdownGracefully(); + } + } + + public ApplicationContext getApplicationContext() { + return applicationContext; + } +} diff --git a/rpc-server/src/main/java/com/appleyk/rpc/server/RpcServerChannelInitializer.java b/rpc-server/src/main/java/com/appleyk/rpc/server/RpcServerChannelInitializer.java new file mode 100644 index 0000000..e06f966 --- /dev/null +++ b/rpc-server/src/main/java/com/appleyk/rpc/server/RpcServerChannelInitializer.java @@ -0,0 +1,46 @@ +package com.appleyk.rpc.server; + +import com.appleyk.rpc.common.codec.RpcDecoder; +import com.appleyk.rpc.common.codec.RpcEncoder; +import com.appleyk.rpc.common.util.LoggerUtils; +import com.appleyk.rpc.core.model.result.RpcRequest; +import com.appleyk.rpc.core.model.result.RpcResponse; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; + +import java.util.Map; + +/** + *

Rpc服务端通道处理器

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 15:41 2021/5/18 + */ +public class RpcServerChannelInitializer extends ChannelInitializer { + + private final Map ioc ; + + public RpcServerChannelInitializer(Map ioc) { + this.ioc = ioc; + } + + @Override + protected void initChannel(SocketChannel sc) throws Exception { + /*从服务端通信通道中获取其关联的管道对象*/ + ChannelPipeline pipeline = sc.pipeline(); + try { + /*往管道里添加消息解码器*/ + pipeline.addLast(new RpcDecoder(RpcRequest.class)); + /*往管道里添加消息编码器*/ + pipeline.addLast(new RpcEncoder(RpcResponse.class)); + /*往管道里添加RPC自己的消息处理器*/ + pipeline.addLast(new RpcServerHandler(ioc)); + } catch (Exception e) { + LoggerUtils.error(e.getMessage()); + } + } +} diff --git a/rpc-server/src/main/java/com/appleyk/rpc/server/RpcServerHandler.java b/rpc-server/src/main/java/com/appleyk/rpc/server/RpcServerHandler.java new file mode 100644 index 0000000..1c11456 --- /dev/null +++ b/rpc-server/src/main/java/com/appleyk/rpc/server/RpcServerHandler.java @@ -0,0 +1,116 @@ +package com.appleyk.rpc.server; + +import com.appleyk.rpc.common.excp.RpcApiNotFoundException; +import com.appleyk.rpc.common.excp.RpcServiceBeanNotFoundException; +import com.appleyk.rpc.common.util.DateUtils; +import com.appleyk.rpc.common.util.GeneralUtils; +import com.appleyk.rpc.common.util.LoggerUtils; +import com.appleyk.rpc.core.model.result.RpcRequest; +import com.appleyk.rpc.core.model.result.RpcResponse; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +import java.lang.reflect.Method; +import java.util.Date; +import java.util.Map; + +/** + *

RPC服务端通道消息处理器

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 16:06 2021/5/18 + */ +public class RpcServerHandler extends SimpleChannelInboundHandler { + + private final Map springContext; + + public RpcServerHandler(Map springContext) { + this.springContext = springContext; + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + System.out.println("请求(连接)进来了!"); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + System.out.println("请求(连接)断开了!"); + } + + /**接收客户端(服务消费者)发过来的消息,处理request请求*/ + @Override + protected void channelRead0(ChannelHandlerContext ctx, RpcRequest request) throws Exception { + Channel channel = ctx.channel(); + System.out.printf("接收来自客户端:{%s}的请求,时间:%s\n",channel.remoteAddress(), DateUtils.dateNow2Str()); + /*基于request,反射,调用相应的接口*/ + try{ + Object result = requestHandler(request); + /*包装响应对象返回给客户端*/ + RpcResponse response = RpcResponse.builder() + .requestId(request.getRequestId()) + .date(new Date()) + .result(result).build(); + channel.writeAndFlush(response); + }catch (Exception ex){ + RpcResponse response = RpcResponse.builder() + .requestId(request.getRequestId()) + .date(new Date()) + .result(null) + .e(ex).build(); + channel.writeAndFlush(response); + } + } + + private Object requestHandler(RpcRequest request) throws Exception { + + if (!isValid(request)){ + throw new RpcApiNotFoundException("请求传参出了问题,无法识别出接口或要调用的接口方法,请核查!"); + } + + /*接口名称*/ + String interfaceName = request.getInterfaceName(); + /*接口实现类的类型*/ + String serviceImplType = request.getType(); + String methodName = request.getMethodName(); + Class[] parameterTypes = request.getParameterTypes(); + Object[] parameters = request.getParameters(); + String apiName = GeneralUtils.isEmpty(serviceImplType) ? interfaceName : interfaceName+"-"+serviceImplType; + Object serviceBean = springContext.get(apiName); + if (serviceBean == null) { + throw new RpcServiceBeanNotFoundException(String.format("This interface {%s} does not exist !", apiName)); + } + Class beanClass = serviceBean.getClass(); + try { + /*基于反射技术,拿到对象的指定方法名指定参数类的方法对象*/ + Method method = beanClass.getMethod(methodName, parameterTypes); + /*基于反射技术,调用方法对象的invoke方法(反向操作就是我们直接用实例来调用它的方法)*/ + return method.invoke(serviceBean, parameters); + }catch (Exception e){ + throw new RuntimeException("利用反射调用bean的方法异常!"+e.getMessage()); + } + } + + private boolean isValid(RpcRequest request){ + /*先解析一波request,验证下是不是有没有漏传的内容,比如接口和方法名没有传..e.tc*/ + System.out.println("#请求唯一ID:"+request.getRequestId()); + System.out.println("#接口名称:"+request.getInterfaceName()); + System.out.println("#接口实现类类型:"+request.getType()); + System.out.println("#接口调用方法:"+request.getMethodName()); + return GeneralUtils.isNotEmpty(request.getRequestId()) + && GeneralUtils.isNotEmpty(request.getInterfaceName()) + && GeneralUtils.isNotEmpty(request.getMethodName()); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + Channel clientChannel = ctx.channel(); + LoggerUtils.error("客户端:{}发送来的请求处理异常,{}\n",clientChannel.localAddress(),cause.getMessage()); + /*关闭通道处理器上下文*/ + ctx.close(); + } +} diff --git a/rpc-server/src/main/java/com/appleyk/rpc/server/autoconfigure/RpcServerAutoConfigure.java b/rpc-server/src/main/java/com/appleyk/rpc/server/autoconfigure/RpcServerAutoConfigure.java new file mode 100644 index 0000000..203781c --- /dev/null +++ b/rpc-server/src/main/java/com/appleyk/rpc/server/autoconfigure/RpcServerAutoConfigure.java @@ -0,0 +1,20 @@ +package com.appleyk.rpc.server.autoconfigure; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + *

rpc-server's beans 自动装配

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @date created on 下午10:40 2021/5/19 + */ +@Configuration +@ComponentScan("com.appleyk.rpc.server") +public class RpcServerAutoConfigure { + public RpcServerAutoConfigure() { + System.out.println("=== RPC-SERVER complete automatic assembly !==="); + } +} diff --git a/rpc-server/src/main/resources/META-INF/spring.factories b/rpc-server/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..673dd2b --- /dev/null +++ b/rpc-server/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.appleyk.rpc.server.autoconfigure.RpcServerAutoConfigure \ No newline at end of file diff --git a/rpc-use-case/pom.xml b/rpc-use-case/pom.xml new file mode 100644 index 0000000..34f0dd0 --- /dev/null +++ b/rpc-use-case/pom.xml @@ -0,0 +1,24 @@ + + + + + seven-rpc + com.appleyk + 0.1.1-SNAPSHOT + + + 4.0.0 + rpc-use-case + pom + rpc使用案例父工程 + + + rpc-demo-consumer + rpc-demo-provider + rpc-demo-api + + + + \ No newline at end of file diff --git a/rpc-use-case/rpc-demo-api/pom.xml b/rpc-use-case/rpc-demo-api/pom.xml new file mode 100644 index 0000000..4d1c084 --- /dev/null +++ b/rpc-use-case/rpc-demo-api/pom.xml @@ -0,0 +1,29 @@ + + + + rpc-use-case + com.appleyk + 0.1.1-SNAPSHOT + + 4.0.0 + rpc-demo-api + 定义公共接口(真实开发中,可以按业务拆分成不同的接口模块) + + + ${artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/api/CacheService.java b/rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/api/CacheService.java new file mode 100644 index 0000000..7d7dd32 --- /dev/null +++ b/rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/api/CacheService.java @@ -0,0 +1,11 @@ +package com.appleyk.rpc.api; + +/** + * 公共接口 + */ +public interface CacheService { + String save(String key,Object data); + Object find(String key); + void update(String key,Object data); + void delete(String key); +} diff --git a/rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/api/OrderService.java b/rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/api/OrderService.java new file mode 100644 index 0000000..1ad76e0 --- /dev/null +++ b/rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/api/OrderService.java @@ -0,0 +1,21 @@ +package com.appleyk.rpc.api; + +import com.appleyk.rpc.model.Order; + +import java.util.List; + +/** + *

订单(接口)服务

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 9:19 2021/5/27 + */ +public interface OrderService { + Order save(Order order); + boolean delete(Long id); + Order update(Order order); + List query(); +} diff --git a/rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/model/Order.java b/rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/model/Order.java new file mode 100644 index 0000000..6951d35 --- /dev/null +++ b/rpc-use-case/rpc-demo-api/src/main/java/com/appleyk/rpc/model/Order.java @@ -0,0 +1,69 @@ +package com.appleyk.rpc.model; + +import java.util.Date; + +/** + *

订单

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 9:26 2021/5/27 + */ +public class Order { + + private Long id ; + private String name; + private Double price; + private Integer amount; + private Date cTime; + + public Order(){} + + public Order(String name, Double price, Integer amount) { + this.name = name; + this.price = price; + this.amount = amount; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public Integer getAmount() { + return amount; + } + + public void setAmount(Integer amount) { + this.amount = amount; + } + + public Date getcTime() { + return cTime; + } + + public void setcTime(Date cTime) { + this.cTime = cTime; + } +} diff --git a/rpc-use-case/rpc-demo-consumer/pom.xml b/rpc-use-case/rpc-demo-consumer/pom.xml new file mode 100644 index 0000000..9a16626 --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/pom.xml @@ -0,0 +1,80 @@ + + + + + rpc-use-case + com.appleyk + 0.1.1-SNAPSHOT + + + + 2.9.2 + + + 4.0.0 + rpc-demo-consumer + rpc案例客户端(服务消费者,服务调用方) + war + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + com.appleyk + rpc-demo-api + ${project.version} + + + com.appleyk + rpc-client + ${project.version} + + + com.appleyk + rpc-registry-zookeeper + ${project.version} + + + io.springfox + springfox-swagger2 + ${springfox.swagger2.version} + + + + io.springfox + springfox-swagger-ui + ${springfox.swagger2.version} + + + + + rpc-consumer-web + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-war-plugin + 3.2.2 + + + + + \ No newline at end of file diff --git a/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/ClientApp.java b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/ClientApp.java new file mode 100644 index 0000000..7518690 --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/ClientApp.java @@ -0,0 +1,28 @@ +package com.appleyk.rpc.sample.client; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + *

RPC客户端用例启动类,项目启动后浏览器输入:http://localhost:8066/swagger-ui.html

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 15:16 2021/3/16 + */ +@SpringBootApplication +public class ClientApp extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(ClientApp.class,args); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(ClientApp.class); + } +} diff --git a/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/config/RpcConfig.java b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/config/RpcConfig.java new file mode 100644 index 0000000..4ea3996 --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/config/RpcConfig.java @@ -0,0 +1,48 @@ +package com.appleyk.rpc.sample.client.config; + +import com.appleyk.rpc.client.annotion.EnableSeven; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +/** + *

+ * Rpc"代理"配置,主要是扫描公共接口包, + * 改写spring扫描bean定义的条件,然后借助FactoryBean实现动态代理 + *

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 17:34 2021/5/19 + */ +@Configuration +//开启rpc功能(指定扫码的基础包,以对包下面的service bean进行一个rpc方式的代理) +@EnableSeven(scanBasePackages = "com.appleyk.rpc.api") +@EnableSwagger2 +public class RpcConfig { + + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2) + .pathMapping("/") + .select() + .apis(RequestHandlerSelectors.basePackage("com.appleyk.rpc.sample.client.controller")) + .paths(PathSelectors.any()) + .build().apiInfo(new ApiInfoBuilder() + .title("Rpc(远程过程调用)API文档") + .description("基于SpringBoot+Netty两大框架实现的一个简易版的个人RPC框架") + .version("v0.1.1") + .contact(new Contact("Appleyk","https://blog.csdn.net/Appleyk", + "yukun24@126.com")) + .build()); + } + +} diff --git a/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/AopController.java b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/AopController.java new file mode 100644 index 0000000..84a9f15 --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/AopController.java @@ -0,0 +1,32 @@ +package com.appleyk.rpc.sample.client.controller; + +import com.appleyk.rpc.common.result.HttpResult; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +/** + *

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 13:36 2021/5/27 + */ +@CrossOrigin +@RestController +@RequestMapping("/rpc") +@Api(tags = "1、AOP统计接口执行时间用例测试") +public class AopController { + @ApiOperation("AOP(面向切面编程)用例,主要为了测试玩,本质上和rpc框架无关!") + @ApiImplicitParams({ + @ApiImplicitParam(name = "name", value = "姓名", defaultValue = "appleyk") + }) + @GetMapping("/aop") + public HttpResult aop(@RequestParam(value = "name",required = false,defaultValue = "appleyk") String name){ + return HttpResult.ok("hello,"+name); + } +} diff --git a/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/CacheController.java b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/CacheController.java new file mode 100644 index 0000000..05876fa --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/CacheController.java @@ -0,0 +1,90 @@ +package com.appleyk.rpc.sample.client.controller; + +import com.appleyk.rpc.api.CacheService; +import com.appleyk.rpc.client.annotion.RpcAutowired; +import com.appleyk.rpc.common.result.HttpResult; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +/** + *

RPC服务消费端接口用例测试

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 16:48 2021/5/19 + */ +@CrossOrigin +@RestController +@RequestMapping("/rpc") +@Api(tags = "2、缓存API用例测试") +public class CacheController { + + @RpcAutowired("mongodb") + private CacheService cacheService; + + + /** + * 缓存保存 + * @param key 键 + * @param val 值 + * @return 响应结果 + */ + @ApiOperation("缓存保存(RPC)用例,主要测试k-v的保存接口功能是否OK") + @ApiImplicitParams({ + @ApiImplicitParam(name = "key", value = "键", defaultValue = "作者"), + @ApiImplicitParam(name = "val", value = "值", defaultValue = "appleyk") + }) + @GetMapping("/cache/save") + public HttpResult save(@RequestParam(value = "key",defaultValue = "作者") String key, + @RequestParam(value = "val",defaultValue = "appleyk") String val){ + String result = cacheService.save(key, val); + return HttpResult.ok(result); + } + + + @ApiOperation("缓存清除(RPC)用例,主要测试k-v的清除接口功能是否OK") + @ApiImplicitParams({ + @ApiImplicitParam(name = "key", value = "键", defaultValue = "作者"), + @ApiImplicitParam(name = "val", value = "值", defaultValue = "appleyk") + }) + @DeleteMapping("/cache/delete") + public HttpResult delete(@RequestParam(value = "key",defaultValue = "作者") String key){ + cacheService.delete(key); + return HttpResult.ok("删除成功!"); + } + + + @ApiOperation("缓存更新(RPC)用例,主要测试k-v的更新接口功能是否OK") + @ApiImplicitParams({ + @ApiImplicitParam(name = "key", value = "键", defaultValue = "作者"), + @ApiImplicitParam(name = "val", value = "值", defaultValue = "appleyk") + }) + @GetMapping("/cache/update") + public HttpResult update(@RequestParam(value = "key",defaultValue = "作者") String key, + @RequestParam(value = "val",defaultValue = "appleyk") String val){ + cacheService.update(key, val); + return HttpResult.ok("更新成功!"); + } + + /** + * 缓存查询 + * @param key 键 + * @return 响应结果 + */ + @ApiOperation("缓存查询(RPC)用例,主要测试k-v的查询接口功能是否OK") + @ApiImplicitParams({ + @ApiImplicitParam(name = "key", value = "键", defaultValue = "作者") + }) + @GetMapping("/cache/query") + public HttpResult query(@RequestParam(value = "key",defaultValue = "作者") String key){ + Object result = cacheService.find(key); + return HttpResult.ok(result); + } + + +} diff --git a/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/OrderController.java b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/OrderController.java new file mode 100644 index 0000000..179963c --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/controller/OrderController.java @@ -0,0 +1,76 @@ +package com.appleyk.rpc.sample.client.controller; + +import com.appleyk.rpc.api.OrderService; +import com.appleyk.rpc.client.annotion.RpcAutowired; +import com.appleyk.rpc.common.result.HttpResult; +import com.appleyk.rpc.model.Order; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +/** + *

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 13:18 2021/5/27 + */ +@CrossOrigin +@RestController +@RequestMapping("/rpc") +@Api(tags = "3、订单API用例测试") +public class OrderController { + + @RpcAutowired + private OrderService orderService; + + @ApiOperation("订单保存(RPC)用例,主要测试订单数据存储接口功能是否OK") + @ApiImplicitParams({ + @ApiImplicitParam(name = "order", value = "订单") + }) + /** + { + "name":"华为手机", + "price":4002.5, + "amount":500 + } + */ + @PostMapping("/order/save") + public HttpResult save(@RequestBody Order order){ + Order result = orderService.save(order); + return HttpResult.ok(result); + } + + @ApiOperation("订单删除(RPC)用例,主要测试订单数据删除接口功能是否OK") + @ApiImplicitParams({ + @ApiImplicitParam(name = "id", value = "订单唯一标识") + }) + @DeleteMapping("/order/delete/{id}") + public HttpResult delete(@PathVariable(name = "id") Long id){ + return HttpResult.ok(orderService.delete(id)); + } + + @ApiOperation("订单更新(RPC)用例,主要测试订单数据更新接口功能是否OK") + @ApiImplicitParams({ + @ApiImplicitParam(name = "order", value = "订单") + }) + @PostMapping("/order/update") + public HttpResult update(@RequestBody Order order){ + Order result = orderService.update(order); + return HttpResult.ok(result); + } + + @ApiOperation("订单查询(RPC)用例,主要测试订单数据查询接口功能是否OK") + @ApiImplicitParams({ + @ApiImplicitParam(name = "order", value = "订单") + }) + @GetMapping("/order/query") + public HttpResult query(){ + return HttpResult.ok(orderService.query()); + } + +} diff --git a/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/excep/ExceptionControllerAdvice.java b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/excep/ExceptionControllerAdvice.java new file mode 100644 index 0000000..49141a1 --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/main/java/com/appleyk/rpc/sample/client/excep/ExceptionControllerAdvice.java @@ -0,0 +1,25 @@ +package com.appleyk.rpc.sample.client.excep; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +/** + *

全局(API)异常拦截类,有异常了,把异常返回给Client,而不是将异常留在Server端

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 17:20 2021/5/24 + */ +@CrossOrigin +@ControllerAdvice +@RestControllerAdvice +public class ExceptionControllerAdvice { + @ResponseBody + @ExceptionHandler({Exception.class}) + public ResponseEntity errorHandler(Exception ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage()); + } +} diff --git a/rpc-use-case/rpc-demo-consumer/src/main/resources/application.yml b/rpc-use-case/rpc-demo-consumer/src/main/resources/application.yml new file mode 100644 index 0000000..d1185cb --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/main/resources/application.yml @@ -0,0 +1,9 @@ +server: + port: 8066 + +seven: + rpc: + zookeeper: + host: 127.0.0.1 + port: 2181 + timeout: 60000 #zk会话过期时间,当临时节点创建后,如果断开连接,则10秒后,临时节点就会被删除 \ No newline at end of file diff --git a/rpc-use-case/rpc-demo-consumer/src/main/resources/banner.txt b/rpc-use-case/rpc-demo-consumer/src/main/resources/banner.txt new file mode 100644 index 0000000..e56b1e2 --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/main/resources/banner.txt @@ -0,0 +1,12 @@ + _____ .___ .___ ___ + ( ___ _ __ ___ , __ / \ / \ .' \ + `--. .' ` | / .' ` |' `. .---' |__-' |,_-' | + | |----' ` / |----' | | | \ | | + \___.' `.___, \/ `.___, / | / \ / `.__, + +SpringBoot Version : ${spring-boot.version} +Banner制作地址:https://www.bootschool.net/ascii/ +项目名称 RPC-CLIENT v0.1.1 +项目作者 Appleyk +构建时间 2021年05月18日 周二 11:22:66.888 +服务端口号 ${server.port} \ No newline at end of file diff --git "a/rpc-use-case/rpc-demo-consumer/src/main/resources/images/\350\247\243\347\240\201\345\231\250\345\267\245\344\275\234\302\267\346\225\210\346\236\234\345\233\276_01.jpg" "b/rpc-use-case/rpc-demo-consumer/src/main/resources/images/\350\247\243\347\240\201\345\231\250\345\267\245\344\275\234\302\267\346\225\210\346\236\234\345\233\276_01.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..9ea9df57154049cd833c307909a9333e98344bd3 GIT binary patch literal 214776 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℞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

4=d@!)r&FXbxr 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∓TR$X00*B@KFAsecR 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

Netty·ByteBuf对象使用,其封装了NIO的ByteBuffer对象,使其API更好操作

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 14:03 2021/5/19 + */ +public class NettyByteBufTest { + @Test + public void byteBuf(){ + + /*在内存开辟一个byte[10]大小的空间*/ + ByteBuf buf = Unpooled.buffer(10); + + /* + * 在Netty的ByteBuf中,不需要使用flip函数对读写进行翻转 + * 因为其底层维护了一个readerIndex(读的下标)和writerIndex(写的下标) + * 0 - readerIndex : 已经读取的区域 + * readerIndex - writerIndex : 剩余可读的区域 + * writerIndex - capacity : 剩余可写的区域 + */ + for (int i = 0; i < 10; i++) { + buf.writeByte(i); + } + System.out.println("容量:"+buf.capacity()); + /*开始读*/ + for (int i = 0; i < 10 ; i++) { + buf.readByte(); + } + + /* + * 读完了,readerIndex=10了,导致不能再读了 + * public boolean isReadable() { + * return this.writerIndex > this.readerIndex; + * } + * 此时writerIndex == readerIndex + */ + /*重置下读指针,就可以继续读了*/ + buf.resetReaderIndex(); + int res = buf.readByte(); + System.out.println(res); + } + + @Test + public void writeInt(){ + ByteBuf buf = Unpooled.directBuffer(10); + buf.writeInt(10); + int i = buf.readInt(); + System.out.println(i); + } + + @Test + public void markedReaderIndex(){ + int capacity = 3; + ByteBuf buf = Unpooled.buffer(capacity); + for (int i = 0; i < capacity ; i++) { + buf.writeInt(i+1); + } + buf.readInt(); + /*标记下下一次读取的位置*/ + buf.markReaderIndex(); + int i = buf.readInt(); + System.out.println(i); + } + + /** + * ByteBuf是按类型顺序写入、然后顺序读取的,前往不要乱读 + */ + @Test + public void writeAndRead() throws Exception{ + int capacity = 2; + ByteBuf buf = Unpooled.buffer(capacity); + /*内容的长度5写进去,这样读取的时候可以按长度去读,防止tcp数据包的拆分和粘在一起的问题*/ + buf.writeInt(5); + buf.writeBytes("hello".getBytes()); + buf.writeInt(3); + buf.writeBytes("中".getBytes("utf-8")); + + /*buf中总过可读字节数, 5占4个字节,hello占5个字节,中占3个字节,可读字节总过 = 5+4+3 = 12*/ + int readableBytes = buf.readableBytes(); + System.out.println("buf中可读取的字节数:"+readableBytes); + + /*读取"hello"的长度*/ + int len = buf.readInt(); + System.out.printf("要读取的内容长度:%d\n",len); + byte[] content = new byte[len]; + buf.readBytes(content); + System.out.printf("要读取的内容:%s",new String(content)); + + /*读取"中"的长度*/ + len = buf.readInt(); + System.out.printf("要读取的内容长度:%d\n",len); + content = new byte[len]; + buf.readBytes(content); + System.out.printf("要读取的内容:%s",new String(content)); + + } +} diff --git a/rpc-use-case/rpc-demo-consumer/src/test/java/RpcServiceBeanTest.java b/rpc-use-case/rpc-demo-consumer/src/test/java/RpcServiceBeanTest.java new file mode 100644 index 0000000..157ac9b --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/test/java/RpcServiceBeanTest.java @@ -0,0 +1,62 @@ +import com.appleyk.rpc.api.CacheService; +import com.appleyk.rpc.api.OrderService; +import com.appleyk.rpc.client.annotion.RpcAutowired; +import com.appleyk.rpc.common.util.JsonUtils; +import com.appleyk.rpc.model.Order; +import com.appleyk.rpc.sample.client.ClientApp; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +/** + *

Rpc service bean 测试

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 14:13 2021/5/26 + */ +@SpringBootTest(classes = ClientApp.class) +public class RpcServiceBeanTest { + + /**第一种,使用我们自己定义的自动注入·注解,我们自己的可以指定bean的类型*/ + @RpcAutowired("mongodb") + private CacheService cacheService1; + + /**第二种,使用Spring自带的自动注入·注解*/ + @Autowired + private CacheService cacheService2; + + /**第二种,使用Spring自带的自动注入·注解*/ + @RpcAutowired + private OrderService orderService; + + @Test + public void cacheSave1(){ + String result = cacheService1.save("111", "222"); + System.out.println("====> "+result); + } + + @Test + public void cacheSave2(){ + String result = cacheService2.save("111", "222"); + System.out.println("====> "+result); + } + + @Test + public void orderSave(){ + Order order = orderService.save(new Order("华为手机", 4200.21, 500)); + System.out.println(JsonUtils.parserJson(order)); + } + + @Test + public void orderQuery(){ + List orders = orderService.query(); + System.out.println("====> "+orders); + } + +} + diff --git a/rpc-use-case/rpc-demo-consumer/src/test/java/ZkCenterTest.java b/rpc-use-case/rpc-demo-consumer/src/test/java/ZkCenterTest.java new file mode 100644 index 0000000..982b155 --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/test/java/ZkCenterTest.java @@ -0,0 +1,55 @@ +import com.appleyk.rpc.sample.client.ClientApp; +import com.appleyk.rpc.registry.ServiceDiscovery; +import com.appleyk.rpc.registry.ServiceRegistry; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + *

zk服务注册和发现中心功能测试

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 15:12 2021/3/16 + */ +@SpringBootTest(classes = ClientApp.class) +public class ZkCenterTest { + + @Autowired + private ServiceRegistry registry; + + @Autowired + private ServiceDiscovery discovery; + + String serviceName = "com.appleyk.rpc.api.CacheService-mongodb"; + String serviceAddress = "localhost:8080"; + + @Test + public void registry(){ + registry.register(serviceName,serviceAddress); + } + + @Test + public void discover(){ + System.out.println(discovery.discover(serviceName)); + } + + /** + * 服务ribbon测试,就是简单的随机策略 + */ + @Test + public void choose(){ + List children = new ArrayList<>(); + children.add(1); + children.add(2); + int answer = new Random().nextInt(children.size()); + int result = children.get(answer); + System.out.println("帮你选择的结果是:"+result); + } +} diff --git a/rpc-use-case/rpc-demo-provider/pom.xml b/rpc-use-case/rpc-demo-provider/pom.xml new file mode 100644 index 0000000..6044485 --- /dev/null +++ b/rpc-use-case/rpc-demo-provider/pom.xml @@ -0,0 +1,62 @@ + + + + + rpc-use-case + com.appleyk + 0.1.1-SNAPSHOT + + + 4.0.0 + rpc-demo-provider + rpc案例服务端(服务提供者) + war + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + com.appleyk + rpc-demo-api + ${project.version} + + + com.appleyk + rpc-server + ${project.version} + + + + + + rpc-provider-web + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-war-plugin + 3.2.2 + + + + + \ No newline at end of file diff --git a/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/ServerApp.java b/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/ServerApp.java new file mode 100644 index 0000000..ed5d3af --- /dev/null +++ b/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/ServerApp.java @@ -0,0 +1,28 @@ +package com.appleyk.rpc.sample.server; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + *

RPC服务端用例启动类

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 11:15 2021/5/18 + */ +@SpringBootApplication +public class ServerApp extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(ServerApp.class,args); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(ServerApp.class); + } +} diff --git a/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/MongoDbCacheServiceImpl.java b/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/MongoDbCacheServiceImpl.java new file mode 100644 index 0000000..b813d22 --- /dev/null +++ b/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/MongoDbCacheServiceImpl.java @@ -0,0 +1,49 @@ +package com.appleyk.rpc.sample.server.impl; + +import com.appleyk.rpc.api.CacheService; +import com.appleyk.rpc.core.annotation.RpcService; +import org.springframework.context.annotation.Primary; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

Rpc服务端,XXXX接口实现类 ==> MB模拟

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 11:55 2021/5/18 + */ +@Primary +@RpcService(name = "mongodb") //暴露服务 +public class MongoDbCacheServiceImpl implements CacheService { + + private Map caches = new ConcurrentHashMap<>(); + + @Override + public String save(String key, Object data) { + System.out.printf("MongoDb实现缓存#save,key={%s}\n",key); + caches.put(key,data); + return String.format("key:%s,value:%s",key,data); + } + + @Override + public Object find(String key) { + System.out.printf("MongoDb实现缓存#find,key={%s}\n",key); + return caches.getOrDefault(key,"None"); + } + + @Override + public void update(String key, Object data) { + System.out.printf("MongoDb实现缓存#update,key={%s}\n",key); + caches.put(key,data); + } + + @Override + public void delete(String key) { + System.out.printf("MongoDb实现缓存#delete,key={%s}\n",key); + caches.remove(key); + } +} diff --git a/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/OrderServiceImpl.java b/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/OrderServiceImpl.java new file mode 100644 index 0000000..e89b4b9 --- /dev/null +++ b/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/OrderServiceImpl.java @@ -0,0 +1,55 @@ +package com.appleyk.rpc.sample.server.impl; + +import com.appleyk.rpc.api.OrderService; +import com.appleyk.rpc.model.Order; +import com.appleyk.rpc.common.util.IdUtils; +import com.appleyk.rpc.core.annotation.RpcService; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

订单服务实现类

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 9:30 2021/5/27 + */ +@RpcService +//@Scope("prototype") +public class OrderServiceImpl implements OrderService { + + /*注意这个是单例bean中的变量,真实场景中不要这么做,对于有状态的bean还是要配置它的scope = "prototype"的*/ + private Map orders = new ConcurrentHashMap<>(); + + @Override + public Order save(Order order) { + long id = IdUtils.getId(); + order.setId(id); + order.setcTime(new Date()); + orders.put(id,order); + return order; + } + + @Override + public boolean delete(Long id) { + orders.remove(id); + return true; + } + + @Override + public Order update(Order order) { + orders.put(order.getId(),order); + return order; + } + + @Override + public List query() { + return new ArrayList<>(orders.values()); + } +} diff --git a/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/RedisCacheServiceImpl.java b/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/RedisCacheServiceImpl.java new file mode 100644 index 0000000..ed7a21c --- /dev/null +++ b/rpc-use-case/rpc-demo-provider/src/main/java/com/appleyk/rpc/sample/server/impl/RedisCacheServiceImpl.java @@ -0,0 +1,46 @@ +package com.appleyk.rpc.sample.server.impl; + +import com.appleyk.rpc.api.CacheService; +import com.appleyk.rpc.core.annotation.RpcService; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *

Rpc服务端,XXXX接口实现类

+ * + * @author appleyk + * @version V.0.1.1 + * @blob https://blog.csdn.net/appleyk + * @github https://github.com/kobeyk + * @date created on 11:55 2021/5/18 + */ +@RpcService(CacheService.class) +public class RedisCacheServiceImpl implements CacheService { + + private Map caches = new ConcurrentHashMap<>(); + + @Override + public String save(String key, Object data) { + System.out.printf("Redis实现缓存#save,key={%s}\n",key); + caches.put(key,data); + return String.format("key:%s,value:%s",key,data); + } + + @Override + public Object find(String key) { + System.out.printf("Redis实现缓存#find,key={%s}\n",key); + return caches.getOrDefault(key,"None"); + } + + @Override + public void update(String key, Object data) { + System.out.printf("Redis实现缓存#update,key={%s}\n",key); + caches.put(key,data); + } + + @Override + public void delete(String key) { + System.out.printf("Redis实现缓存#delete,key={%s}\n",key); + caches.remove(key); + } +} diff --git a/rpc-use-case/rpc-demo-provider/src/main/resources/application.yml b/rpc-use-case/rpc-demo-provider/src/main/resources/application.yml new file mode 100644 index 0000000..8c94bdc --- /dev/null +++ b/rpc-use-case/rpc-demo-provider/src/main/resources/application.yml @@ -0,0 +1,9 @@ +server: + port: 8077 + +seven: + rpc: + zookeeper: + host: 127.0.0.1 + port: 2181 + timeout: 60000 #zk会话过期时间,当临时节点创建后,如果断开连接,则10秒后,临时节点就会被删除 \ No newline at end of file diff --git a/rpc-use-case/rpc-demo-provider/src/main/resources/banner.txt b/rpc-use-case/rpc-demo-provider/src/main/resources/banner.txt new file mode 100644 index 0000000..22b94e5 --- /dev/null +++ b/rpc-use-case/rpc-demo-provider/src/main/resources/banner.txt @@ -0,0 +1,11 @@ + _____ .___ .___ ___ + ( ___ _ __ ___ , __ / \ / \ .' \ + `--. .' ` | / .' ` |' `. .---' |__-' |,_-' | + | |----' ` / |----' | | | \ | | + \___.' `.___, \/ `.___, / | / \ / `.__, + +SpringBoot Version : ${spring-boot.version} +Banner制作地址:https://www.bootschool.net/ascii/ +项目名称 RPC-SERVER v0.1.1 +项目作者 Appleyk +构建时间 2021年05月18日 周二 11:22:66.888 \ No newline at end of file diff --git a/rpc-use-case/rpc-demo-provider/src/main/resources/logback-spring.xml b/rpc-use-case/rpc-demo-provider/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..efc177d --- /dev/null +++ b/rpc-use-case/rpc-demo-provider/src/main/resources/logback-spring.xml @@ -0,0 +1,46 @@ + + + + + + + %d %p (%file:%line\)- %m%n + + UTF-8 + + + + + + + ../logs/sys-server.log + + + + + ../logs/sys-server.%d.%i.log + + 30 + + + 1024KB + + + + + + %d %p (%file:%line\)- %m%n + + + UTF-8 + + + + + + + + + \ No newline at end of file diff --git a/rpc-use-case/rpc-demo-provider/src/test/java/ClassInterfacesTest.java b/rpc-use-case/rpc-demo-provider/src/test/java/ClassInterfacesTest.java new file mode 100644 index 0000000..cbadeee --- /dev/null +++ b/rpc-use-case/rpc-demo-provider/src/test/java/ClassInterfacesTest.java @@ -0,0 +1,16 @@ +import com.appleyk.rpc.sample.server.impl.MongoDbCacheServiceImpl; +import org.junit.jupiter.api.Test; +import java.lang.reflect.Type; + +public class ClassInterfacesTest { + /** + * 获取实现类直接实现的接口(包含泛型参数)数组 + * @param args + */ + @Test + public void main(String[] args) { + MongoDbCacheServiceImpl mongodb = new MongoDbCacheServiceImpl(); + Type[] genericInterfaces = mongodb.getClass().getGenericInterfaces(); + System.out.println(genericInterfaces[0].getTypeName()); + } +}

^P+FM%mabsDw4OK*w}OoQz&4SHU0TbTn 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�@}a!pL;F1GPH+>Z$6Y(zQsgb3;#tD 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 z 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 literal 0 HcmV?d00001 diff --git a/rpc-use-case/rpc-demo-consumer/src/main/resources/logback-spring.xml b/rpc-use-case/rpc-demo-consumer/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..0f18ff2 --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/main/resources/logback-spring.xml @@ -0,0 +1,46 @@ + + + + + + + %d %p (%file:%line\)- %m%n + + UTF-8 + + + + + + + ../logs/sys-client.log + + + + + ../logs/sys-client.%d.%i.log + + 30 + + + 1024KB + + + + + + %d %p (%file:%line\)- %m%n + + + UTF-8 + + + + + + + + + \ No newline at end of file diff --git a/rpc-use-case/rpc-demo-consumer/src/test/java/NettyByteBufTest.java b/rpc-use-case/rpc-demo-consumer/src/test/java/NettyByteBufTest.java new file mode 100644 index 0000000..2b73e6c --- /dev/null +++ b/rpc-use-case/rpc-demo-consumer/src/test/java/NettyByteBufTest.java @@ -0,0 +1,104 @@ +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.jupiter.api.Test; + +/** + *