├── .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 | 
3 | 
4 | 
5 | 
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 extends JavaFileObject> 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