├── .gitignore ├── README.md ├── pom.xml ├── publish.sh └── src └── main ├── java └── com │ └── github │ └── ship │ ├── annotation │ ├── InjectService.java │ ├── LoadBalanceAno.java │ ├── MessageProtocolAno.java │ └── Service.java │ ├── client │ ├── cache │ │ └── ServerDiscoveryCache.java │ ├── core │ │ ├── DefaultMethodInvoker.java │ │ └── MethodInvoker.java │ ├── generic │ │ ├── DefaultGenericService.java │ │ ├── GenericService.java │ │ └── GenericServiceFactory.java │ ├── manager │ │ ├── LoadBalanceManager.java │ │ ├── MessageProtocolsManager.java │ │ └── ServerDiscoveryManager.java │ ├── net │ │ ├── NetClient.java │ │ ├── NetClientFactory.java │ │ ├── NettyNetClient.java │ │ ├── RpcFuture.java │ │ └── handler │ │ │ ├── SendHandler.java │ │ │ └── SendHandlerV2.java │ └── proxy │ │ ├── AbstractClientProxyFactory.java │ │ ├── ClientProxyFactory.java │ │ └── impl │ │ ├── JavassistClientProxyFactory.java │ │ ├── JdkClientProxyFactory.java │ │ └── JdkCompilerClientProxyFactory.java │ ├── common │ ├── constants │ │ ├── ProxyTypeEnum.java │ │ ├── RegisterCenterTypeEnum.java │ │ ├── RpcConstant.java │ │ └── RpcStatusEnum.java │ ├── exception │ │ └── RpcException.java │ ├── model │ │ ├── RpcRequest.java │ │ ├── RpcResponse.java │ │ └── Service.java │ └── serializer │ │ └── ZookeeperSerializer.java │ ├── config │ ├── RpcAutoConfiguration.java │ └── properties │ │ └── RpcConfig.java │ ├── discovery │ ├── ServerDiscovery.java │ ├── ServerRegister.java │ ├── ServiceObject.java │ ├── nacos │ │ └── NacosServerDiscovery.java │ ├── register │ │ └── DefaultServerRegister.java │ └── zk │ │ ├── ZkChildListenerImpl.java │ │ └── ZookeeperServerDiscovery.java │ ├── server │ ├── NettyRpcServer.java │ ├── RequestHandler.java │ ├── RpcServer.java │ └── register │ │ └── DefaultRpcProcessor.java │ ├── spi │ ├── balance │ │ ├── LoadBalance.java │ │ └── impl │ │ │ ├── FullRoundBalance.java │ │ │ ├── RandomBalance.java │ │ │ ├── SmoothWeightRoundBalance.java │ │ │ └── WeightRoundBalance.java │ └── protocol │ │ ├── MessageProtocol.java │ │ └── impl │ │ ├── HessianMessageProtocol.java │ │ ├── JavaSerializeMessageProtocol.java │ │ ├── KryoMessageProtocol.java │ │ └── ProtoBufMessageProtocol.java │ └── util │ ├── ClassUtils.java │ ├── CodeGenerateUtils.java │ ├── GenericObjectUtil.java │ ├── ReflectUtils.java │ ├── RpcResponseUtils.java │ ├── SerializingUtil.java │ └── SpringContextHolder.java └── resources ├── META-INF ├── services │ ├── com.github.ship.spi.balance.LoadBalance │ └── com.github.ship.spi.protocol.MessageProtocol └── spring.factories └── log4j.properties /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | .mvn/ 35 | mvnw 36 | *.cmd 37 | *.log 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ship-rpc-spring-boot-starter 2 | ![](https://img.shields.io/github/stars/2YSP/rpc-spring-boot-starter.svg) 3 | ![](https://img.shields.io/github/forks/2YSP/rpc-spring-boot-starter.svg) 4 | ![](https://img.shields.io/github/release/2YSP/rpc-spring-boot-starter.svg) 5 | ![](https://img.shields.io/github/downloads/2YSP/rpc-spring-boot-starter/total.svg) 6 | 7 | 基于netty实现的高性能可扩展RPC框架 8 | 9 | # 一、特性 10 | - 支持多种序列化协议,包括java,protobuf,kryo和hessian 11 | - 客户端调用支持多种负载均衡算法,包括随机、轮询、加权轮询和平滑加权轮询 12 | - 支持主流注册中心Nacos和Zookeeper 13 | - 支持泛化调用 14 | - 客户端代理对象生成方式支持JDK动态代理、Javassist字节码和Java动态编译生成 15 | 16 | # 二、使用方法 17 | 18 | 19 | ## 2.1 pom.xml 20 | 添加maven依赖到你的项目中 21 | ```xml 22 | 23 | io.github.2ysp 24 | ship-rpc-spring-boot-starter 25 | 1.0.4-RELEASE 26 | 27 | ``` 28 | ## 2.2 客户端 29 | 30 | 31 | 32 | **1. 普通RPC调用** 33 | 34 | 引入接口依赖,使用@InjectService注解注入远程方法 35 | ```java 36 | @RestController 37 | @RequestMapping("test") 38 | public class TestController { 39 | 40 | @InjectService 41 | private UserService userService; 42 | 43 | @GetMapping("/user") 44 | public ApiResult getUser(@RequestParam("id")Long id){ 45 | return userService.getUser(id); 46 | } 47 | } 48 | ``` 49 | 50 | **2. 泛化调用** 51 | ```java 52 | @RestController 53 | @RequestMapping("/GenericTest") 54 | public class GenericTestController { 55 | 56 | 57 | @GetMapping("/user") 58 | public String getUserString(@RequestParam("id") Long id) { 59 | //cn.sp.UserService.getUserString 60 | GenericService instance = GenericServiceFactory.getInstance("cn.sp.UserService"); 61 | Object result = instance.$invoke("getUserString", new String[]{"java.lang.Long"}, new Object[]{id}); 62 | return result.toString(); 63 | } 64 | 65 | 66 | @GetMapping("") 67 | public String getUser(@RequestParam("id") Long id) { 68 | //cn.sp.UserService.getUserString 69 | GenericService instance = GenericServiceFactory.getInstance("cn.sp.UserService"); 70 | Object result = instance.$invoke("getUser", new String[]{"java.lang.Long"}, new Object[]{id}); 71 | return result.toString(); 72 | } 73 | } 74 | ``` 75 | 76 | 77 | **配置项:** 78 | | 属性 |含义 | 可选项 | 79 | | --- | --- | --- | 80 | | sp.rpc.protocol | 消息序列化协议 | java,protobuf,kryo,hessian | 81 | | sp.rpc.register-address | 注册中心地址 | 默认localhost:2181 | 82 | | sp.rpc.register-center-type | 注册中心类型,默认nacos | nacos
zk| 83 | | sp.rpc.load-balance | 负载均衡算法 | random
round
weightRound
smoothWeightRound| 84 | | sp.rpc.proxy-type | 客户端代理对象生成方式,默认JDK动态代理 | jdk
javassist
compiler| 85 | 86 | 建议使用Javassist字节码或Java动态编译的方式,性能更好。 87 | ## 2.3 服务端 88 | 提供远程方法并注入IOC 89 | 90 | ```java 91 | @Service 92 | public class UserServiceImpl implements UserService{ 93 | 94 | private static Logger logger = LoggerFactory.getLogger(UserService.class); 95 | 96 | 97 | @Override 98 | public ApiResult getUser(Long id) { 99 | logger.info("现在是【3】号提供服务"); 100 | User user = new User(1L,"XX",2,"www.aa.com"); 101 | return ApiResult.success(user); 102 | } 103 | 104 | @Override 105 | public String getUserString(Long id) { 106 | logger.info("getUserString"); 107 | User user = new User(1L,"XX",2,"www.aa.com"); 108 | return JSON.toJSONString(ApiResult.success(user)); 109 | } 110 | } 111 | ``` 112 | **注意:** 这里的@Service注解不是Spring的,而是com.github.ship.annotation.Service。 113 | 114 | **配置项:** 115 | | 属性 |含义 | 可选项 | 116 | | --- | --- | --- | 117 | | sp.rpc.protocol | 消息序列化协议 | java,protobuf,kryo | 118 | | sp.rpc.register-address | 注册中心地址 | 默认localhost:2181 | 119 | | sp.rpc.register-center-type | 注册中心类型,默认nacos | nacos
zk| 120 | | sp.rpc.server-port | 服务端通信端口号 | 默认9999| 121 | | sp.rpc.weight | 权重 |默认1 | 122 | 123 | **启动顺序:** 注册中心——> 服务端 ——> 客户端 124 | 125 | **用法示例:** https://github.com/2YSP/rpc-example 126 | 127 | **文章:** https://www.cnblogs.com/2YSP/p/13545217.html 128 | 129 | # 三、TODO List 130 | - 执行链动态Filter 131 | - 支持链路追踪 132 | - RPC上下文传递等 133 | 134 | 135 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.3.2.RELEASE 9 | 10 | 11 | io.github.2ysp 12 | ship-rpc-spring-boot-starter 13 | 1.0.4-RELEASE 14 | 15 | ship-rpc-spring-boot-starter 16 | spring-boot-starter for RPC 17 | https://github.com/2YSP/rpc-spring-boot-starter 18 | 19 | 20 | 21 | 22 | The Apache Software License, Version 2.0 23 | http://www.apache.org/licenses/LICENSE-2.0.txt 24 | repo 25 | 26 | 27 | 28 | https://github.com/2YSP/rpc-spring-boot-starter.git 29 | https://github.com/2YSP/rpc-spring-boot-starter 30 | 31 | 32 | 33 | ship 34 | stylishman525@gmail.com 35 | 36 | Developer 37 | 38 | +8 39 | 40 | 41 | 42 | 43 | 44 | ossrh 45 | https://s01.oss.sonatype.org/content/repositories/snapshots 46 | 47 | 48 | ossrh 49 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 50 | 51 | 52 | 53 | 54 | 55 | 1.8 56 | 1.0.7 57 | 4.0.2 58 | 1.4.4 59 | 4.0.4 60 | 3.25.0-GA 61 | 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-configuration-processor 67 | true 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-autoconfigure 73 | 74 | 75 | 76 | 77 | com.101tec 78 | zkclient 79 | 0.10 80 | 81 | 82 | 83 | com.alibaba 84 | fastjson 85 | 1.2.56 86 | 87 | 88 | 89 | io.netty 90 | netty-all 91 | 92 | 93 | 94 | org.slf4j 95 | slf4j-api 96 | 2.0.0-alpha1 97 | 98 | 99 | 100 | 101 | com.dyuproject.protostuff 102 | protostuff-core 103 | ${protostuff.version} 104 | 105 | 106 | 107 | com.dyuproject.protostuff 108 | protostuff-runtime 109 | ${protostuff.version} 110 | 111 | 112 | 113 | com.esotericsoftware 114 | kryo 115 | ${kryo.version} 116 | 117 | 118 | 119 | 120 | com.google.guava 121 | guava 122 | 29.0-jre 123 | 124 | 125 | 126 | 127 | com.alipay.sofa 128 | hessian 129 | ${hessian.version} 130 | 131 | 132 | 133 | com.alibaba.nacos 134 | nacos-client 135 | ${nacos-client.version} 136 | 137 | 138 | 139 | org.javassist 140 | javassist 141 | ${javassist.version} 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | org.springframework.boot 151 | spring-boot-dependencies 152 | ${spring-boot.version} 153 | pom 154 | import 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | release 163 | 164 | 165 | 166 | 167 | org.apache.maven.plugins 168 | maven-source-plugin 169 | 2.2.1 170 | 171 | 172 | package 173 | 174 | jar-no-fork 175 | 176 | 177 | 178 | 179 | 180 | 181 | org.apache.maven.plugins 182 | maven-javadoc-plugin 183 | 2.9.1 184 | 185 | private 186 | true 187 | UTF-8 188 | UTF-8 189 | UTF-8 190 | -Xdoclint:none 191 | 192 | 193 | 194 | 195 | package 196 | 197 | jar 198 | 199 | 200 | 201 | 202 | 203 | 204 | org.apache.maven.plugins 205 | maven-gpg-plugin 206 | 1.6 207 | 208 | 209 | verify 210 | 211 | sign 212 | 213 | 214 | 215 | 216 | 217 | 218 | org.apache.maven.plugins 219 | maven-compiler-plugin 220 | 3.0 221 | 222 | 1.8 223 | 1.8 224 | true 225 | true 226 | UTF-8 227 | 228 | 229 | 230 | 231 | org.apache.maven.plugins 232 | maven-release-plugin 233 | 2.5.1 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | mvn clean install deploy -P release -------------------------------------------------------------------------------- /src/main/java/com/github/ship/annotation/InjectService.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * 该注解用于注入远程服务 11 | * @author 2YSP 12 | * @date 2020/7/26 13:13 13 | */ 14 | @Target(ElementType.FIELD) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | public @interface InjectService { 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/annotation/LoadBalanceAno.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 负载均衡注解 7 | */ 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Documented 11 | public @interface LoadBalanceAno { 12 | 13 | String value() default ""; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/annotation/MessageProtocolAno.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author Ship 7 | * @date 2020/8/19 16:33 8 | */ 9 | @Target(ElementType.TYPE) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface MessageProtocolAno { 13 | 14 | String value() default ""; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/annotation/Service.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.annotation; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * 被该注解标记的服务可提供远程RPC访问功能 13 | * @author 2YSP 14 | * @date 2020/7/26 13:11 15 | */ 16 | @Target(ElementType.TYPE) 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Component 19 | @Documented 20 | public @interface Service { 21 | 22 | String value() default ""; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/cache/ServerDiscoveryCache.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.cache; 2 | 3 | import com.github.ship.common.model.Service; 4 | import com.google.common.cache.Cache; 5 | import com.google.common.cache.CacheBuilder; 6 | import org.springframework.util.CollectionUtils; 7 | 8 | import java.util.List; 9 | import java.util.concurrent.Callable; 10 | import java.util.concurrent.ExecutionException; 11 | 12 | /** 13 | * 服务发现本地缓存 14 | */ 15 | public class ServerDiscoveryCache { 16 | /** 17 | * 服务实例缓存 18 | */ 19 | private static final Cache> SERVICE_CACHE = CacheBuilder.newBuilder() 20 | .concurrencyLevel(4) 21 | .initialCapacity(32) 22 | .maximumSize(2048).build(); 23 | 24 | 25 | /** 26 | * put 27 | * @param serviceName 28 | * @param serviceList 29 | */ 30 | public static void put(String serviceName, List serviceList) { 31 | SERVICE_CACHE.put(serviceName, serviceList); 32 | } 33 | 34 | /** 35 | * 清空缓存 36 | * 37 | * @param serviceName 38 | */ 39 | public static void removeAll(String serviceName) { 40 | SERVICE_CACHE.invalidate(serviceName); 41 | } 42 | 43 | /** 44 | * 判断缓存是否存在 45 | * 46 | * @param serviceName 47 | * @return 48 | */ 49 | public static boolean isEmpty(String serviceName) { 50 | return CollectionUtils.isEmpty(SERVICE_CACHE.getIfPresent(serviceName)); 51 | } 52 | 53 | /** 54 | * 获取缓存服务实例 55 | * 56 | * @param serviceName 57 | * @return 58 | */ 59 | public static List get(String serviceName) { 60 | return SERVICE_CACHE.getIfPresent(serviceName); 61 | } 62 | 63 | /** 64 | * 65 | * @param serviceName 66 | * @param callable 67 | * @return 68 | * @throws ExecutionException 69 | */ 70 | public static List get(String serviceName, Callable> callable) throws ExecutionException { 71 | return SERVICE_CACHE.get(serviceName, callable); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/core/DefaultMethodInvoker.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.core; 2 | 3 | import com.github.ship.client.manager.MessageProtocolsManager; 4 | import com.github.ship.client.manager.ServerDiscoveryManager; 5 | import com.github.ship.client.net.NetClient; 6 | import com.github.ship.common.constants.RpcStatusEnum; 7 | import com.github.ship.common.model.RpcRequest; 8 | import com.github.ship.common.model.RpcResponse; 9 | import com.github.ship.common.model.Service; 10 | import com.github.ship.common.exception.RpcException; 11 | import com.github.ship.spi.balance.LoadBalance; 12 | import com.github.ship.spi.protocol.MessageProtocol; 13 | 14 | import java.util.List; 15 | import java.util.UUID; 16 | 17 | /** 18 | * @Author: Ship 19 | * @Description: 20 | * @Date: Created in 2023/6/15 21 | */ 22 | public class DefaultMethodInvoker implements MethodInvoker { 23 | 24 | private ServerDiscoveryManager serverDiscoveryManager; 25 | 26 | private NetClient netClient; 27 | 28 | private LoadBalance loadBalance; 29 | 30 | public DefaultMethodInvoker(ServerDiscoveryManager serverDiscoveryManager, NetClient netClient, LoadBalance loadBalance) { 31 | this.serverDiscoveryManager = serverDiscoveryManager; 32 | this.netClient = netClient; 33 | this.loadBalance = loadBalance; 34 | } 35 | 36 | @Override 37 | public Object $invoke(String interfaceClassName, String methodName, String[] parameterTypeNames, Object[] args, Boolean generic) { 38 | // 1.获得服务信息 39 | String serviceName = interfaceClassName; 40 | List services = serverDiscoveryManager.getServiceList(serviceName); 41 | Service service = loadBalance.chooseOne(services); 42 | // 2.构造request对象 43 | RpcRequest request = new RpcRequest(); 44 | request.setRequestId(UUID.randomUUID().toString()); 45 | request.setServiceName(service.getName()); 46 | request.setMethod(methodName); 47 | request.setParameters(args); 48 | request.setParameterTypeNames(parameterTypeNames); 49 | request.setGeneric(generic); 50 | // 3.协议层编组 51 | MessageProtocol messageProtocol = MessageProtocolsManager.get(service.getProtocol()); 52 | RpcResponse response = netClient.sendRequest(request, service, messageProtocol); 53 | if (response == null) { 54 | throw new RpcException("the response is null"); 55 | } 56 | // 6.结果处理 57 | if (RpcStatusEnum.ERROR.getCode().equals(response.getRpcStatus())) { 58 | throw response.getException(); 59 | } 60 | if (RpcStatusEnum.NOT_FOUND.getCode().equals(response.getRpcStatus())) { 61 | throw new RpcException(" service not found!"); 62 | } 63 | return response.getReturnValue(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/core/MethodInvoker.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.core; 2 | 3 | /** 4 | * @Author: Ship 5 | * @Description: 6 | * @Date: Created in 2023/6/15 7 | */ 8 | public interface MethodInvoker { 9 | 10 | /** 11 | * @param interfaceClassName 12 | * @param methodName 13 | * @param parameterTypeNames 14 | * @param args 15 | * @param generic 16 | * @return 17 | */ 18 | Object $invoke(String interfaceClassName, String methodName, String[] parameterTypeNames, Object[] args, Boolean generic); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/generic/DefaultGenericService.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.generic; 2 | 3 | import com.github.ship.client.core.MethodInvoker; 4 | 5 | 6 | /** 7 | * @Author: Ship 8 | * @Description: 9 | * @Date: Created in 2023/6/15 10 | */ 11 | public class DefaultGenericService implements GenericService { 12 | 13 | private MethodInvoker methodInvoker; 14 | 15 | private String interfaceClassName; 16 | 17 | public DefaultGenericService(MethodInvoker methodInvoker, String interfaceClassName) { 18 | this.methodInvoker = methodInvoker; 19 | this.interfaceClassName = interfaceClassName; 20 | } 21 | 22 | 23 | @Override 24 | public Object $invoke(String methodName, String[] parameterTypeNames, Object[] args) { 25 | return methodInvoker.$invoke(interfaceClassName, methodName, parameterTypeNames, args, true); 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/generic/GenericService.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.generic; 2 | 3 | 4 | /** 5 | * @author Ship 6 | * @version 1.0.0 7 | * @description: 8 | * @date 2023/06/15 14:38 9 | */ 10 | public interface GenericService { 11 | 12 | /** 13 | * 泛化调用 14 | * @param methodName 15 | * @param parameterTypeNames 16 | * @param args 17 | * @return 18 | */ 19 | Object $invoke(String methodName, String[] parameterTypeNames, Object[] args); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/generic/GenericServiceFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.generic; 2 | 3 | import com.github.ship.client.core.MethodInvoker; 4 | import com.github.ship.util.SpringContextHolder; 5 | 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | /** 10 | * @Author: Ship 11 | * @Description: 12 | * @Date: Created in 2023/6/15 13 | */ 14 | public final class GenericServiceFactory { 15 | 16 | /** 17 | * 实例缓存,key:接口类名 18 | */ 19 | private static final Map INSTANCE_MAP = new ConcurrentHashMap<>(); 20 | 21 | private GenericServiceFactory() {} 22 | 23 | /** 24 | * @param interfaceClassName 25 | * @return 26 | */ 27 | public static GenericService getInstance(String interfaceClassName) { 28 | return INSTANCE_MAP.computeIfAbsent(interfaceClassName, clz -> { 29 | MethodInvoker methodInvoker = SpringContextHolder.getBean(MethodInvoker.class); 30 | DefaultGenericService genericService = new DefaultGenericService(methodInvoker, interfaceClassName); 31 | return genericService; 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/manager/LoadBalanceManager.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.manager; 2 | 3 | import com.github.ship.annotation.LoadBalanceAno; 4 | import com.github.ship.common.exception.RpcException; 5 | import com.github.ship.spi.balance.LoadBalance; 6 | import org.springframework.util.Assert; 7 | 8 | import java.util.*; 9 | 10 | /** 11 | * @Author: Ship 12 | * @Description: 13 | * @Date: Created in 2023/6/15 14 | */ 15 | public class LoadBalanceManager { 16 | 17 | private static final Map LOAD_BALANCE_MAP = new HashMap<>(); 18 | 19 | static { 20 | ServiceLoader loader = ServiceLoader.load(LoadBalance.class); 21 | Iterator iterator = loader.iterator(); 22 | while (iterator.hasNext()) { 23 | LoadBalance loadBalance = iterator.next(); 24 | LoadBalanceAno ano = loadBalance.getClass().getAnnotation(LoadBalanceAno.class); 25 | Assert.notNull(ano, "load balance name can not be empty!"); 26 | LOAD_BALANCE_MAP.put(ano.value(), loadBalance); 27 | } 28 | } 29 | 30 | /** 31 | * 使用spi匹配符合配置的负载均衡算法 32 | * @param name 33 | * @return 34 | */ 35 | public static LoadBalance getLoadBalance(String name) { 36 | Optional optional = Optional.of(LOAD_BALANCE_MAP.get(name)); 37 | if (optional.isPresent()) { 38 | return optional.get(); 39 | } 40 | throw new RpcException("invalid load balance config"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/manager/MessageProtocolsManager.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.manager; 2 | 3 | import com.github.ship.annotation.MessageProtocolAno; 4 | import com.github.ship.spi.protocol.MessageProtocol; 5 | import org.springframework.util.Assert; 6 | 7 | import java.util.HashMap; 8 | import java.util.Iterator; 9 | import java.util.Map; 10 | import java.util.ServiceLoader; 11 | 12 | /** 13 | * @Author: Ship 14 | * @Description: 15 | * @Date: Created in 2023/6/15 16 | */ 17 | public class MessageProtocolsManager { 18 | 19 | private static final Map SUPPORT_MESSAGE_PROTOCOL_MAP = new HashMap<>(); 20 | 21 | static { 22 | ServiceLoader loader = ServiceLoader.load(MessageProtocol.class); 23 | Iterator iterator = loader.iterator(); 24 | while (iterator.hasNext()) { 25 | MessageProtocol messageProtocol = iterator.next(); 26 | MessageProtocolAno ano = messageProtocol.getClass().getAnnotation(MessageProtocolAno.class); 27 | Assert.notNull(ano, "message protocol name can not be empty!"); 28 | SUPPORT_MESSAGE_PROTOCOL_MAP.put(ano.value(), messageProtocol); 29 | } 30 | } 31 | 32 | /** 33 | * 获取消息协议实现 34 | * @param protocolName 35 | * @return 36 | */ 37 | public static MessageProtocol get(String protocolName) { 38 | return SUPPORT_MESSAGE_PROTOCOL_MAP.get(protocolName); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/manager/ServerDiscoveryManager.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.manager; 2 | 3 | import com.alibaba.nacos.common.utils.ConcurrentHashSet; 4 | import com.github.ship.client.cache.ServerDiscoveryCache; 5 | import com.github.ship.common.exception.RpcException; 6 | import com.github.ship.common.model.Service; 7 | import com.github.ship.discovery.ServerDiscovery; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.List; 12 | import java.util.Set; 13 | import java.util.concurrent.ExecutionException; 14 | 15 | /** 16 | * @Author: Ship 17 | * @Description: 18 | * @Date: Created in 2023/6/15 19 | */ 20 | public class ServerDiscoveryManager { 21 | 22 | private static Logger logger = LoggerFactory.getLogger(ServerDiscoveryManager.class); 23 | 24 | private ServerDiscovery serverDiscovery; 25 | /** 26 | * 已经注册的远程服务service监听器集合 27 | */ 28 | private static volatile Set SERVICE_REGISTERED_LISTENERS = new ConcurrentHashSet<>(); 29 | 30 | public ServerDiscoveryManager(ServerDiscovery serverDiscovery) { 31 | this.serverDiscovery = serverDiscovery; 32 | } 33 | 34 | /** 35 | * 注册监听 36 | */ 37 | public void registerChangeListener(String serviceName) { 38 | synchronized (serviceName.intern()) { 39 | if (SERVICE_REGISTERED_LISTENERS.contains(serviceName)) { 40 | return; 41 | } 42 | serverDiscovery.registerChangeListener(serviceName); 43 | SERVICE_REGISTERED_LISTENERS.add(serviceName); 44 | } 45 | } 46 | 47 | /** 48 | * 根据服务名获取可用的服务地址列表 49 | * 50 | * @param serviceName 51 | * @return 52 | */ 53 | public List getServiceList(String serviceName) { 54 | List serviceList = null; 55 | try { 56 | serviceList = ServerDiscoveryCache.get(serviceName, () -> { 57 | List services = serverDiscovery.findServiceList(serviceName); 58 | if (services == null || services.size() == 0) { 59 | throw new RpcException("No provider available!"); 60 | } 61 | this.registerChangeListener(serviceName); 62 | return services; 63 | }); 64 | } catch (ExecutionException e) { 65 | logger.error("加载服务列表缓存异常", e); 66 | throw new RpcException(e.getMessage()); 67 | } catch (Exception e) { 68 | logger.error("加载服务列表缓存异常", e); 69 | throw new RpcException(e.getMessage()); 70 | } 71 | return serviceList; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/net/NetClient.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.net; 2 | 3 | import com.github.ship.common.model.Service; 4 | import com.github.ship.spi.protocol.MessageProtocol; 5 | import com.github.ship.common.model.RpcRequest; 6 | import com.github.ship.common.model.RpcResponse; 7 | 8 | /** 9 | * 10 | * 网络请求客户端,定义请求规范 11 | * @author 2YSP 12 | * @date 2020/7/25 20:11 13 | * 14 | */ 15 | public interface NetClient { 16 | 17 | /** 18 | * 发送请求 19 | * @param rpcRequest 20 | * @param service 21 | * @param messageProtocol 22 | * @return 23 | */ 24 | RpcResponse sendRequest(RpcRequest rpcRequest, Service service, MessageProtocol messageProtocol); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/net/NetClientFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.net; 2 | 3 | /** 4 | * @Author: Ship 5 | * @Description: 6 | * @Date: Created in 2023/6/15 7 | */ 8 | public class NetClientFactory { 9 | 10 | private NetClientFactory(){ 11 | 12 | } 13 | 14 | public static NetClient getInstance() { 15 | return new NettyNetClient(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/net/NettyNetClient.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.net; 2 | 3 | 4 | import com.github.ship.client.net.handler.SendHandlerV2; 5 | import com.github.ship.common.model.RpcRequest; 6 | import com.github.ship.common.model.RpcResponse; 7 | import com.github.ship.common.model.Service; 8 | import com.github.ship.spi.protocol.MessageProtocol; 9 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 10 | import io.netty.bootstrap.Bootstrap; 11 | import io.netty.channel.*; 12 | import io.netty.channel.nio.NioEventLoopGroup; 13 | import io.netty.channel.socket.SocketChannel; 14 | import io.netty.channel.socket.nio.NioSocketChannel; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.util.Map; 19 | import java.util.concurrent.*; 20 | 21 | 22 | /** 23 | * @author 2YSP 24 | * @date 2020/7/25 20:12 25 | */ 26 | public class NettyNetClient implements NetClient { 27 | 28 | private static Logger logger = LoggerFactory.getLogger(NettyNetClient.class); 29 | 30 | private static ExecutorService threadPool = new ThreadPoolExecutor(4, 10, 200, 31 | TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder() 32 | .setNameFormat("rpcClient-%d") 33 | .build()); 34 | 35 | private EventLoopGroup loopGroup = new NioEventLoopGroup(4); 36 | 37 | /** 38 | * 已连接的服务缓存 39 | * key: 服务地址,格式:ip:port 40 | */ 41 | public static Map connectedServerNodes = new ConcurrentHashMap<>(); 42 | 43 | @Override 44 | public RpcResponse sendRequest(RpcRequest rpcRequest, Service service, MessageProtocol messageProtocol) { 45 | 46 | String address = service.getIp() + ":" + service.getPort(); 47 | synchronized (address.intern()) { 48 | if (connectedServerNodes.containsKey(address)) { 49 | SendHandlerV2 handler = connectedServerNodes.get(address); 50 | logger.info("使用现有的连接"); 51 | return handler.sendRequest(rpcRequest); 52 | } 53 | final SendHandlerV2 handler = new SendHandlerV2(messageProtocol, address); 54 | threadPool.submit(() -> { 55 | // 配置客户端 56 | Bootstrap b = new Bootstrap(); 57 | b.group(loopGroup).channel(NioSocketChannel.class) 58 | .option(ChannelOption.TCP_NODELAY, true) 59 | .handler(new ChannelInitializer() { 60 | @Override 61 | protected void initChannel(SocketChannel socketChannel) throws Exception { 62 | ChannelPipeline pipeline = socketChannel.pipeline(); 63 | pipeline 64 | // .addLast(new FixedLengthFrameDecoder(20)) 65 | .addLast(handler); 66 | } 67 | }); 68 | // 启用客户端连接 69 | ChannelFuture channelFuture = b.connect(service.getIp(), service.getPort()); 70 | channelFuture.addListener(new ChannelFutureListener() { 71 | @Override 72 | public void operationComplete(ChannelFuture channelFuture) throws Exception { 73 | connectedServerNodes.put(address, handler); 74 | } 75 | }); 76 | } 77 | ); 78 | logger.info("使用新的连接。。。"); 79 | return handler.sendRequest(rpcRequest); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/net/RpcFuture.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.net; 2 | 3 | import java.util.concurrent.*; 4 | 5 | /** 6 | * @author 2YSP 7 | * @date 2020/8/19 22:31 8 | */ 9 | public class RpcFuture implements Future { 10 | 11 | private T response; 12 | /** 13 | * 因为请求和响应是一一对应的,所以这里是1 14 | */ 15 | private CountDownLatch countDownLatch = new CountDownLatch(1); 16 | /** 17 | * Future的请求时间,用于计算Future是否超时 18 | */ 19 | private long beginTime = System.currentTimeMillis(); 20 | 21 | @Override 22 | public boolean cancel(boolean mayInterruptIfRunning) { 23 | return false; 24 | } 25 | 26 | @Override 27 | public boolean isCancelled() { 28 | return false; 29 | } 30 | 31 | @Override 32 | public boolean isDone() { 33 | if (response != null) { 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | /** 40 | * 获取响应,直到有结果才返回 41 | * @return 42 | * @throws InterruptedException 43 | * @throws ExecutionException 44 | */ 45 | @Override 46 | public T get() throws InterruptedException, ExecutionException { 47 | countDownLatch.await(); 48 | return response; 49 | } 50 | 51 | @Override 52 | public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 53 | if (countDownLatch.await(timeout,unit)){ 54 | return response; 55 | } 56 | return null; 57 | } 58 | 59 | public void setResponse(T response) { 60 | this.response = response; 61 | countDownLatch.countDown(); 62 | } 63 | 64 | public long getBeginTime() { 65 | return beginTime; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/net/handler/SendHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.net.handler; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import io.netty.util.ReferenceCountUtil; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.concurrent.CountDownLatch; 12 | 13 | /** 14 | * 15 | * 发送处理类,定义Netty入站处理细则 16 | * @author 2YSP 17 | * @date 2020/7/25 20:15 18 | */ 19 | public class SendHandler extends ChannelInboundHandlerAdapter { 20 | 21 | private static Logger logger = LoggerFactory.getLogger(SendHandler.class); 22 | 23 | private CountDownLatch cdl; 24 | 25 | private Object readMsg; 26 | 27 | private byte[] data; 28 | 29 | public SendHandler(byte[] data){ 30 | cdl = new CountDownLatch(1); 31 | this.data = data; 32 | } 33 | 34 | /** 35 | * 连接服务端成功后发送数据 36 | * @param ctx 37 | * @throws Exception 38 | */ 39 | @Override 40 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 41 | logger.debug("Connect to server successfully:{}",ctx); 42 | ByteBuf reqBuf = Unpooled.buffer(data.length); 43 | reqBuf.writeBytes(data); 44 | // ByteBuf reqBuf = Unpooled.copiedBuffer(data); 45 | logger.debug("Client sends message:{}",reqBuf); 46 | ctx.writeAndFlush(reqBuf); 47 | } 48 | 49 | /** 50 | * 读取数据,数据读取完毕释放cd锁 51 | * @param ctx 52 | * @param msg 53 | * @throws Exception 54 | */ 55 | @Override 56 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 57 | logger.debug("Client reads message:{}",msg); 58 | ByteBuf byteBuf = (ByteBuf) msg; 59 | byte[] resp = new byte[byteBuf.readableBytes()]; 60 | byteBuf.readBytes(resp); 61 | // 手动回收 62 | ReferenceCountUtil.release(byteBuf); 63 | readMsg = resp; 64 | cdl.countDown(); 65 | } 66 | 67 | 68 | public Object respData() throws InterruptedException { 69 | cdl.await(); 70 | return readMsg; 71 | } 72 | 73 | @Override 74 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 75 | cause.printStackTrace(); 76 | logger.error("Exception occurred:{}",cause.getMessage()); 77 | ctx.close(); 78 | } 79 | 80 | @Override 81 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 82 | ctx.flush(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/net/handler/SendHandlerV2.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.net.handler; 2 | 3 | import com.github.ship.client.net.NettyNetClient; 4 | import com.github.ship.client.net.RpcFuture; 5 | import com.github.ship.common.constants.RpcStatusEnum; 6 | import com.github.ship.common.model.RpcRequest; 7 | import com.github.ship.common.model.RpcResponse; 8 | import com.github.ship.spi.protocol.MessageProtocol; 9 | import com.github.ship.common.exception.RpcException; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.buffer.Unpooled; 12 | import io.netty.channel.Channel; 13 | import io.netty.channel.ChannelHandlerContext; 14 | import io.netty.channel.ChannelInboundHandlerAdapter; 15 | import io.netty.util.ReferenceCountUtil; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import java.util.Map; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | import java.util.concurrent.CountDownLatch; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.concurrent.TimeoutException; 24 | 25 | /** 26 | * @author 2YSP 27 | * @date 2020/8/19 20:06 28 | */ 29 | public class SendHandlerV2 extends ChannelInboundHandlerAdapter { 30 | 31 | private static Logger logger = LoggerFactory.getLogger(SendHandlerV2.class); 32 | 33 | /** 34 | * 等待通道建立最大时间 35 | */ 36 | static final int CHANNEL_WAIT_TIME = 4; 37 | /** 38 | * 等待响应最大时间 39 | */ 40 | static final int RESPONSE_WAIT_TIME = 8; 41 | 42 | private volatile Channel channel; 43 | 44 | private String remoteAddress; 45 | 46 | private static Map> requestMap = new ConcurrentHashMap<>(); 47 | 48 | private MessageProtocol messageProtocol; 49 | 50 | private CountDownLatch latch = new CountDownLatch(1); 51 | 52 | public SendHandlerV2(MessageProtocol messageProtocol, String remoteAddress) { 53 | this.messageProtocol = messageProtocol; 54 | this.remoteAddress = remoteAddress; 55 | } 56 | 57 | @Override 58 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 59 | this.channel = ctx.channel(); 60 | } 61 | 62 | @Override 63 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 64 | logger.debug("Connect to server successfully:{}", ctx); 65 | latch.countDown(); 66 | } 67 | 68 | @Override 69 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 70 | logger.debug("Client reads message:{}", msg); 71 | ByteBuf byteBuf = (ByteBuf) msg; 72 | byte[] resp = new byte[byteBuf.readableBytes()]; 73 | byteBuf.readBytes(resp); 74 | // 手动回收 75 | ReferenceCountUtil.release(byteBuf); 76 | RpcResponse response = messageProtocol.unmarshallingResponse(resp); 77 | RpcFuture future = requestMap.get(response.getRequestId()); 78 | if (future == null) { 79 | logger.error("the future is null,maybe because request {} is timeout", response.getRequestId()); 80 | return; 81 | } 82 | future.setResponse(response); 83 | } 84 | 85 | @Override 86 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 87 | cause.printStackTrace(); 88 | logger.error("Exception occurred:{}", cause.getMessage()); 89 | ctx.close(); 90 | } 91 | 92 | @Override 93 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 94 | ctx.flush(); 95 | } 96 | 97 | @Override 98 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 99 | super.channelInactive(ctx); 100 | logger.error("channel inactive with remoteAddress:[{}]", remoteAddress); 101 | NettyNetClient.connectedServerNodes.remove(remoteAddress); 102 | 103 | } 104 | 105 | @Override 106 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 107 | super.userEventTriggered(ctx, evt); 108 | } 109 | 110 | public RpcResponse sendRequest(RpcRequest request) { 111 | RpcResponse response; 112 | RpcFuture future = new RpcFuture<>(); 113 | requestMap.put(request.getRequestId(), future); 114 | try { 115 | byte[] data = messageProtocol.marshallingRequest(request); 116 | ByteBuf reqBuf = Unpooled.buffer(data.length); 117 | reqBuf.writeBytes(data); 118 | if (latch.await(CHANNEL_WAIT_TIME, TimeUnit.SECONDS)) { 119 | channel.writeAndFlush(reqBuf); 120 | // 等待响应 121 | response = future.get(RESPONSE_WAIT_TIME, TimeUnit.SECONDS); 122 | } else { 123 | throw new RpcException("establish channel time out"); 124 | } 125 | } catch (TimeoutException timeoutException) { 126 | return new RpcResponse(RpcStatusEnum.REQUEST_TIME_OUT, request.getRequestId()); 127 | } catch (Exception e) { 128 | throw new RpcException(e.getMessage()); 129 | } finally { 130 | requestMap.remove(request.getRequestId()); 131 | } 132 | return response; 133 | } 134 | 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/proxy/AbstractClientProxyFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.proxy; 2 | 3 | import com.github.ship.client.core.MethodInvoker; 4 | import com.github.ship.util.ReflectUtils; 5 | import javassist.*; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.lang.reflect.*; 10 | import java.lang.reflect.Modifier; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | 15 | /** 16 | * 客户端代理工厂:用于创建远程服务代理类 17 | * 封装编组请求、请求发送、编组响应等操作 18 | * 19 | * @author 2YSP 20 | * @date 2020/7/25 20:55 21 | */ 22 | public abstract class AbstractClientProxyFactory implements ClientProxyFactory { 23 | 24 | private Map, Object> objectCache = new ConcurrentHashMap<>(); 25 | 26 | protected MethodInvoker methodInvoker; 27 | 28 | 29 | public AbstractClientProxyFactory(MethodInvoker methodInvoker) { 30 | this.methodInvoker = methodInvoker; 31 | } 32 | 33 | /** 34 | * 获取客户端服务代理对象 35 | * 36 | * @param clazz 37 | * @param 38 | * @return 39 | */ 40 | @Override 41 | public T getProxy(Class clazz) { 42 | return (T) objectCache.computeIfAbsent(clazz, clz -> this.newProxyInstance(clz)); 43 | } 44 | 45 | /** 46 | * 模板方法子类实现 47 | * @param clazz 48 | * @return 49 | */ 50 | protected Object newProxyInstance(Class clazz) { 51 | throw new UnsupportedOperationException("must have impl method"); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/proxy/ClientProxyFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.proxy; 2 | 3 | /** 4 | * @Author: Ship 5 | * @Description: 6 | * @Date: Created in 2023/7/10 7 | */ 8 | public interface ClientProxyFactory { 9 | 10 | /** 11 | * 获取客户端服务代理对象 12 | * @param clazz 13 | * @param 14 | * @return 15 | */ 16 | T getProxy(Class clazz); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/proxy/impl/JavassistClientProxyFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.proxy.impl; 2 | 3 | import com.github.ship.client.core.MethodInvoker; 4 | import com.github.ship.client.proxy.AbstractClientProxyFactory; 5 | import com.github.ship.util.CodeGenerateUtils; 6 | import javassist.*; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.lang.reflect.Constructor; 11 | import java.lang.reflect.Method; 12 | import java.lang.reflect.Modifier; 13 | 14 | /** 15 | * @Author: Ship 16 | * @Description: Javassist字节码创建代理对象 17 | * @Date: Created in 2023/7/10 18 | */ 19 | public class JavassistClientProxyFactory extends AbstractClientProxyFactory { 20 | 21 | private static Logger logger = LoggerFactory.getLogger(JavassistClientProxyFactory.class); 22 | 23 | private static final String METHOD_INVOKER_CLASS_PATH = "com.github.ship.client.core.MethodInvoker"; 24 | 25 | public JavassistClientProxyFactory(MethodInvoker methodInvoker) { 26 | super(methodInvoker); 27 | } 28 | 29 | @Override 30 | protected Object newProxyInstance(Class clazz) { 31 | return this.generateClassInstance(clazz); 32 | } 33 | 34 | private Object generateClassInstance(Class clazz) { 35 | ClassPool pool = ClassPool.getDefault(); 36 | pool.insertClassPath(new LoaderClassPath(clazz.getClassLoader())); 37 | // 创建实现类 38 | String implClassName = clazz.getName() + "Impl"; 39 | CtClass cc = pool.makeClass(implClassName); 40 | try { 41 | // 添加接口 42 | cc.addInterface(toCtClass(clazz, pool)); 43 | 44 | // 2. 新增一个字段 private MethodInvoker methodInvoker; 45 | // 字段名为 methodInvoker 46 | CtField param = new CtField(pool.get(METHOD_INVOKER_CLASS_PATH), "methodInvoker", cc); 47 | // 访问级别是 private 48 | param.setModifiers(Modifier.PRIVATE); 49 | cc.addField(param); 50 | 51 | // 添加构造方法 52 | CtConstructor constructor = new CtConstructor(new CtClass[]{pool.get(METHOD_INVOKER_CLASS_PATH)}, cc); 53 | constructor.setBody("{$0.methodInvoker = $1;}"); 54 | cc.addConstructor(constructor); 55 | 56 | Method[] declaredMethods = clazz.getDeclaredMethods(); 57 | for (Method method : declaredMethods) { 58 | if (!Modifier.isPublic(method.getModifiers())) { 59 | continue; 60 | } 61 | // 创建方法 62 | CtClass[] parameters = new CtClass[method.getParameterTypes().length]; 63 | for (int i = 0; i < method.getParameterTypes().length; i++) { 64 | parameters[i] = toCtClass(method.getParameterTypes()[i], pool); 65 | } 66 | CtMethod ctMethod = new CtMethod(toCtClass(method.getReturnType(), pool) 67 | , method.getName(), parameters, cc); 68 | ctMethod.setModifiers(Modifier.PUBLIC); 69 | ctMethod.setBody(CodeGenerateUtils.genJavassistMethodBody(clazz, method)); 70 | cc.addMethod(ctMethod); 71 | } 72 | // 创建实例 73 | Constructor con = cc.toClass().getConstructor(MethodInvoker.class); 74 | Object instance = con.newInstance(methodInvoker); 75 | return instance; 76 | } catch (Exception e) { 77 | logger.error("generateClassInstance error", e); 78 | } 79 | return null; 80 | } 81 | 82 | 83 | private static CtClass toCtClass(Class clazz, ClassPool pool) throws NotFoundException { 84 | return pool.get(clazz.getName()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/proxy/impl/JdkClientProxyFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.proxy.impl; 2 | 3 | import com.github.ship.client.core.MethodInvoker; 4 | import com.github.ship.client.proxy.AbstractClientProxyFactory; 5 | import com.github.ship.util.ReflectUtils; 6 | 7 | import java.lang.reflect.InvocationHandler; 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Proxy; 10 | 11 | /** 12 | * @Author: Ship 13 | * @Description: JDK动态代理实现 14 | * @Date: Created in 2023/7/10 15 | */ 16 | public class JdkClientProxyFactory extends AbstractClientProxyFactory { 17 | 18 | 19 | public JdkClientProxyFactory(MethodInvoker methodInvoker) { 20 | super(methodInvoker); 21 | } 22 | 23 | @Override 24 | protected Object newProxyInstance(Class clazz) { 25 | return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new ClientInvocationHandler(clazz)); 26 | } 27 | 28 | private class ClientInvocationHandler implements InvocationHandler { 29 | 30 | private Class clazz; 31 | 32 | public ClientInvocationHandler(Class clazz) { 33 | this.clazz = clazz; 34 | } 35 | 36 | 37 | @Override 38 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 39 | if (method.getName().equals("toString")) { 40 | return proxy.toString(); 41 | } 42 | if (method.getName().equals("hashCode")) { 43 | return 0; 44 | } 45 | return methodInvoker.$invoke(clazz.getName(), method.getName(), ReflectUtils.getParameterTypeNames(method), args, false); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/client/proxy/impl/JdkCompilerClientProxyFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.client.proxy.impl; 2 | 3 | import com.github.ship.client.core.MethodInvoker; 4 | import com.github.ship.client.proxy.AbstractClientProxyFactory; 5 | import com.github.ship.common.exception.RpcException; 6 | import com.github.ship.util.ClassUtils; 7 | import com.github.ship.util.CodeGenerateUtils; 8 | import com.github.ship.util.ReflectUtils; 9 | import com.google.common.collect.Lists; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import javax.tools.*; 14 | import java.io.*; 15 | import java.lang.reflect.Constructor; 16 | import java.lang.reflect.Method; 17 | import java.lang.reflect.Parameter; 18 | import java.net.URI; 19 | import java.net.URL; 20 | import java.security.AccessController; 21 | import java.security.PrivilegedAction; 22 | import java.util.*; 23 | 24 | /** 25 | * @author Ship 26 | * @version 1.0.0 27 | * @description: 28 | * @date 2023/08/17 13:53 29 | */ 30 | public class JdkCompilerClientProxyFactory extends AbstractClientProxyFactory { 31 | 32 | private static Logger logger = LoggerFactory.getLogger(JdkCompilerClientProxyFactory.class); 33 | 34 | private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 35 | 36 | private final DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); 37 | 38 | private final JavaFileManagerImpl javaFileManager; 39 | 40 | private final ClassLoaderImpl classLoader; 41 | 42 | private List options; 43 | 44 | public JdkCompilerClientProxyFactory(MethodInvoker methodInvoker) { 45 | super(methodInvoker); 46 | options = new ArrayList(); 47 | options.add("-source"); 48 | options.add("1.8"); 49 | options.add("-target"); 50 | options.add("1.8"); 51 | StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, null, null); 52 | final ClassLoader loader = Thread.currentThread().getContextClassLoader(); 53 | classLoader = AccessController.doPrivileged(new PrivilegedAction() { 54 | @Override 55 | public ClassLoaderImpl run() { 56 | return new ClassLoaderImpl(loader); 57 | } 58 | }); 59 | javaFileManager = new JavaFileManagerImpl(fileManager, classLoader); 60 | } 61 | 62 | 63 | @Override 64 | protected Object newProxyInstance(Class clazz) { 65 | // 实现类类名 66 | String fullClassName = clazz.getName() + "Impl"; 67 | int i = fullClassName.lastIndexOf('.'); 68 | // eg: UserServiceImpl 69 | String className = fullClassName.substring(i + 1); 70 | String packageName = fullClassName.substring(0, i); 71 | String sourceCode = generateSourceCode(className, packageName, clazz); 72 | JavaFileObjectImpl javaFileObject = new JavaFileObjectImpl(className, sourceCode); 73 | javaFileManager.putFileForInput(StandardLocation.SOURCE_PATH, packageName, className + ClassUtils.JAVA_EXTENSION, javaFileObject); 74 | JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, diagnosticCollector, options, null, Lists.newArrayList(javaFileObject)); 75 | // 编译代码 76 | Boolean result = task.call(); 77 | if (result == null || !result) { 78 | logger.error("Compilation fail."); 79 | List> diagnostics = diagnosticCollector.getDiagnostics(); 80 | for (Diagnostic diagnostic : diagnostics) { 81 | logger.error(diagnostic.getMessage(null)); 82 | } 83 | throw new RpcException("Compilation fail. "); 84 | } 85 | try { 86 | Class implClass = classLoader.loadClass(fullClassName); 87 | Constructor constructor = implClass.getConstructor(MethodInvoker.class); 88 | Object instance = constructor.newInstance(super.methodInvoker); 89 | return instance; 90 | } catch (ClassNotFoundException e) { 91 | throw new RpcException("Class [" + fullClassName + "] not find"); 92 | } catch (Exception e) { 93 | throw new RpcException("New class [" + fullClassName + "] instance fail,errorMsg:" + e.getMessage()); 94 | } 95 | } 96 | 97 | /** 98 | * 生成实现类源代码 99 | * 100 | * @param className 101 | * @param packageName 102 | * @param interfaceClazz 103 | * @return 104 | */ 105 | private static String generateSourceCode(String className, String packageName, Class interfaceClazz) { 106 | /** 107 | * package com.github.ship.client.proxy.impl; 108 | * 109 | * import com.github.ship.client.core.MethodInvoker; 110 | * 111 | * public class UserServiceImpl implements UserService { 112 | * 113 | * private MethodInvoker methodInvoker; 114 | * 115 | * public UserServiceImpl(MethodInvoker methodInvoker) { this.methodInvoker = methodInvoker;} 116 | * 117 | * @Override 118 | * public Object getUser(Long id) { 119 | * return methodInvoker.$invoke(UserService.class.getName(), "getUser", new String[]{"java.lang.Long"}, new Object[]{id}, Boolean.FALSE); 120 | * } 121 | * } 122 | */ 123 | String interfaceClazzName = className.replace("Impl", ""); 124 | 125 | final StringBuilder sb = new StringBuilder(); 126 | sb.append("package " + packageName + ";\n"); 127 | sb.append("import com.github.ship.client.core.MethodInvoker;\n"); 128 | sb.append(String.format("public class %s implements %s {", className, interfaceClazzName)); 129 | sb.append("\n"); 130 | sb.append("private MethodInvoker methodInvoker;"); 131 | sb.append("\n"); 132 | sb.append(String.format("public %s(MethodInvoker methodInvoker) { this.methodInvoker = methodInvoker;}", className)); 133 | sb.append("\n"); 134 | 135 | Method[] declaredMethods = interfaceClazz.getDeclaredMethods(); 136 | for (Method method : declaredMethods) { 137 | fillMethodCode(interfaceClazz, sb, method); 138 | sb.append("\n"); 139 | } 140 | sb.append("}"); 141 | return sb.toString(); 142 | } 143 | 144 | // public static void main(String[] args) { 145 | // String code = generateSourceCode("UserServiceImpl", "com.github.ship.client.proxy.impl", UserService.class); 146 | // System.out.println(code); 147 | // } 148 | 149 | /** 150 | * 填充方法实现代码 151 | * 152 | * @param interfaceClazz 153 | * @param sb 154 | * @param method 155 | */ 156 | private static void fillMethodCode(Class interfaceClazz, StringBuilder sb, Method method) { 157 | sb.append("@Override\n"); 158 | // 方法名 159 | String methodName = method.getName(); 160 | // 返回值类型 161 | Class returnType = method.getReturnType(); 162 | sb.append(String.format("public %s %s(", returnType.getName(), methodName)); 163 | int paramLength = method.getParameterCount(); 164 | if (paramLength == 0) { 165 | throw new RpcException("at last one parameter required"); 166 | } 167 | Parameter[] parameters = method.getParameters(); 168 | String[] parameterTypeNames = ReflectUtils.getParameterTypeNames(method); 169 | for (int i = 0; i < parameterTypeNames.length; i++) { 170 | sb.append(parameterTypeNames[i] + " " + parameters[i].getName()); 171 | if (i != parameterTypeNames.length - 1) { 172 | sb.append(","); 173 | } 174 | } 175 | sb.append(")"); 176 | String methodBody = CodeGenerateUtils.genJavaCompilerMethodBody(interfaceClazz, method); 177 | sb.append(methodBody); 178 | } 179 | 180 | 181 | /** 182 | * 183 | */ 184 | private static final class JavaFileObjectImpl extends SimpleJavaFileObject { 185 | private final CharSequence source; 186 | private ByteArrayOutputStream bytecode; 187 | 188 | public JavaFileObjectImpl(String baseName, final CharSequence source) { 189 | super(ClassUtils.toURI(baseName + ClassUtils.JAVA_EXTENSION), Kind.SOURCE); 190 | this.source = source; 191 | } 192 | 193 | public JavaFileObjectImpl(String name, final Kind kind) { 194 | super(ClassUtils.toURI(name), kind); 195 | this.source = null; 196 | } 197 | 198 | protected JavaFileObjectImpl(URI uri, Kind kind) { 199 | super(uri, kind); 200 | this.source = null; 201 | } 202 | 203 | @Override 204 | public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { 205 | if (source == null) { 206 | throw new UnsupportedOperationException("source == null"); 207 | } 208 | return source; 209 | } 210 | 211 | @Override 212 | public InputStream openInputStream() throws IOException { 213 | return new ByteArrayInputStream(getByteCodes()); 214 | } 215 | 216 | @Override 217 | public OutputStream openOutputStream() throws IOException { 218 | bytecode = new ByteArrayOutputStream(); 219 | return bytecode; 220 | } 221 | 222 | public byte[] getByteCodes() { 223 | return bytecode.toByteArray(); 224 | } 225 | } 226 | 227 | 228 | /** 229 | * 重写ClassLoader 230 | */ 231 | private final class ClassLoaderImpl extends ClassLoader { 232 | private final Map classes = new HashMap<>(); 233 | 234 | public ClassLoaderImpl(ClassLoader parent) { 235 | super(parent); 236 | } 237 | 238 | Collection files() { 239 | return Collections.unmodifiableCollection(classes.values()); 240 | } 241 | 242 | /** 243 | * findClass 方法用于查找指定名称的类。如果类已经被加载过,可以直接返回已加载的类; 244 | * 否则,需要使用动态编译生成类的字节码,并通过 defineClass 方法将其加载到 JVM 中执行。 245 | * 246 | * @param qualifiedClassName 247 | * @return 248 | * @throws ClassNotFoundException 249 | */ 250 | @Override 251 | protected Class findClass(String qualifiedClassName) throws ClassNotFoundException { 252 | JavaFileObject javaFileObject = classes.get(qualifiedClassName); 253 | if (javaFileObject != null) { 254 | byte[] bytes = ((JavaFileObjectImpl) javaFileObject).getByteCodes(); 255 | return defineClass(qualifiedClassName, bytes, 0, bytes.length); 256 | } 257 | return getClass().getClassLoader().loadClass(qualifiedClassName); 258 | } 259 | 260 | void add(final String qualifiedClassName, final JavaFileObject javaFile) { 261 | classes.put(qualifiedClassName, javaFile); 262 | } 263 | 264 | @Override 265 | protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 266 | return super.loadClass(name, resolve); 267 | } 268 | 269 | @Override 270 | public InputStream getResourceAsStream(String name) { 271 | if (name.endsWith(ClassUtils.CLASS_EXTENSION)) { 272 | String qualifiedClassName = name.substring(0, name.length() - ClassUtils.CLASS_EXTENSION.length()).replace('/', '.'); 273 | JavaFileObjectImpl file = (JavaFileObjectImpl) classes.get(qualifiedClassName); 274 | if (file != null) { 275 | return new ByteArrayInputStream(file.getByteCodes()); 276 | } 277 | } 278 | return super.getResourceAsStream(name); 279 | } 280 | } 281 | 282 | 283 | private static final class JavaFileManagerImpl extends ForwardingJavaFileManager { 284 | 285 | private final ClassLoaderImpl classLoader; 286 | 287 | private final Map fileObjectMap = new HashMap<>(); 288 | 289 | public JavaFileManagerImpl(JavaFileManager fileManager, ClassLoaderImpl classLoader) { 290 | super(fileManager); 291 | this.classLoader = classLoader; 292 | } 293 | 294 | @Override 295 | public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { 296 | JavaFileObject javaFileObject = fileObjectMap.get(uri(location, packageName, relativeName)); 297 | if (javaFileObject != null) { 298 | return javaFileObject; 299 | } 300 | return super.getFileForInput(location, packageName, relativeName); 301 | } 302 | 303 | public void putFileForInput(StandardLocation location, String packageName, String relativeName, JavaFileObject javaFileObject) { 304 | fileObjectMap.put(uri(location, packageName, relativeName), javaFileObject); 305 | } 306 | 307 | 308 | @Override 309 | public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { 310 | JavaFileObjectImpl javaFileObject = new JavaFileObjectImpl(className, kind); 311 | classLoader.add(className, javaFileObject); 312 | return javaFileObject; 313 | } 314 | 315 | @Override 316 | public ClassLoader getClassLoader(Location location) { 317 | return classLoader; 318 | } 319 | 320 | @Override 321 | public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException { 322 | Iterable result = super.list(location, packageName, kinds, recurse); 323 | 324 | ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 325 | List urlList = new ArrayList(); 326 | Enumeration e = contextClassLoader.getResources("com"); 327 | while (e.hasMoreElements()) { 328 | urlList.add(e.nextElement()); 329 | } 330 | 331 | ArrayList files = new ArrayList(); 332 | 333 | if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) { 334 | for (JavaFileObject file : fileObjectMap.values()) { 335 | if (file.getKind() == JavaFileObject.Kind.CLASS && file.getName().startsWith(packageName)) { 336 | files.add(file); 337 | } 338 | } 339 | 340 | files.addAll(classLoader.files()); 341 | } else if (location == StandardLocation.SOURCE_PATH && kinds.contains(JavaFileObject.Kind.SOURCE)) { 342 | for (JavaFileObject file : fileObjectMap.values()) { 343 | if (file.getKind() == JavaFileObject.Kind.SOURCE && file.getName().startsWith(packageName)) { 344 | files.add(file); 345 | } 346 | } 347 | } 348 | for (JavaFileObject file : result) { 349 | files.add(file); 350 | } 351 | return files; 352 | } 353 | 354 | @Override 355 | public String inferBinaryName(Location location, JavaFileObject file) { 356 | if (file instanceof JavaFileObjectImpl) { 357 | return file.getName(); 358 | } 359 | return super.inferBinaryName(location, file); 360 | } 361 | 362 | private URI uri(Location location, String packageName, String relativeName) { 363 | return ClassUtils.toURI(location.getName() + '/' + packageName + '/' + relativeName); 364 | } 365 | } 366 | 367 | 368 | } 369 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/common/constants/ProxyTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.common.constants; 2 | 3 | import com.github.ship.client.proxy.impl.JavassistClientProxyFactory; 4 | import com.github.ship.client.proxy.impl.JdkClientProxyFactory; 5 | import com.github.ship.client.proxy.impl.JdkCompilerClientProxyFactory; 6 | import com.github.ship.common.exception.RpcException; 7 | 8 | import java.util.Arrays; 9 | 10 | /** 11 | * @author Ship 12 | * @version 1.0.0 13 | * @description: 14 | * @date 2023/06/16 16:14 15 | */ 16 | public enum ProxyTypeEnum { 17 | /** 18 | * jdk动态代理 19 | */ 20 | JDK("jdk", "jdk动态代理", JdkClientProxyFactory.class), 21 | /** 22 | * javassist字节码生成 23 | */ 24 | JAVASSIST("javassist", "javassist字节码生成", JavassistClientProxyFactory.class), 25 | /** 26 | * java动态编译 27 | */ 28 | COMPILER("compiler", "java动态编译", JdkCompilerClientProxyFactory.class); 29 | 30 | private String code; 31 | 32 | private String desc; 33 | 34 | private Class clientProxyFactoryClass; 35 | 36 | ProxyTypeEnum(String code, String desc, Class clientProxyFactoryClass) { 37 | this.code = code; 38 | this.desc = desc; 39 | this.clientProxyFactoryClass = clientProxyFactoryClass; 40 | } 41 | 42 | public String getCode() { 43 | return code; 44 | } 45 | 46 | public String getDesc() { 47 | return desc; 48 | } 49 | 50 | public Class getClientProxyFactoryClass() { 51 | return clientProxyFactoryClass; 52 | } 53 | 54 | public static ProxyTypeEnum getByCode(String code) { 55 | return Arrays.asList(values()).stream().filter(i -> i.getCode().equals(code)).findFirst() 56 | .orElseThrow(() -> new RpcException("invalid config of proxyType!")); 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/common/constants/RegisterCenterTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.common.constants; 2 | 3 | import com.github.ship.common.exception.RpcException; 4 | import com.github.ship.discovery.nacos.NacosServerDiscovery; 5 | import com.github.ship.discovery.zk.ZookeeperServerDiscovery; 6 | 7 | import java.util.Arrays; 8 | 9 | /** 10 | * @author Ship 11 | * @version 1.0.0 12 | * @description: 13 | * @date 2023/06/16 16:14 14 | */ 15 | public enum RegisterCenterTypeEnum { 16 | 17 | ZOOKEEPER("zk", "zookeeper", ZookeeperServerDiscovery.class), 18 | NACOS("nacos", "nacos", NacosServerDiscovery.class); 19 | 20 | private String code; 21 | 22 | private String desc; 23 | 24 | private Class clazz; 25 | 26 | RegisterCenterTypeEnum(String code, String desc, Class clazz) { 27 | this.code = code; 28 | this.desc = desc; 29 | this.clazz = clazz; 30 | } 31 | 32 | public String getCode() { 33 | return code; 34 | } 35 | 36 | public String getDesc() { 37 | return desc; 38 | } 39 | 40 | public Class getClazz() { 41 | return clazz; 42 | } 43 | 44 | public static RegisterCenterTypeEnum getByCode(String code) { 45 | return Arrays.asList(values()).stream().filter(i -> i.getCode().equals(code)).findFirst() 46 | .orElseThrow(() -> new RpcException("invalid config of registerCenterType")); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/common/constants/RpcConstant.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.common.constants; 2 | 3 | /** 4 | * @author 2YSP 5 | * @date 2020/7/25 20:00 6 | */ 7 | public class RpcConstant { 8 | 9 | private RpcConstant(){} 10 | 11 | /** 12 | * Zookeeper服务注册地址 13 | */ 14 | public static final String ZK_SERVICE_PATH = "/rpc"; 15 | /*** 16 | * 编码 17 | */ 18 | public static final String UTF_8 = "UTF-8"; 19 | /** 20 | * 路径分隔符 21 | */ 22 | public static final String PATH_DELIMITER = "/"; 23 | /** 24 | * java序列化协议 25 | */ 26 | public static final String PROTOCOL_JAVA = "java"; 27 | /** 28 | * protobuf序列化协议 29 | */ 30 | public static final String PROTOCOL_PROTOBUF = "protobuf"; 31 | /** 32 | * kryo序列化协议 33 | */ 34 | public static final String PROTOCOL_KRYO = "kryo"; 35 | /** 36 | * hessian序列化协议 37 | */ 38 | public static final String PROTOCOL_HESSIAN = "hessian"; 39 | /** 40 | * 随机 41 | */ 42 | public static final String BALANCE_RANDOM = "random"; 43 | /** 44 | * 轮询 45 | */ 46 | public static final String BALANCE_ROUND = "round"; 47 | /** 48 | * 加权轮询 49 | */ 50 | public static final String BALANCE_WEIGHT_ROUND = "weightRound"; 51 | /** 52 | * 平滑加权轮询 53 | */ 54 | public static final String BALANCE_SMOOTH_WEIGHT_ROUND = "smoothWeightRound"; 55 | 56 | /** 57 | * nacos group name 58 | */ 59 | public static final String NACOS_APP_GROUP_NAME = "PRC_GROUP"; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/common/constants/RpcStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.common.constants; 2 | 3 | /** 4 | * @author 2YSP 5 | * @date 2020/7/25 21:11 6 | */ 7 | public enum RpcStatusEnum { 8 | /** 9 | * SUCCESS 10 | */ 11 | SUCCESS(200, "SUCCESS"), 12 | /** 13 | * ERROR 14 | */ 15 | ERROR(500, "ERROR"), 16 | /** 17 | * NOT FOUND 18 | */ 19 | NOT_FOUND(404, "NOT FOUND"), 20 | /** 21 | * REQUEST TIME OUT 22 | */ 23 | REQUEST_TIME_OUT(504, "REQUEST TIME OUT"); 24 | 25 | private Integer code; 26 | 27 | private String desc; 28 | 29 | RpcStatusEnum(Integer code, String desc) { 30 | this.code = code; 31 | this.desc = desc; 32 | } 33 | 34 | public Integer getCode() { 35 | return code; 36 | } 37 | 38 | public String getDesc() { 39 | return desc; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/common/exception/RpcException.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.common.exception; 2 | 3 | /** 4 | * @author 2YSP 5 | * @date 2020/7/25 21:34 6 | */ 7 | public class RpcException extends RuntimeException { 8 | 9 | public RpcException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/common/model/RpcRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.common.model; 2 | 3 | import java.io.Serializable; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * @author 2YSP 9 | * @date 2020/7/25 21:02 10 | */ 11 | public class RpcRequest implements Serializable { 12 | 13 | private String requestId; 14 | /** 15 | * 请求的服务名 16 | */ 17 | private String serviceName; 18 | /** 19 | * 请求调用的方法 20 | */ 21 | private String method; 22 | 23 | private Map headers = new HashMap<>(); 24 | 25 | private String[] parameterTypeNames; 26 | 27 | private Object[] parameters; 28 | 29 | private Boolean generic; 30 | 31 | public Boolean getGeneric() { 32 | return generic; 33 | } 34 | 35 | public void setGeneric(Boolean generic) { 36 | this.generic = generic; 37 | } 38 | 39 | public String getRequestId() { 40 | return requestId; 41 | } 42 | 43 | public void setRequestId(String requestId) { 44 | this.requestId = requestId; 45 | } 46 | 47 | public String getServiceName() { 48 | return serviceName; 49 | } 50 | 51 | public void setServiceName(String serviceName) { 52 | this.serviceName = serviceName; 53 | } 54 | 55 | public String getMethod() { 56 | return method; 57 | } 58 | 59 | public void setMethod(String method) { 60 | this.method = method; 61 | } 62 | 63 | public Map getHeaders() { 64 | return headers; 65 | } 66 | 67 | public void setHeaders(Map headers) { 68 | this.headers = headers; 69 | } 70 | 71 | 72 | public Object[] getParameters() { 73 | return parameters; 74 | } 75 | 76 | public void setParameters(Object[] parameters) { 77 | this.parameters = parameters; 78 | } 79 | 80 | 81 | public String[] getParameterTypeNames() { 82 | return parameterTypeNames; 83 | } 84 | 85 | public void setParameterTypeNames(String[] parameterTypeNames) { 86 | this.parameterTypeNames = parameterTypeNames; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/common/model/RpcResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.common.model; 2 | 3 | import com.github.ship.common.constants.RpcStatusEnum; 4 | import com.github.ship.common.exception.RpcException; 5 | 6 | import java.io.Serializable; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * @author 2YSP 12 | * @date 2020/7/25 21:03 13 | */ 14 | public class RpcResponse implements Serializable { 15 | 16 | private String requestId; 17 | 18 | private Map headers = new HashMap<>(); 19 | 20 | private Object returnValue; 21 | 22 | private RpcException exception; 23 | 24 | private Integer rpcStatus; 25 | 26 | public RpcResponse() { 27 | } 28 | 29 | public RpcResponse(RpcStatusEnum rpcStatus) { 30 | this.rpcStatus = rpcStatus.getCode(); 31 | } 32 | 33 | public RpcResponse(RpcStatusEnum rpcStatus, String requestId) { 34 | this.rpcStatus = rpcStatus.getCode(); 35 | this.requestId = requestId; 36 | } 37 | 38 | public String getRequestId() { 39 | return requestId; 40 | } 41 | 42 | public void setRequestId(String requestId) { 43 | this.requestId = requestId; 44 | } 45 | 46 | 47 | public Map getHeaders() { 48 | return headers; 49 | } 50 | 51 | public void setHeaders(Map headers) { 52 | this.headers = headers; 53 | } 54 | 55 | public Object getReturnValue() { 56 | return returnValue; 57 | } 58 | 59 | public void setReturnValue(Object returnValue) { 60 | this.returnValue = returnValue; 61 | } 62 | 63 | public RpcException getException() { 64 | return exception; 65 | } 66 | 67 | public void setException(RpcException exception) { 68 | this.exception = exception; 69 | } 70 | 71 | public Integer getRpcStatus() { 72 | return rpcStatus; 73 | } 74 | 75 | public void setRpcStatus(Integer rpcStatus) { 76 | this.rpcStatus = rpcStatus; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/common/model/Service.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.common.model; 2 | 3 | /** 4 | * @author 2YSP 5 | * @date 2020/7/25 19:46 6 | */ 7 | public class Service { 8 | /** 9 | * 服务名称 10 | */ 11 | private String name; 12 | /** 13 | * 服务协议 14 | */ 15 | private String protocol; 16 | /** 17 | * 服务地址ip 18 | */ 19 | private String ip; 20 | /** 21 | * 服务地址端口号 22 | */ 23 | private Integer port; 24 | 25 | /** 26 | * 权重,越大优先级越高 27 | */ 28 | private Integer weight; 29 | 30 | 31 | public Integer getWeight() { 32 | return weight; 33 | } 34 | 35 | public void setWeight(Integer weight) { 36 | this.weight = weight; 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | public void setName(String name) { 44 | this.name = name; 45 | } 46 | 47 | public String getProtocol() { 48 | return protocol; 49 | } 50 | 51 | public void setProtocol(String protocol) { 52 | this.protocol = protocol; 53 | } 54 | 55 | public String getIp() { 56 | return ip; 57 | } 58 | 59 | public void setIp(String ip) { 60 | this.ip = ip; 61 | } 62 | 63 | public Integer getPort() { 64 | return port; 65 | } 66 | 67 | public void setPort(Integer port) { 68 | this.port = port; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/common/serializer/ZookeeperSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.common.serializer; 2 | 3 | import org.I0Itec.zkclient.exception.ZkMarshallingError; 4 | import org.I0Itec.zkclient.serialize.ZkSerializer; 5 | 6 | import java.nio.charset.StandardCharsets; 7 | 8 | /** 9 | * zk序列化器 10 | * @author 2YSP 11 | * @date 2020/7/25 19:53 12 | */ 13 | public class ZookeeperSerializer implements ZkSerializer { 14 | /** 15 | * 序列化 16 | * @param o 17 | * @return 18 | * @throws ZkMarshallingError 19 | */ 20 | @Override 21 | public byte[] serialize(Object o) throws ZkMarshallingError { 22 | return String.valueOf(o).getBytes(StandardCharsets.UTF_8); 23 | } 24 | 25 | /** 26 | * 反序列化 27 | * @param bytes 28 | * @return 29 | * @throws ZkMarshallingError 30 | */ 31 | @Override 32 | public Object deserialize(byte[] bytes) throws ZkMarshallingError { 33 | return new String(bytes, StandardCharsets.UTF_8); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/config/RpcAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.config; 2 | 3 | import com.github.ship.client.core.DefaultMethodInvoker; 4 | import com.github.ship.client.core.MethodInvoker; 5 | import com.github.ship.client.manager.LoadBalanceManager; 6 | import com.github.ship.client.manager.MessageProtocolsManager; 7 | import com.github.ship.client.manager.ServerDiscoveryManager; 8 | import com.github.ship.client.net.NetClientFactory; 9 | import com.github.ship.client.proxy.ClientProxyFactory; 10 | import com.github.ship.common.constants.ProxyTypeEnum; 11 | import com.github.ship.common.constants.RegisterCenterTypeEnum; 12 | import com.github.ship.common.exception.RpcException; 13 | import com.github.ship.config.properties.RpcConfig; 14 | import com.github.ship.discovery.ServerDiscovery; 15 | import com.github.ship.discovery.ServerRegister; 16 | import com.github.ship.discovery.register.DefaultServerRegister; 17 | import com.github.ship.server.NettyRpcServer; 18 | import com.github.ship.server.RequestHandler; 19 | import com.github.ship.server.RpcServer; 20 | import com.github.ship.server.register.DefaultRpcProcessor; 21 | import com.github.ship.spi.balance.LoadBalance; 22 | import com.github.ship.spi.protocol.MessageProtocol; 23 | import com.github.ship.util.SpringContextHolder; 24 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 25 | import org.springframework.context.ApplicationContext; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.context.annotation.Configuration; 28 | 29 | import javax.annotation.Resource; 30 | import java.lang.reflect.Constructor; 31 | 32 | /** 33 | * 注入需要的bean 34 | * 35 | * @author 2YSP 36 | * @date 2020/7/25 19:43 37 | */ 38 | @Configuration 39 | @EnableConfigurationProperties(RpcConfig.class) 40 | public class RpcAutoConfiguration { 41 | 42 | @Resource 43 | private RpcConfig rpcConfig; 44 | 45 | RpcAutoConfiguration(ApplicationContext applicationContext) { 46 | SpringContextHolder.setApplicationContext(applicationContext); 47 | } 48 | 49 | 50 | @Bean 51 | public ServerDiscovery serverDiscovery() { 52 | RegisterCenterTypeEnum registerCenterTypeEnum = RegisterCenterTypeEnum.getByCode(rpcConfig.getRegisterCenterType()); 53 | try { 54 | Constructor con = registerCenterTypeEnum.getClazz().getConstructor(String.class); 55 | return (ServerDiscovery) con.newInstance(rpcConfig.getRegisterAddress()); 56 | } catch (Exception e) { 57 | throw new RpcException("init ServerDiscovery bean exception:" + e.getMessage()); 58 | } 59 | } 60 | 61 | @Bean 62 | public ServerRegister serverRegister(ServerDiscovery serverDiscovery) { 63 | return new DefaultServerRegister(serverDiscovery, rpcConfig); 64 | } 65 | 66 | 67 | @Bean 68 | public ServerDiscoveryManager serverDiscoveryManager(ServerDiscovery serverDiscovery) { 69 | return new ServerDiscoveryManager(serverDiscovery); 70 | } 71 | 72 | 73 | @Bean 74 | public LoadBalance loadBalance() { 75 | // 设置负载均衡算法 76 | LoadBalance loadBalance = LoadBalanceManager.getLoadBalance(rpcConfig.getLoadBalance()); 77 | return loadBalance; 78 | } 79 | 80 | @Bean 81 | public DefaultMethodInvoker methodInvoker(ServerDiscoveryManager manager, 82 | LoadBalance loadBalance) { 83 | return new DefaultMethodInvoker(manager, NetClientFactory.getInstance(), loadBalance); 84 | } 85 | 86 | 87 | @Bean 88 | public ClientProxyFactory proxyFactory(MethodInvoker methodInvoker) { 89 | ProxyTypeEnum proxyTypeEnum = ProxyTypeEnum.getByCode(rpcConfig.getProxyType()); 90 | try { 91 | // 反射创建实例 用SPI也可以但是没法按需加载 92 | Constructor con = proxyTypeEnum.getClientProxyFactoryClass().getConstructor(MethodInvoker.class); 93 | return (ClientProxyFactory) con.newInstance(methodInvoker); 94 | } catch (Exception e) { 95 | throw new RpcException("init ClientProxyFactory bean exception:" + e.getMessage()); 96 | } 97 | } 98 | 99 | @Bean 100 | public RequestHandler requestHandler(ServerRegister serverRegister) { 101 | MessageProtocol messageProtocol = MessageProtocolsManager.get(rpcConfig.getProtocol()); 102 | if (messageProtocol == null) { 103 | throw new RpcException("invalid message protocol config!"); 104 | } 105 | return new RequestHandler(messageProtocol, serverRegister); 106 | } 107 | 108 | @Bean 109 | public RpcServer rpcServer(RequestHandler requestHandler) { 110 | return new NettyRpcServer(rpcConfig.getServerPort(), rpcConfig.getProtocol(), requestHandler); 111 | } 112 | 113 | @Bean 114 | public DefaultRpcProcessor rpcProcessor(ClientProxyFactory clientProxyFactory, 115 | ServerRegister serverRegister, 116 | RpcServer rpcServer, 117 | ServerDiscoveryManager manager) { 118 | return new DefaultRpcProcessor(clientProxyFactory, serverRegister, rpcServer, manager); 119 | } 120 | 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/config/properties/RpcConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.config.properties; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | /** 6 | * @author 2YSP 7 | * @date 2020/7/26 15:13 8 | */ 9 | @ConfigurationProperties(prefix = "sp.rpc") 10 | public class RpcConfig { 11 | 12 | /** 13 | * 服务注册中心地址 14 | */ 15 | private String registerAddress = "127.0.0.1:2181"; 16 | /** 17 | * 注册中心类型,默认nacos 18 | */ 19 | private String registerCenterType = "nacos"; 20 | 21 | /** 22 | * 服务暴露端口 23 | */ 24 | private Integer serverPort = 9999; 25 | /** 26 | * 服务协议 27 | */ 28 | private String protocol = "java"; 29 | /** 30 | * 负载均衡算法 31 | */ 32 | private String loadBalance = "random"; 33 | /** 34 | * 权重,默认为1 35 | */ 36 | private Integer weight = 1; 37 | /** 38 | * 客户端代理对象生成方式,默认JDK动态代理 39 | */ 40 | private String proxyType = "jdk"; 41 | 42 | 43 | public String getProxyType() { 44 | return proxyType; 45 | } 46 | 47 | public void setProxyType(String proxyType) { 48 | this.proxyType = proxyType; 49 | } 50 | 51 | public String getRegisterCenterType() { 52 | return registerCenterType; 53 | } 54 | 55 | public void setRegisterCenterType(String registerCenterType) { 56 | this.registerCenterType = registerCenterType; 57 | } 58 | 59 | public Integer getWeight() { 60 | return weight; 61 | } 62 | 63 | public void setWeight(Integer weight) { 64 | this.weight = weight; 65 | } 66 | 67 | public String getLoadBalance() { 68 | return loadBalance; 69 | } 70 | 71 | public void setLoadBalance(String loadBalance) { 72 | this.loadBalance = loadBalance; 73 | } 74 | 75 | public String getRegisterAddress() { 76 | return registerAddress; 77 | } 78 | 79 | public void setRegisterAddress(String registerAddress) { 80 | this.registerAddress = registerAddress; 81 | } 82 | 83 | public Integer getServerPort() { 84 | return serverPort; 85 | } 86 | 87 | public void setServerPort(Integer serverPort) { 88 | this.serverPort = serverPort; 89 | } 90 | 91 | public String getProtocol() { 92 | return protocol; 93 | } 94 | 95 | public void setProtocol(String protocol) { 96 | this.protocol = protocol; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/discovery/ServerDiscovery.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.discovery; 2 | 3 | import com.github.ship.common.model.Service; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 9 | * 服务发现抽象类 10 | * @author 2YSP 11 | * @date 2020/7/25 19:45 12 | */ 13 | public interface ServerDiscovery { 14 | 15 | /** 16 | * 服务暴露 17 | * @param serviceResource 18 | */ 19 | void exportService(Service serviceResource); 20 | 21 | /** 22 | * 根据服务名查找服务列表 23 | * @param serviceName 24 | * @return 25 | */ 26 | List findServiceList(String serviceName); 27 | 28 | /** 29 | * 注册服务监听 30 | */ 31 | void registerChangeListener(String serviceName); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/discovery/ServerRegister.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.discovery; 2 | 3 | /** 4 | * 服务注册器,定义服务注册规范 5 | * @author 2YSP 6 | * @date 2020/7/26 13:15 7 | */ 8 | public interface ServerRegister { 9 | 10 | /** 11 | * 注册 12 | * @param so 13 | * @throws Exception 14 | */ 15 | void register(ServiceObject so)throws Exception; 16 | 17 | ServiceObject getServiceObject(String name)throws Exception; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/discovery/ServiceObject.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.discovery; 2 | 3 | /** 4 | * 服务持有对象,保存具体的服务信息备用 5 | * @author 2YSP 6 | * @date 2020/7/26 13:16 7 | */ 8 | public class ServiceObject { 9 | /** 10 | * 11 | * 服务名称 12 | */ 13 | private String name; 14 | /** 15 | * 服务Class 16 | */ 17 | private Class clazz; 18 | /** 19 | * 具体服务 20 | */ 21 | private Object obj; 22 | 23 | public ServiceObject(String name, Class clazz, Object obj) { 24 | this.name = name; 25 | this.clazz = clazz; 26 | this.obj = obj; 27 | } 28 | 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public void setName(String name) { 35 | this.name = name; 36 | } 37 | 38 | public Class getClazz() { 39 | return clazz; 40 | } 41 | 42 | public void setClazz(Class clazz) { 43 | this.clazz = clazz; 44 | } 45 | 46 | public Object getObj() { 47 | return obj; 48 | } 49 | 50 | public void setObj(Object obj) { 51 | this.obj = obj; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/discovery/nacos/NacosServerDiscovery.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.discovery.nacos; 2 | 3 | import com.github.ship.client.cache.ServerDiscoveryCache; 4 | import com.github.ship.common.constants.RpcConstant; 5 | import com.github.ship.common.exception.RpcException; 6 | import com.github.ship.common.model.Service; 7 | import com.github.ship.discovery.ServerDiscovery; 8 | import com.alibaba.nacos.api.exception.NacosException; 9 | import com.alibaba.nacos.api.naming.NamingFactory; 10 | import com.alibaba.nacos.api.naming.NamingService; 11 | import com.alibaba.nacos.api.naming.listener.NamingEvent; 12 | import com.alibaba.nacos.api.naming.pojo.Instance; 13 | import com.google.common.collect.Lists; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.util.CollectionUtils; 17 | 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * @author Ship 25 | * @version 1.0.0 26 | * @description: 27 | * @date 2023/06/16 15:21 28 | */ 29 | public class NacosServerDiscovery implements ServerDiscovery { 30 | 31 | private static Logger logger = LoggerFactory.getLogger(NacosServerDiscovery.class); 32 | 33 | private NamingService namingService; 34 | 35 | public NacosServerDiscovery(String serverAddr) { 36 | try { 37 | this.namingService = NamingFactory.createNamingService(serverAddr); 38 | } catch (NacosException e) { 39 | throw new RpcException("create nacos namingService fail"); 40 | } 41 | } 42 | 43 | @Override 44 | public void exportService(Service serviceResource) { 45 | Instance instance = new Instance(); 46 | instance.setIp(serviceResource.getIp()); 47 | instance.setPort(serviceResource.getPort()); 48 | instance.setEphemeral(true); 49 | Map metadataMap = new HashMap<>(); 50 | metadataMap.put("protocol", serviceResource.getProtocol()); 51 | instance.setWeight(serviceResource.getWeight()); 52 | instance.setMetadata(metadataMap); 53 | try { 54 | namingService.registerInstance(serviceResource.getName(), RpcConstant.NACOS_APP_GROUP_NAME, instance); 55 | } catch (NacosException e) { 56 | logger.error("register to nacos fail", e); 57 | throw new RpcException("register to nacos fail"); 58 | } 59 | logger.info("register interface info to nacos success!"); 60 | } 61 | 62 | @Override 63 | public List findServiceList(String serviceName) { 64 | List instanceList = null; 65 | try { 66 | instanceList = namingService.getAllInstances(serviceName, RpcConstant.NACOS_APP_GROUP_NAME); 67 | } catch (NacosException e) { 68 | logger.error("get all instances fail", e); 69 | throw new RpcException("get all instances fail"); 70 | } 71 | if (CollectionUtils.isEmpty(instanceList)) { 72 | return Lists.newArrayList(); 73 | } 74 | return instanceList.stream() 75 | .filter(i -> i.isHealthy()) 76 | .map(instance -> convertToService(instance)).collect(Collectors.toList()); 77 | } 78 | 79 | private Service convertToService(Instance instance) { 80 | Service service = new Service(); 81 | service.setWeight((int) instance.getWeight()); 82 | // PRC_GROUP@@cn.sp.UserService 转成 cn.sp.UserService 83 | service.setName(instance.getServiceName().replace(RpcConstant.NACOS_APP_GROUP_NAME + "@@", "")); 84 | Map metadata = instance.getMetadata(); 85 | service.setProtocol(metadata.get("protocol")); 86 | service.setIp(instance.getIp()); 87 | service.setPort(instance.getPort()); 88 | return service; 89 | } 90 | 91 | @Override 92 | public void registerChangeListener(String serviceName) { 93 | try { 94 | namingService.subscribe(serviceName, RpcConstant.NACOS_APP_GROUP_NAME, event -> { 95 | if (event instanceof NamingEvent) { 96 | List instances = ((NamingEvent) event).getInstances(); 97 | if (CollectionUtils.isEmpty(instances)) { 98 | ServerDiscoveryCache.removeAll(serviceName); 99 | } 100 | List serviceList = instances.stream().filter(i -> i.isHealthy()).map(this::convertToService).collect(Collectors.toList()); 101 | if (CollectionUtils.isEmpty(serviceList)) { 102 | ServerDiscoveryCache.removeAll(serviceName); 103 | } else { 104 | ServerDiscoveryCache.put(serviceName, serviceList); 105 | } 106 | } 107 | }); 108 | } catch (NacosException e) { 109 | e.printStackTrace(); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/discovery/register/DefaultServerRegister.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.discovery.register; 2 | 3 | import com.github.ship.common.model.Service; 4 | import com.github.ship.config.properties.RpcConfig; 5 | import com.github.ship.discovery.ServerDiscovery; 6 | import com.github.ship.discovery.ServerRegister; 7 | import com.github.ship.discovery.ServiceObject; 8 | 9 | import java.net.InetAddress; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * 默认服务注册器 15 | * 16 | * @author 2YSP 17 | * @date 2020/7/26 13:18 18 | */ 19 | public class DefaultServerRegister implements ServerRegister { 20 | 21 | private Map serviceMap = new HashMap<>(); 22 | 23 | private ServerDiscovery serverDiscovery; 24 | 25 | private RpcConfig rpcConfig; 26 | 27 | public DefaultServerRegister(ServerDiscovery serverDiscovery, RpcConfig rpcConfig) { 28 | this.serverDiscovery = serverDiscovery; 29 | this.rpcConfig = rpcConfig; 30 | } 31 | 32 | @Override 33 | public void register(ServiceObject so) throws Exception { 34 | if (so == null) { 35 | throw new IllegalArgumentException("parameter cannot be empty"); 36 | } 37 | serviceMap.put(so.getName(), so); 38 | Service service = new Service(); 39 | String host = InetAddress.getLocalHost().getHostAddress(); 40 | service.setIp(host); 41 | service.setPort(rpcConfig.getServerPort()); 42 | service.setName(so.getClazz().getName()); 43 | service.setProtocol(rpcConfig.getProtocol()); 44 | service.setWeight(rpcConfig.getWeight()); 45 | serverDiscovery.exportService(service); 46 | } 47 | 48 | @Override 49 | public ServiceObject getServiceObject(String name) throws Exception { 50 | return serviceMap.get(name); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/discovery/zk/ZkChildListenerImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.discovery.zk; 2 | 3 | import com.github.ship.client.cache.ServerDiscoveryCache; 4 | import org.I0Itec.zkclient.IZkChildListener; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 子节点事件监听处理类 12 | */ 13 | public class ZkChildListenerImpl implements IZkChildListener { 14 | 15 | private static Logger logger = LoggerFactory.getLogger(ZkChildListenerImpl.class); 16 | 17 | /** 18 | * 监听子节点的删除和新增事件 19 | * @param parentPath /rpc/serviceName/service 20 | * @param childList 21 | * @throws Exception 22 | */ 23 | @Override 24 | public void handleChildChange(String parentPath, List childList) throws Exception { 25 | logger.debug("Child change parentPath:[{}] -- childList:[{}]", parentPath, childList); 26 | // 只要子节点有改动就清空缓存 27 | String[] arr = parentPath.split("/"); 28 | ServerDiscoveryCache.removeAll(arr[2]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/discovery/zk/ZookeeperServerDiscovery.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.discovery.zk; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.github.ship.common.constants.RpcConstant; 5 | import com.github.ship.common.model.Service; 6 | import com.github.ship.common.serializer.ZookeeperSerializer; 7 | import com.github.ship.discovery.ServerDiscovery; 8 | import org.I0Itec.zkclient.ZkClient; 9 | 10 | import java.io.UnsupportedEncodingException; 11 | import java.net.URLDecoder; 12 | import java.net.URLEncoder; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Optional; 16 | import java.util.stream.Collectors; 17 | 18 | import static com.github.ship.common.constants.RpcConstant.*; 19 | 20 | /** 21 | * zk服务注册器,提供服务注册、暴露服务的能力 22 | * 23 | * @author 2YSP 24 | * @date 2020/7/25 19:49 25 | */ 26 | public class ZookeeperServerDiscovery implements ServerDiscovery { 27 | 28 | private ZkClient zkClient; 29 | 30 | public ZookeeperServerDiscovery(String zkAddress) { 31 | zkClient = new ZkClient(zkAddress); 32 | zkClient.setZkSerializer(new ZookeeperSerializer()); 33 | } 34 | 35 | public static void main(String[] args) { 36 | System.out.println(System.getProperty("user.dir")); 37 | } 38 | 39 | /** 40 | * 服务暴露(其实就是把服务信息保存到Zookeeper上) 41 | * 42 | * @param serviceResource 43 | */ 44 | @Override 45 | public void exportService(Service serviceResource) { 46 | String serviceName = serviceResource.getName(); 47 | String uri = JSON.toJSONString(serviceResource); 48 | try { 49 | uri = URLEncoder.encode(uri, UTF_8); 50 | } catch (UnsupportedEncodingException e) { 51 | e.printStackTrace(); 52 | } 53 | String servicePath = ZK_SERVICE_PATH + PATH_DELIMITER + serviceName + "/service"; 54 | if (!zkClient.exists(servicePath)) { 55 | // 没有该节点就创建 56 | zkClient.createPersistent(servicePath, true); 57 | } 58 | 59 | String uriPath = servicePath + PATH_DELIMITER + uri; 60 | if (zkClient.exists(uriPath)) { 61 | // 删除之前的节点 62 | zkClient.delete(uriPath); 63 | } 64 | // 创建一个临时节点,会话失效即被清理 65 | zkClient.createEphemeral(uriPath); 66 | } 67 | 68 | /** 69 | * 使用Zookeeper客户端,通过服务名获取服务列表 70 | * 服务名格式:接口全路径 71 | * 72 | * @param serviceName 73 | * @return 74 | */ 75 | @Override 76 | public List findServiceList(String serviceName) { 77 | String servicePath = RpcConstant.ZK_SERVICE_PATH + RpcConstant.PATH_DELIMITER + serviceName + "/service"; 78 | List children = zkClient.getChildren(servicePath); 79 | return Optional.ofNullable(children).orElse(new ArrayList<>()).stream().map(str -> { 80 | String deCh = null; 81 | try { 82 | deCh = URLDecoder.decode(str, RpcConstant.UTF_8); 83 | } catch (UnsupportedEncodingException e) { 84 | e.printStackTrace(); 85 | } 86 | return JSON.parseObject(deCh, Service.class); 87 | }).collect(Collectors.toList()); 88 | } 89 | 90 | 91 | @Override 92 | public void registerChangeListener(String serviceName) { 93 | String servicePath = RpcConstant.ZK_SERVICE_PATH + RpcConstant.PATH_DELIMITER + serviceName + "/service"; 94 | zkClient.subscribeChildChanges(servicePath, new ZkChildListenerImpl()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/server/NettyRpcServer.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.server; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.Unpooled; 7 | import io.netty.channel.*; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.handler.logging.LogLevel; 12 | import io.netty.handler.logging.LoggingHandler; 13 | import io.netty.util.ReferenceCountUtil; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.util.concurrent.ExecutorService; 18 | import java.util.concurrent.LinkedBlockingQueue; 19 | import java.util.concurrent.ThreadPoolExecutor; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | 23 | /** 24 | * @author 2YSP 25 | * @date 2020/7/26 14:08 26 | */ 27 | public class NettyRpcServer extends RpcServer { 28 | 29 | private static Logger logger = LoggerFactory.getLogger(NettyRpcServer.class); 30 | 31 | private Channel channel; 32 | 33 | private static final ExecutorService pool = new ThreadPoolExecutor(4, 8, 34 | 200, TimeUnit.SECONDS, 35 | new LinkedBlockingQueue<>(1000), 36 | new ThreadFactoryBuilder().setNameFormat("rpcServer-%d").build()); 37 | 38 | public NettyRpcServer(int port, String protocol, RequestHandler requestHandler) { 39 | super(port, protocol, requestHandler); 40 | } 41 | 42 | @Override 43 | public void start() { 44 | // 配置服务器 45 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); 46 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 47 | try { 48 | ServerBootstrap b = new ServerBootstrap(); 49 | b.group(bossGroup, workerGroup) 50 | .channel(NioServerSocketChannel.class) 51 | .option(ChannelOption.SO_BACKLOG, 100) 52 | .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer() { 53 | 54 | @Override 55 | protected void initChannel(SocketChannel ch) throws Exception { 56 | ChannelPipeline pipeline = ch.pipeline(); 57 | pipeline 58 | // .addLast(new FixedLengthFrameDecoder(20)) 59 | .addLast(new ChannelRequestHandler()); 60 | } 61 | }); 62 | 63 | // 启动服务 64 | ChannelFuture future = b.bind(port).sync(); 65 | logger.debug("Server started successfully."); 66 | channel = future.channel(); 67 | // 等待服务通道关闭 68 | future.channel().closeFuture().sync(); 69 | } catch (Exception e) { 70 | e.printStackTrace(); 71 | logger.error("start netty sever failed,msg:{}", e.getMessage()); 72 | } finally { 73 | // 释放线程组资源 74 | bossGroup.shutdownGracefully(); 75 | workerGroup.shutdownGracefully(); 76 | } 77 | } 78 | 79 | @Override 80 | public void stop() { 81 | this.channel.close(); 82 | } 83 | 84 | private class ChannelRequestHandler extends ChannelInboundHandlerAdapter { 85 | 86 | @Override 87 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 88 | logger.debug("Channel active :{}", ctx); 89 | } 90 | 91 | @Override 92 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 93 | pool.submit(()->{ 94 | try { 95 | logger.debug("the server receives message :{}", msg); 96 | ByteBuf byteBuf = (ByteBuf) msg; 97 | // 消息写入reqData 98 | byte[] reqData = new byte[byteBuf.readableBytes()]; 99 | byteBuf.readBytes(reqData); 100 | // 手动回收 101 | ReferenceCountUtil.release(byteBuf); 102 | byte[] respData = requestHandler.handleRequest(reqData); 103 | ByteBuf respBuf = Unpooled.buffer(respData.length); 104 | respBuf.writeBytes(respData); 105 | logger.debug("Send response:{}", respBuf); 106 | ctx.writeAndFlush(respBuf); 107 | }catch (Exception e){ 108 | logger.error("server read exception",e); 109 | } 110 | }); 111 | } 112 | 113 | @Override 114 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 115 | ctx.flush(); 116 | } 117 | 118 | @Override 119 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 120 | // Close the connection when an exception is raised. 121 | cause.printStackTrace(); 122 | logger.error("Exception occurred:{}", cause.getMessage()); 123 | ctx.close(); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/server/RequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.server; 2 | 3 | import com.github.ship.common.exception.RpcException; 4 | import com.github.ship.spi.protocol.MessageProtocol; 5 | import com.github.ship.common.model.RpcRequest; 6 | import com.github.ship.common.model.RpcResponse; 7 | import com.github.ship.common.constants.RpcStatusEnum; 8 | import com.github.ship.discovery.ServerRegister; 9 | import com.github.ship.discovery.ServiceObject; 10 | import com.github.ship.util.ReflectUtils; 11 | import com.github.ship.util.RpcResponseUtils; 12 | import com.alibaba.fastjson.JSON; 13 | 14 | import java.lang.reflect.Method; 15 | 16 | /** 17 | * 请求处理者,提供解组请求、编组响应等操作 18 | * 19 | * @author 2YSP 20 | * @date 2020/7/26 14:06 21 | */ 22 | public class RequestHandler { 23 | 24 | private MessageProtocol protocol; 25 | 26 | 27 | private ServerRegister serverRegister; 28 | 29 | public RequestHandler(MessageProtocol protocol, ServerRegister serverRegister) { 30 | this.protocol = protocol; 31 | this.serverRegister = serverRegister; 32 | } 33 | 34 | 35 | public byte[] handleRequest(byte[] data) throws Exception { 36 | // 1.解组消息 37 | RpcRequest req = this.protocol.unmarshallingRequest(data); 38 | 39 | // 2.查找服务对应 40 | ServiceObject so = serverRegister.getServiceObject(req.getServiceName()); 41 | 42 | RpcResponse response = null; 43 | 44 | if (so == null) { 45 | response = new RpcResponse(RpcStatusEnum.NOT_FOUND); 46 | 47 | } else { 48 | try { 49 | // 3.反射调用对应的方法过程 50 | Method method = so.getClazz().getMethod(req.getMethod(), ReflectUtils.convertToParameterTypes(req.getParameterTypeNames())); 51 | Object returnValue = method.invoke(so.getObj(), req.getParameters()); 52 | response = new RpcResponse(RpcStatusEnum.SUCCESS); 53 | if (req.getGeneric()) { 54 | response.setReturnValue(RpcResponseUtils.handlerReturnValue(returnValue)); 55 | } else { 56 | response.setReturnValue(returnValue); 57 | } 58 | } catch (Exception e) { 59 | response = new RpcResponse(RpcStatusEnum.ERROR); 60 | String errMsg = JSON.toJSONString(e); 61 | response.setException(new RpcException(errMsg)); 62 | } 63 | } 64 | // 编组响应消息 65 | response.setRequestId(req.getRequestId()); 66 | return this.protocol.marshallingResponse(response); 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/server/RpcServer.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.server; 2 | 3 | /** 4 | * Rpc服务端抽象类 5 | * @author 2YSP 6 | * @date 2020/7/26 14:04 7 | */ 8 | public abstract class RpcServer { 9 | 10 | /** 11 | * 服务端口 12 | */ 13 | protected int port; 14 | /** 15 | * 服务协议 16 | */ 17 | protected String protocol; 18 | /** 19 | * 请求处理者 20 | */ 21 | protected RequestHandler requestHandler; 22 | 23 | public RpcServer(int port, String protocol, RequestHandler requestHandler) { 24 | this.port = port; 25 | this.protocol = protocol; 26 | this.requestHandler = requestHandler; 27 | } 28 | 29 | /** 30 | * 开启服务 31 | */ 32 | public abstract void start(); 33 | 34 | /** 35 | * 关闭服务 36 | */ 37 | public abstract void stop(); 38 | 39 | public int getPort() { 40 | return port; 41 | } 42 | 43 | public void setPort(int port) { 44 | this.port = port; 45 | } 46 | 47 | public String getProtocol() { 48 | return protocol; 49 | } 50 | 51 | public void setProtocol(String protocol) { 52 | this.protocol = protocol; 53 | } 54 | 55 | public RequestHandler getRequestHandler() { 56 | return requestHandler; 57 | } 58 | 59 | public void setRequestHandler(RequestHandler requestHandler) { 60 | this.requestHandler = requestHandler; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/server/register/DefaultRpcProcessor.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.server.register; 2 | 3 | import com.github.ship.annotation.InjectService; 4 | import com.github.ship.annotation.Service; 5 | import com.github.ship.client.manager.ServerDiscoveryManager; 6 | import com.github.ship.client.proxy.AbstractClientProxyFactory; 7 | import com.github.ship.client.proxy.ClientProxyFactory; 8 | import com.github.ship.discovery.ServerRegister; 9 | import com.github.ship.discovery.ServiceObject; 10 | import com.github.ship.server.RpcServer; 11 | import com.google.common.collect.Lists; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.context.ApplicationContext; 15 | import org.springframework.context.ApplicationListener; 16 | import org.springframework.context.event.ContextRefreshedEvent; 17 | 18 | import java.lang.reflect.Field; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Objects; 22 | 23 | /** 24 | * Rpc处理者,支持服务启动暴露,自动注入Service 25 | * 26 | * @author 2YSP 27 | * @date 2020/7/26 14:46 28 | */ 29 | public class DefaultRpcProcessor implements ApplicationListener { 30 | 31 | private static Logger logger = LoggerFactory.getLogger(DefaultRpcProcessor.class); 32 | 33 | 34 | private ClientProxyFactory clientProxyFactory; 35 | 36 | private ServerRegister serverRegister; 37 | 38 | private RpcServer rpcServer; 39 | 40 | private ServerDiscoveryManager serverDiscoveryManager; 41 | 42 | 43 | public DefaultRpcProcessor(ClientProxyFactory clientProxyFactory, ServerRegister serverRegister, 44 | RpcServer rpcServer, ServerDiscoveryManager serverDiscoveryManager) { 45 | this.clientProxyFactory = clientProxyFactory; 46 | this.serverRegister = serverRegister; 47 | this.rpcServer = rpcServer; 48 | this.serverDiscoveryManager = serverDiscoveryManager; 49 | } 50 | 51 | @Override 52 | public void onApplicationEvent(ContextRefreshedEvent event) { 53 | // Spring启动完毕过后会收到一个事件通知 54 | if (Objects.isNull(event.getApplicationContext().getParent())) { 55 | ApplicationContext context = event.getApplicationContext(); 56 | // 开启服务 57 | startServer(context); 58 | // 注入Service 59 | injectService(context); 60 | } 61 | } 62 | 63 | private void injectService(ApplicationContext context) { 64 | final List serviceNameList = Lists.newArrayList(); 65 | String[] names = context.getBeanDefinitionNames(); 66 | for (String name : names) { 67 | Class clazz = context.getType(name); 68 | if (Objects.isNull(clazz)) { 69 | continue; 70 | } 71 | 72 | Field[] declaredFields = clazz.getDeclaredFields(); 73 | for (Field field : declaredFields) { 74 | // 找出标记了InjectService注解的属性 75 | InjectService injectService = field.getAnnotation(InjectService.class); 76 | if (injectService == null) { 77 | continue; 78 | } 79 | 80 | Class fieldClass = field.getType(); 81 | Object object = context.getBean(name); 82 | field.setAccessible(true); 83 | try { 84 | Object proxy = clientProxyFactory.getProxy(fieldClass); 85 | field.set(object, proxy); 86 | } catch (IllegalAccessException e) { 87 | e.printStackTrace(); 88 | } catch (Exception e) { 89 | e.printStackTrace(); 90 | } 91 | serviceNameList.add(fieldClass.getName()); 92 | } 93 | } 94 | // 注册子节点监听 95 | serviceNameList.forEach(i -> { 96 | serverDiscoveryManager.registerChangeListener(i); 97 | }); 98 | logger.info("register service change listener successfully"); 99 | } 100 | 101 | private void startServer(ApplicationContext context) { 102 | Map beans = context.getBeansWithAnnotation(Service.class); 103 | if (beans.size() > 0) { 104 | boolean startServerFlag = true; 105 | for (Object obj : beans.values()) { 106 | try { 107 | Class clazz = obj.getClass(); 108 | Class[] interfaces = clazz.getInterfaces(); 109 | ServiceObject so = null; 110 | /** 111 | * 如果只实现了一个接口就用父类的className作为服务名 112 | * 如果该类实现了多个接口,则用注解里的value作为服务名 113 | */ 114 | if (interfaces.length != 1) { 115 | Service service = clazz.getAnnotation(Service.class); 116 | String value = service.value(); 117 | if (value.equals("")) { 118 | startServerFlag = false; 119 | throw new UnsupportedOperationException("The exposed interface is not specific with '" + obj.getClass().getName() + "'"); 120 | } 121 | so = new ServiceObject(value, Class.forName(value), obj); 122 | } else { 123 | Class supperClass = interfaces[0]; 124 | so = new ServiceObject(supperClass.getName(), supperClass, obj); 125 | } 126 | serverRegister.register(so); 127 | } catch (Exception e) { 128 | e.printStackTrace(); 129 | } 130 | 131 | } 132 | 133 | if (startServerFlag) { 134 | rpcServer.start(); 135 | } 136 | } 137 | 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/spi/balance/LoadBalance.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.spi.balance; 2 | 3 | import com.github.ship.common.model.Service; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 负载均衡算法接口 9 | */ 10 | public interface LoadBalance { 11 | /** 12 | * 13 | * @param services 14 | * @return 15 | */ 16 | Service chooseOne(List services); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/spi/balance/impl/FullRoundBalance.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.spi.balance.impl; 2 | 3 | import com.github.ship.annotation.LoadBalanceAno; 4 | import com.github.ship.common.constants.RpcConstant; 5 | import com.github.ship.common.model.Service; 6 | import com.github.ship.spi.balance.LoadBalance; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 轮询算法 14 | */ 15 | @LoadBalanceAno(RpcConstant.BALANCE_ROUND) 16 | public class FullRoundBalance implements LoadBalance { 17 | 18 | private static Logger logger = LoggerFactory.getLogger(FullRoundBalance.class); 19 | 20 | private int index; 21 | 22 | @Override 23 | public synchronized Service chooseOne(List services) { 24 | // 加锁防止多线程情况下,index超出services.size() 25 | if (index == services.size()) { 26 | index = 0; 27 | } 28 | return services.get(index++); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/spi/balance/impl/RandomBalance.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.spi.balance.impl; 2 | 3 | import com.github.ship.annotation.LoadBalanceAno; 4 | import com.github.ship.common.constants.RpcConstant; 5 | import com.github.ship.common.model.Service; 6 | import com.github.ship.spi.balance.LoadBalance; 7 | 8 | import java.util.List; 9 | import java.util.Random; 10 | 11 | /** 12 | * 随机算法 13 | */ 14 | @LoadBalanceAno(RpcConstant.BALANCE_RANDOM) 15 | public class RandomBalance implements LoadBalance { 16 | 17 | private static Random random = new Random(); 18 | 19 | @Override 20 | public Service chooseOne(List services) { 21 | return services.get(random.nextInt(services.size())); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/spi/balance/impl/SmoothWeightRoundBalance.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.spi.balance.impl; 2 | 3 | import com.github.ship.annotation.LoadBalanceAno; 4 | import com.github.ship.common.constants.RpcConstant; 5 | import com.github.ship.common.model.Service; 6 | import com.github.ship.spi.balance.LoadBalance; 7 | 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * 平滑加权轮询 14 | */ 15 | @LoadBalanceAno(RpcConstant.BALANCE_SMOOTH_WEIGHT_ROUND) 16 | public class SmoothWeightRoundBalance implements LoadBalance { 17 | /** 18 | * key:服务value:当前权重 19 | */ 20 | private static final Map map = new HashMap<>(); 21 | 22 | @Override 23 | public synchronized Service chooseOne(List services) { 24 | services.forEach(service -> 25 | map.computeIfAbsent(service.toString(), key -> service.getWeight()) 26 | ); 27 | Service maxWeightServer = null; 28 | int allWeight = services.stream().mapToInt(Service::getWeight).sum(); 29 | for (Service service : services) { 30 | Integer currentWeight = map.get(service.toString()); 31 | if (maxWeightServer == null || currentWeight > map.get(maxWeightServer.toString())) { 32 | maxWeightServer = service; 33 | } 34 | } 35 | 36 | assert maxWeightServer != null; 37 | 38 | map.put(maxWeightServer.toString(), map.get(maxWeightServer.toString()) - allWeight); 39 | 40 | for (Service service : services) { 41 | Integer currentWeight = map.get(service.toString()); 42 | map.put(service.toString(), currentWeight + service.getWeight()); 43 | } 44 | return maxWeightServer; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/spi/balance/impl/WeightRoundBalance.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.spi.balance.impl; 2 | 3 | import com.github.ship.annotation.LoadBalanceAno; 4 | import com.github.ship.common.constants.RpcConstant; 5 | import com.github.ship.common.model.Service; 6 | import com.github.ship.spi.balance.LoadBalance; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 加权轮询 12 | */ 13 | @LoadBalanceAno(RpcConstant.BALANCE_WEIGHT_ROUND) 14 | public class WeightRoundBalance implements LoadBalance { 15 | 16 | private static int index; 17 | 18 | @Override 19 | public synchronized Service chooseOne(List services) { 20 | int allWeight = services.stream().mapToInt(Service::getWeight).sum(); 21 | int number = (index++) % allWeight; 22 | for(Service service : services){ 23 | if (service.getWeight() > number){ 24 | return service; 25 | } 26 | number -= service.getWeight(); 27 | } 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/spi/protocol/MessageProtocol.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.spi.protocol; 2 | 3 | import com.github.ship.common.model.RpcRequest; 4 | import com.github.ship.common.model.RpcResponse; 5 | 6 | /** 7 | *消息协议:定义编组请求、解组请求、编组响应、解组响应的规范 8 | * @author 2YSP 9 | * @date 2020/7/25 21:01 10 | */ 11 | public interface MessageProtocol { 12 | /** 13 | * 编组请求 14 | * @param request 请求信息 15 | * @return 请求字节数组 16 | * @throws Exception 17 | */ 18 | byte[] marshallingRequest(RpcRequest request) throws Exception; 19 | 20 | /** 21 | * 解组请求 22 | * @param data 23 | * @return 24 | * @throws Exception 25 | */ 26 | RpcRequest unmarshallingRequest(byte[] data) throws Exception; 27 | 28 | /** 29 | * 编组响应 30 | * @param response 31 | * @return 32 | */ 33 | byte[] marshallingResponse(RpcResponse response) throws Exception; 34 | 35 | /** 36 | * 解组响应 37 | * @param data 38 | * @return 39 | * @throws Exception 40 | */ 41 | RpcResponse unmarshallingResponse(byte[] data) throws Exception; 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/spi/protocol/impl/HessianMessageProtocol.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.spi.protocol.impl; 2 | 3 | import com.github.ship.annotation.MessageProtocolAno; 4 | import com.github.ship.common.constants.RpcConstant; 5 | import com.github.ship.common.constants.RpcStatusEnum; 6 | import com.github.ship.common.model.RpcRequest; 7 | import com.github.ship.common.model.RpcResponse; 8 | import com.github.ship.spi.protocol.MessageProtocol; 9 | import com.github.ship.util.GenericObjectUtil; 10 | import com.alibaba.fastjson.JSON; 11 | import com.alipay.hessian.generic.io.GenericSerializerFactory; 12 | import com.alipay.hessian.generic.model.GenericObject; 13 | import com.caucho.hessian.io.Hessian2Input; 14 | import com.caucho.hessian.io.Hessian2Output; 15 | import com.caucho.hessian.io.SerializerFactory; 16 | 17 | import java.io.ByteArrayInputStream; 18 | import java.io.ByteArrayOutputStream; 19 | import java.util.Map; 20 | 21 | /** 22 | * @author Ship 23 | * @version 1.0.0 24 | * @description: 25 | * @date 2023/06/15 14:00 26 | */ 27 | @MessageProtocolAno(RpcConstant.PROTOCOL_HESSIAN) 28 | public class HessianMessageProtocol implements MessageProtocol { 29 | 30 | private final static SerializerFactory serializerFactory = new SerializerFactory(); 31 | 32 | private final static GenericSerializerFactory genericSerializerFactory = new GenericSerializerFactory(); 33 | 34 | @Override 35 | public byte[] marshallingRequest(RpcRequest request) throws Exception { 36 | Map dataMap = JSON.parseObject(JSON.toJSONString(request), Map.class); 37 | GenericObject genericObject = GenericObjectUtil.buildGenericObject(RpcRequest.class.getName(), dataMap); 38 | // Do serializer 39 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 40 | Hessian2Output hout = new Hessian2Output(bout); 41 | hout.setSerializerFactory(genericSerializerFactory); // set to genericSerializerFactory 42 | hout.writeObject(genericObject); 43 | hout.close(); 44 | return bout.toByteArray(); 45 | } 46 | 47 | @Override 48 | public RpcRequest unmarshallingRequest(byte[] data) throws Exception { 49 | ByteArrayInputStream bin = new ByteArrayInputStream(data, 0, data.length); 50 | Hessian2Input hin = new Hessian2Input(bin); 51 | hin.setSerializerFactory(serializerFactory); 52 | Object dst = hin.readObject(); 53 | hin.close(); 54 | RpcRequest rpcRequest = (RpcRequest) dst; 55 | for (int i = 0; i < rpcRequest.getParameterTypeNames().length; i++) { 56 | if ("java.lang.Long".equals(rpcRequest.getParameterTypeNames()[i]) && rpcRequest.getParameters()[i] != null) { 57 | rpcRequest.getParameters()[i] = Long.valueOf(rpcRequest.getParameters()[i].toString()); 58 | } 59 | } 60 | return rpcRequest; 61 | } 62 | 63 | @Override 64 | public byte[] marshallingResponse(RpcResponse response) throws Exception { 65 | Map dataMap = JSON.parseObject(JSON.toJSONString(response), Map.class); 66 | GenericObject genericObject = GenericObjectUtil.buildGenericObject(RpcResponse.class.getName(), dataMap); 67 | // Do serializer 68 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 69 | Hessian2Output hout = new Hessian2Output(bout); 70 | hout.setSerializerFactory(genericSerializerFactory); // set to genericSerializerFactory 71 | hout.writeObject(genericObject); 72 | hout.close(); 73 | return bout.toByteArray(); 74 | } 75 | 76 | @Override 77 | public RpcResponse unmarshallingResponse(byte[] data) throws Exception { 78 | ByteArrayInputStream bin = new ByteArrayInputStream(data, 0, data.length); 79 | Hessian2Input hin = new Hessian2Input(bin); 80 | hin.setSerializerFactory(serializerFactory); 81 | Object dst = hin.readObject(); 82 | hin.close(); 83 | return (RpcResponse) dst; 84 | } 85 | 86 | public static void main(String[] args) throws Exception { 87 | RpcRequest request = new RpcRequest(); 88 | request.setRequestId("001"); 89 | request.setServiceName("user-service"); 90 | request.setMethod("sayHello"); 91 | request.setParameterTypeNames(new String[]{"java.lang.Long"}); 92 | request.setParameters(new Object[]{1L}); 93 | 94 | HessianMessageProtocol hessianMessageProtocol = new HessianMessageProtocol(); 95 | byte[] bytes = hessianMessageProtocol.marshallingRequest(request); 96 | System.out.println(bytes.length); 97 | 98 | RpcRequest request1 = hessianMessageProtocol.unmarshallingRequest(bytes); 99 | System.out.println(request1); 100 | 101 | RpcResponse response = new RpcResponse(); 102 | response.setRequestId("001"); 103 | response.setReturnValue("aaa"); 104 | response.setRpcStatus(RpcStatusEnum.SUCCESS.getCode()); 105 | 106 | byte[] bytes1 = hessianMessageProtocol.marshallingResponse(response); 107 | 108 | RpcResponse rpcResponse = hessianMessageProtocol.unmarshallingResponse(bytes1); 109 | System.out.println(rpcResponse.toString()); 110 | 111 | } 112 | 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/spi/protocol/impl/JavaSerializeMessageProtocol.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.spi.protocol.impl; 2 | 3 | 4 | import com.github.ship.annotation.MessageProtocolAno; 5 | import com.github.ship.common.constants.RpcConstant; 6 | import com.github.ship.common.model.RpcRequest; 7 | import com.github.ship.common.model.RpcResponse; 8 | import com.github.ship.spi.protocol.MessageProtocol; 9 | 10 | import java.io.ByteArrayInputStream; 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.IOException; 13 | import java.io.ObjectInputStream; 14 | import java.io.ObjectOutputStream; 15 | 16 | /** 17 | * Java序列化消息协议 18 | * 19 | * @author 2YSP 20 | * @date 2020/7/25 21:07 21 | */ 22 | @MessageProtocolAno(RpcConstant.PROTOCOL_JAVA) 23 | public class JavaSerializeMessageProtocol implements MessageProtocol { 24 | 25 | private byte[] serialize(Object o) throws IOException { 26 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 27 | ObjectOutputStream out = new ObjectOutputStream(bout); 28 | out.writeObject(o); 29 | return bout.toByteArray(); 30 | 31 | } 32 | 33 | 34 | @Override 35 | public byte[] marshallingRequest(RpcRequest request) throws Exception { 36 | return this.serialize(request); 37 | } 38 | 39 | @Override 40 | public RpcRequest unmarshallingRequest(byte[] data) throws Exception { 41 | ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data)); 42 | return (RpcRequest) in.readObject(); 43 | } 44 | 45 | @Override 46 | public byte[] marshallingResponse(RpcResponse response) throws Exception { 47 | return this.serialize(response); 48 | } 49 | 50 | @Override 51 | public RpcResponse unmarshallingResponse(byte[] data) throws Exception { 52 | ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data)); 53 | return (RpcResponse) in.readObject(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/spi/protocol/impl/KryoMessageProtocol.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.spi.protocol.impl; 2 | 3 | import com.github.ship.annotation.MessageProtocolAno; 4 | import com.github.ship.common.constants.RpcConstant; 5 | import com.github.ship.common.model.RpcRequest; 6 | import com.github.ship.common.model.RpcResponse; 7 | import com.github.ship.spi.protocol.MessageProtocol; 8 | import com.esotericsoftware.kryo.Kryo; 9 | import com.esotericsoftware.kryo.io.Input; 10 | import com.esotericsoftware.kryo.io.Output; 11 | import org.objenesis.strategy.StdInstantiatorStrategy; 12 | 13 | import java.io.ByteArrayInputStream; 14 | import java.io.ByteArrayOutputStream; 15 | 16 | /** 17 | * Kryo实现序列化和反序列化 18 | * 注意:kryo不是线程安全的 19 | */ 20 | @MessageProtocolAno(RpcConstant.PROTOCOL_KRYO) 21 | public class KryoMessageProtocol implements MessageProtocol { 22 | 23 | private static final ThreadLocal kryoLocal = new ThreadLocal() { 24 | @Override 25 | protected Kryo initialValue() { 26 | Kryo kryo = new Kryo(); 27 | kryo.setReferences(false); 28 | kryo.register(RpcRequest.class); 29 | kryo.register(RpcResponse.class); 30 | Kryo.DefaultInstantiatorStrategy strategy = (Kryo.DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy(); 31 | strategy.setFallbackInstantiatorStrategy(new StdInstantiatorStrategy()); 32 | return kryo; 33 | } 34 | }; 35 | 36 | /** 37 | * 获得当前线程的 Kryo 实例 38 | * 39 | * @return 当前线程的 Kryo 实例 40 | */ 41 | public static Kryo getInstance() { 42 | return kryoLocal.get(); 43 | } 44 | 45 | @Override 46 | public byte[] marshallingRequest(RpcRequest request) throws Exception { 47 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 48 | Output output = new Output(bout); 49 | Kryo kryo = getInstance(); 50 | kryo.writeClassAndObject(output, request); 51 | byte[] bytes = output.toBytes(); 52 | output.flush(); 53 | return bytes; 54 | } 55 | 56 | @Override 57 | public RpcRequest unmarshallingRequest(byte[] data) throws Exception { 58 | ByteArrayInputStream bin = new ByteArrayInputStream(data); 59 | Input input = new Input(bin); 60 | Kryo kryo = getInstance(); 61 | RpcRequest request = (RpcRequest) kryo.readClassAndObject(input); 62 | input.close(); 63 | return request; 64 | } 65 | 66 | @Override 67 | public byte[] marshallingResponse(RpcResponse response) throws Exception { 68 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 69 | Output output = new Output(bout); 70 | Kryo kryo = getInstance(); 71 | kryo.writeClassAndObject(output, response); 72 | byte[] bytes = output.toBytes(); 73 | output.flush(); 74 | return bytes; 75 | } 76 | 77 | @Override 78 | public RpcResponse unmarshallingResponse(byte[] data) throws Exception { 79 | ByteArrayInputStream bin = new ByteArrayInputStream(data); 80 | Input input = new Input(bin); 81 | Kryo kryo = getInstance(); 82 | RpcResponse response = (RpcResponse) kryo.readClassAndObject(input); 83 | input.close(); 84 | return response; 85 | } 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/spi/protocol/impl/ProtoBufMessageProtocol.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.spi.protocol.impl; 2 | 3 | import com.github.ship.annotation.MessageProtocolAno; 4 | import com.github.ship.common.constants.RpcConstant; 5 | import com.github.ship.common.model.RpcRequest; 6 | import com.github.ship.common.model.RpcResponse; 7 | import com.github.ship.spi.protocol.MessageProtocol; 8 | import com.github.ship.util.SerializingUtil; 9 | 10 | /** 11 | * Protobuf序列化协议 12 | * @author 2YSP 13 | * @date 2020/8/5 21:22 14 | */ 15 | @MessageProtocolAno(RpcConstant.PROTOCOL_PROTOBUF) 16 | public class ProtoBufMessageProtocol implements MessageProtocol { 17 | 18 | @Override 19 | public byte[] marshallingRequest(RpcRequest request) throws Exception { 20 | return SerializingUtil.serialize(request); 21 | } 22 | 23 | @Override 24 | public RpcRequest unmarshallingRequest(byte[] data) throws Exception { 25 | return SerializingUtil.deserialize(data,RpcRequest.class); 26 | } 27 | 28 | @Override 29 | public byte[] marshallingResponse(RpcResponse response) throws Exception { 30 | return SerializingUtil.serialize(response); 31 | } 32 | 33 | @Override 34 | public RpcResponse unmarshallingResponse(byte[] data) throws Exception { 35 | return SerializingUtil.deserialize(data,RpcResponse.class); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/util/ClassUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.util; 2 | 3 | import java.net.URI; 4 | import java.net.URISyntaxException; 5 | 6 | /** 7 | * @author Ship 8 | * @version 1.0.0 9 | * @description: 10 | * @date 2023/08/18 11:24 11 | */ 12 | public class ClassUtils { 13 | 14 | public static final String CLASS_EXTENSION = ".class"; 15 | 16 | public static final String JAVA_EXTENSION = ".java"; 17 | 18 | public static URI toURI(String name) { 19 | try { 20 | return new URI(name); 21 | } catch (URISyntaxException e) { 22 | e.printStackTrace(); 23 | } 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/util/CodeGenerateUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.util; 2 | 3 | import com.github.ship.common.exception.RpcException; 4 | 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Parameter; 7 | 8 | /** 9 | * @author Ship 10 | * @version 1.0.0 11 | * @description: 代码生成工具类 12 | * @date 2023/08/18 14:27 13 | */ 14 | public class CodeGenerateUtils { 15 | 16 | 17 | /** 18 | * 构建Javassist需要的方法体 19 | * 20 | * @param interfaceClazz 21 | * @param method 22 | * @return 23 | */ 24 | public static String genJavassistMethodBody(Class interfaceClazz, Method method) { 25 | String methodBody = String.format("{\n return methodInvoker.$invoke(\"%s\", \"%s\", new String[]{%s},new Object[]{%s}, Boolean.FALSE);\n}", 26 | interfaceClazz.getName(), 27 | method.getName(), 28 | buildParameters(method), buildRealParameters(method)); 29 | return methodBody; 30 | } 31 | 32 | /** 33 | * 构建JavaCompiler需要的方法体 34 | * 35 | * @param interfaceClazz 36 | * @param method 37 | * @return 38 | */ 39 | public static String genJavaCompilerMethodBody(Class interfaceClazz, Method method) { 40 | String returnTypeName = method.getReturnType().getName(); 41 | String methodBody = String.format("{\n return (%s)methodInvoker.$invoke(\"%s\", \"%s\", new String[]{%s},new Object[]{%s}, Boolean.FALSE);\n}", 42 | returnTypeName, 43 | interfaceClazz.getName(), 44 | method.getName(), 45 | buildParameters(method), buildRealParameterNames(method)); 46 | return methodBody; 47 | } 48 | 49 | 50 | public static String buildRealParameterNames(Method method) { 51 | int paramLength = method.getParameterCount(); 52 | if (paramLength == 0) { 53 | throw new RpcException("at last one parameter required"); 54 | } 55 | final StringBuilder sb = new StringBuilder(); 56 | Parameter[] parameters = method.getParameters(); 57 | for (int i = 0; i < paramLength; i++) { 58 | sb.append(parameters[i].getName()); 59 | if (i != paramLength - 1) { 60 | sb.append(","); 61 | } 62 | } 63 | return sb.toString(); 64 | } 65 | 66 | /** 67 | * @param method 68 | * @return 69 | */ 70 | public static String buildRealParameters(Method method) { 71 | int paramLength = method.getParameterCount(); 72 | if (paramLength == 0) { 73 | throw new RpcException("at last one parameter required"); 74 | } 75 | StringBuilder sb = new StringBuilder(); 76 | for (int i = 0; i < paramLength; i++) { 77 | sb.append("$" + (i + 1)); 78 | if (i != paramLength - 1) { 79 | sb.append(","); 80 | } 81 | } 82 | return sb.toString(); 83 | } 84 | 85 | /** 86 | * @param method 87 | * @return 88 | */ 89 | public static String buildParameters(Method method) { 90 | if (method.getParameterCount() == 0) { 91 | throw new RpcException("at last one parameter required"); 92 | } 93 | String[] parameterTypeNames = ReflectUtils.getParameterTypeNames(method); 94 | StringBuilder sb = new StringBuilder(); 95 | int i = 0; 96 | for (String p : parameterTypeNames) { 97 | sb.append("\""); 98 | sb.append(p); 99 | sb.append("\""); 100 | if (i != parameterTypeNames.length - 1) { 101 | sb.append(","); 102 | } 103 | i++; 104 | } 105 | return sb.toString(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/util/GenericObjectUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.util; 2 | 3 | import com.alipay.hessian.generic.exception.ConvertException; 4 | import com.alipay.hessian.generic.model.*; 5 | 6 | import java.lang.reflect.Array; 7 | import java.util.*; 8 | 9 | /** 10 | * @author Ship 11 | * @version 1.0.0 12 | * @description: 13 | * @date 2023/06/15 11:13 14 | */ 15 | public class GenericObjectUtil { 16 | 17 | 18 | /** 19 | * 将 GenericObject 转换为具体对象 20 | * 21 | * @param genericObject 22 | * 待转换的GenericObject 23 | * @return 转换后结果 24 | */ 25 | @SuppressWarnings("unchecked") 26 | public static T convertToObject(Object genericObject) { 27 | 28 | try { 29 | return (T) innerToConvertObject(genericObject, new IdentityHashMap()); 30 | } catch (Throwable t) { 31 | throw new ConvertException(t); 32 | } 33 | } 34 | 35 | private static Object innerToConvertObject(Object value, Map map) throws Exception { 36 | 37 | // 判null 38 | if (value == null) { 39 | return null; 40 | } 41 | 42 | // 值为GenericObject类型 43 | if (value.getClass() == GenericObject.class) { 44 | GenericObject genericObject = (GenericObject) value; 45 | return doConvertToObject(genericObject, map); 46 | } 47 | 48 | // 值为GenericCollection类型 49 | if (value.getClass() == GenericCollection.class) { 50 | GenericCollection collection = (GenericCollection) value; 51 | return doConvertToCollection(collection, map); 52 | } 53 | 54 | // 值为GenericMap类型 55 | if (value.getClass() == GenericMap.class) { 56 | GenericMap genericMap = (GenericMap) value; 57 | return doConvertToMap(genericMap, map); 58 | } 59 | 60 | // 值为GenericArray类型 61 | if (value.getClass() == GenericArray.class) { 62 | GenericArray genericArray = (GenericArray) value; 63 | return doConvertToArray(genericArray, map); 64 | } 65 | 66 | // 值为GenericClass类型 67 | if (value.getClass() == GenericClass.class) { 68 | GenericClass genericClass = (GenericClass) value; 69 | return doConvertToClass(genericClass, map); 70 | } 71 | 72 | // 说明是jdk类, 处理集合类,将集合类中结果转换 73 | Object obj = handleCollectionOrMapToObject(value, map); 74 | return obj; 75 | } 76 | 77 | @SuppressWarnings({ "rawtypes", "unchecked" }) 78 | private static Object handleCollectionOrMapToObject(Object value, Map map) throws Exception { 79 | 80 | // 1. 判null 81 | if (value == null) { 82 | return null; 83 | } 84 | 85 | // 2. 查看缓存的转换记录是否存在转换历史 86 | if (map.get(value) != null) { 87 | return map.get(value); 88 | } 89 | 90 | // 3. 处理Collection实现类情况 91 | if (Collection.class.isAssignableFrom(value.getClass())) { 92 | 93 | Collection values = (Collection) value; 94 | Collection result = (Collection) value.getClass().newInstance(); 95 | map.put(value, result); 96 | 97 | for (Object obj : values) { 98 | result.add(innerToConvertObject(obj, map)); 99 | } 100 | 101 | return result; 102 | } 103 | 104 | // 4. 处理Map实现类情况 105 | if (Map.class.isAssignableFrom(value.getClass())) { 106 | 107 | Map valueMap = (Map) value; 108 | Map result = (Map) value.getClass().newInstance(); 109 | map.put(value, result); 110 | 111 | Iterator iter = valueMap.entrySet().iterator(); 112 | while (iter.hasNext()) { 113 | Map.Entry entry = (Map.Entry) iter.next(); 114 | result.put(innerToConvertObject(entry.getKey(), map), innerToConvertObject(entry.getValue(), map)); 115 | } 116 | 117 | return result; 118 | } 119 | 120 | return value; 121 | } 122 | 123 | private static Object doConvertToObject(GenericObject genericObject, Map map) throws Exception { 124 | 125 | if (genericObject == null) { 126 | return genericObject; 127 | } 128 | Map result = new HashMap(); 129 | 130 | // 如果map中缓存转换结果,直接返回 131 | Object object = map.get(genericObject); 132 | if (object != null) { 133 | return object; 134 | } 135 | 136 | for (Map.Entry entry : genericObject.getFields().entrySet()) { 137 | result.put(entry.getKey(), convertToObject(entry.getValue())); 138 | } 139 | 140 | return result; 141 | } 142 | 143 | private static Class loadClassFromTCCL(String clazzName) throws ClassNotFoundException { 144 | return Class.forName(clazzName, true, Thread.currentThread().getContextClassLoader()); 145 | } 146 | 147 | @SuppressWarnings({ "rawtypes", "unchecked" }) 148 | private static Object doConvertToCollection(GenericCollection genericCollection, Map map) 149 | throws Exception { 150 | 151 | // 如果map中缓存转换结果,直接返回 152 | Object object = map.get(genericCollection); 153 | if (object != null) { 154 | return object; 155 | } 156 | 157 | // 检测 GenericCollection 是否封装 Collection 实例 158 | Class clazz = loadClassFromTCCL(genericCollection.getType()); 159 | if (!Collection.class.isAssignableFrom(clazz)) { 160 | throw new IllegalArgumentException("GenericCollection实例未封装Collection实例."); 161 | } 162 | 163 | // 初始化Collection对象,并放入map 164 | Collection result = (Collection) clazz.newInstance(); 165 | map.put(genericCollection, result); 166 | 167 | // 填充Collection对象 168 | Collection values = genericCollection.getCollection(); 169 | for (Object value : values) { 170 | result.add(innerToConvertObject(value, map)); 171 | } 172 | 173 | return result; 174 | } 175 | 176 | @SuppressWarnings({ "unchecked", "rawtypes" }) 177 | private static Object doConvertToMap(GenericMap genericMap, Map map) throws Exception { 178 | 179 | // 如果map中缓存转换结果,直接返回 180 | Object object = map.get(genericMap); 181 | if (object != null) { 182 | return object; 183 | } 184 | 185 | // 检测 GenericMap 是否封装 Map 实例 186 | Class clazz = loadClassFromTCCL(genericMap.getType()); 187 | if (!Map.class.isAssignableFrom(clazz)) { 188 | throw new IllegalArgumentException("GenericMap实例未封装Map实例."); 189 | } 190 | 191 | // 初始化对象,并放入map 192 | Map result = (Map) clazz.newInstance(); 193 | map.put(genericMap, result); 194 | 195 | // 填充map对象 196 | Iterator iter = genericMap.getMap().entrySet().iterator(); 197 | while (iter.hasNext()) { 198 | Map.Entry entry = (Map.Entry) iter.next(); 199 | result.put(innerToConvertObject(entry.getKey(), map), innerToConvertObject(entry.getValue(), map)); 200 | } 201 | 202 | return result; 203 | } 204 | 205 | @SuppressWarnings("rawtypes") 206 | private static Object doConvertToArray(GenericArray genericArray, Map map) throws Exception { 207 | 208 | // 如果map中缓存转换结果,直接返回 209 | Object object = map.get(genericArray); 210 | if (object != null) { 211 | return object; 212 | } 213 | 214 | // 初始化数组对象,并放入map 215 | Class clazz = loadClassFromTCCL(genericArray.getComponentType()); 216 | Object[] objects = genericArray.getObjects(); 217 | Object result = Array.newInstance(clazz, objects.length); 218 | map.put(genericArray, result); 219 | 220 | // 填充数组对象 221 | for (int i = 0; i < objects.length; i++) { 222 | Array.set(result, i, innerToConvertObject(objects[i], map)); 223 | } 224 | 225 | return result; 226 | } 227 | 228 | private static Object doConvertToClass(GenericClass genericClass, Map map) 229 | throws ClassNotFoundException { 230 | // 如果map中缓存转换结果,直接返回 231 | Object object = map.get(genericClass); 232 | if (object != null) { 233 | return object; 234 | } 235 | 236 | Object obj = loadClassFromTCCL(genericClass.getClazzName()); 237 | map.put(genericClass, obj); 238 | return obj; 239 | } 240 | 241 | @SuppressWarnings("rawtypes") 242 | public static GenericObject buildGenericObject(String className, Map data) { 243 | GenericObject object = new GenericObject(className); 244 | Iterator iterator = data.entrySet().iterator(); 245 | while (iterator.hasNext()) { 246 | Map.Entry entry = (Map.Entry) iterator.next(); 247 | object.putField((String) entry.getKey(), entry.getValue()); 248 | } 249 | return object; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/util/ReflectUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.util; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | /** 6 | * @Author: Ship 7 | * @Description: 8 | * @Date: Created in 2023/6/15 9 | */ 10 | public class ReflectUtils { 11 | 12 | /** 13 | * @param method 14 | * @return 15 | */ 16 | public static String[] getParameterTypeNames(Method method) { 17 | Class[] parameterTypes = method.getParameterTypes(); 18 | String[] arr = new String[parameterTypes.length]; 19 | for (int i = 0; i < parameterTypes.length; i++) { 20 | arr[i] = parameterTypes[i].getName(); 21 | } 22 | return arr; 23 | } 24 | 25 | /** 26 | * @param parameterTypeNames 27 | * @return 28 | */ 29 | public static Class[] convertToParameterTypes(String[] parameterTypeNames) { 30 | Class[] arr = new Class[parameterTypeNames.length]; 31 | for (int i = 0; i < parameterTypeNames.length; i++) { 32 | try { 33 | arr[i] = Class.forName(parameterTypeNames[i]); 34 | } catch (ClassNotFoundException e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | return arr; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/util/RpcResponseUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.util; 2 | 3 | import com.github.ship.common.exception.RpcException; 4 | import com.alibaba.fastjson.JSON; 5 | import com.google.common.collect.Lists; 6 | 7 | import java.math.BigDecimal; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * @Author: Ship 13 | * @Description: 14 | * @Date: Created in 2023/6/18 15 | */ 16 | public class RpcResponseUtils { 17 | 18 | private static List JDK_CLASS = Lists.newArrayList( 19 | Long.class, 20 | Integer.class, 21 | Byte.class, 22 | Short.class, 23 | Double.class, 24 | BigDecimal.class, 25 | String.class 26 | ); 27 | 28 | /** 29 | * 泛化调用结果处理 30 | * 31 | * @param returnValue 32 | * @return 33 | */ 34 | public static Object handlerReturnValue(Object returnValue) { 35 | if (returnValue == null) { 36 | return null; 37 | } 38 | Class returnValueClass = returnValue.getClass(); 39 | if (returnValueClass.isPrimitive()) { 40 | throw new RpcException("方法返回值不支持JDK原始类型"); 41 | } 42 | if (JDK_CLASS.contains(returnValueClass)) { 43 | return returnValue; 44 | } 45 | // POJO 转map 46 | Map dataMap = JSON.parseObject(JSON.toJSONString(returnValue), Map.class); 47 | return dataMap; 48 | } 49 | 50 | public static void main(String[] args) { 51 | Long a = 1L; 52 | System.out.println(a.getClass().isPrimitive()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/util/SerializingUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.util; 2 | 3 | 4 | import com.dyuproject.protostuff.LinkedBuffer; 5 | import com.dyuproject.protostuff.ProtobufIOUtil; 6 | import com.dyuproject.protostuff.Schema; 7 | import com.dyuproject.protostuff.runtime.RuntimeSchema; 8 | 9 | 10 | /** 11 | * protostuff工具类 12 | * 13 | * @author Ship 14 | * @date 2020-08-05 14:53 15 | */ 16 | public class SerializingUtil { 17 | 18 | /** 19 | * 将目标类序列化为byte数组 20 | * 21 | * @param source 22 | * @param 23 | * @return 24 | */ 25 | public static byte[] serialize(T source) { 26 | Schema schema = RuntimeSchema.getSchema((Class) source.getClass()); 27 | LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); 28 | final byte[] result; 29 | try { 30 | result = ProtobufIOUtil.toByteArray(source, schema, buffer); 31 | } finally { 32 | buffer.clear(); 33 | } 34 | return result; 35 | } 36 | 37 | /** 38 | * 将byte数组序列化为目标类 39 | * 40 | * @param source 41 | * @param clazz 42 | * @param 43 | * @return 44 | */ 45 | public static T deserialize(byte[] source, Class clazz) { 46 | Schema schema = RuntimeSchema.getSchema(clazz); 47 | T t = schema.newMessage(); 48 | ProtobufIOUtil.mergeFrom(source, t, schema); 49 | return t; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/ship/util/SpringContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.github.ship.util; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | 6 | /** 7 | * @author Ship 8 | * @version 1.0.0 9 | * @description: 10 | * @date 2023/06/16 11 | */ 12 | public class SpringContextHolder { 13 | 14 | private static ApplicationContext applicationContext; 15 | 16 | private SpringContextHolder() { 17 | 18 | } 19 | 20 | public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 21 | SpringContextHolder.applicationContext = applicationContext; 22 | } 23 | 24 | /** 25 | * 根据类获取bean 26 | * 27 | * @param clazz 28 | * @param 29 | * @return 30 | */ 31 | public static T getBean(Class clazz) { 32 | return applicationContext.getBean(clazz); 33 | } 34 | 35 | 36 | /** 37 | * 根据名称获取bean 38 | * 39 | * @param name 40 | * @return 41 | */ 42 | public static Object getBean(String name) { 43 | return applicationContext.getBean(name); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/com.github.ship.spi.balance.LoadBalance: -------------------------------------------------------------------------------- 1 | com.github.ship.spi.balance.impl.RandomBalance 2 | com.github.ship.spi.balance.impl.FullRoundBalance 3 | com.github.ship.spi.balance.impl.WeightRoundBalance 4 | com.github.ship.spi.balance.impl.SmoothWeightRoundBalance -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/com.github.ship.spi.protocol.MessageProtocol: -------------------------------------------------------------------------------- 1 | com.github.ship.spi.protocol.impl.JavaSerializeMessageProtocol 2 | com.github.ship.spi.protocol.impl.ProtoBufMessageProtocol 3 | com.github.ship.spi.protocol.impl.KryoMessageProtocol 4 | com.github.ship.spi.protocol.impl.HessianMessageProtocol -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.github.ship.config.RpcAutoConfiguration -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); 2 | # you may not use this file except in compliance with the License. 3 | # You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | 13 | # log4j configuration used during build and unit tests 14 | 15 | log4j.rootLogger=info,stdout 16 | log4j.threshold=ALL 17 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 18 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 19 | log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %t %-5p %c{2} (%F:%M(%L)) - %m%n 20 | --------------------------------------------------------------------------------