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 0000000..9ea9df5 Binary files /dev/null and "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" differ 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; + +/** + *

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()); + } +}