# SPeed-RPC 手写基于注解声明式服务接口 RPC 框架
**Repository Path**: tlgen_1/spring-boot-rpc
## Basic Information
- **Project Name**: SPeed-RPC 手写基于注解声明式服务接口 RPC 框架
- **Description**: 1.基于注解 @Fetch 面向接口设计, 声明式服务调用
2.支持负载均衡策略,默认随机策略
3.统一返回响应体,由于 RPC 调用返回类型不可预测,定义统一响应报文
4.支持从 Nacos 注册中心中拉取服务列表
5.支持底层 OkHttp3 ,设置请求超时时间、读写超时时间配置
6.服务熔断降级 forback 返回托底数据响应,正在研发...
- **Primary Language**: Unknown
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2023-12-05
- **Last Updated**: 2023-12-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 手写 RPC 框架
#### 1.SPeed-RPC 框架简介
```ruby
1.基于注解 @Fetch 面向接口设计, 声明式服务调用
2.支持负载均衡策略,默认随机策略
3.统一返回响应体,由于 RPC 调用返回类型不可预测,定义统一响应报文
4.支持从 Nacos 注册中心中拉取服务列表
5.支持底层 OkHttp3 ,设置请求超时时间、读写超时时间配置
6.服务熔断降级 forback 返回托底数据响应,正在研发...
```
## 技术实现分享:
#### 1.Spring 中接口如何作为 bean 进行注册?
```ruby
首先,在 Spring 中接口是无法做为 bean 进行注册的,至于怎么做,
有一招偷天换日,使用 JDK 动态代理,把接口通过动态代理类去做为实际的 Bean,
即代理对象,即实现层,这里可以去参考 MyBatis Plus 框架的底层设计技巧,
这里基于手动封装的 @Fetch 注解以及反射
结合 Spring 框架 Bean 生命周期扩展接口进行 Bean 的注册,
当然还会涉及到一招叫鬼斧神工,Spring 的 FactoryBean。
```
#### 2.请求框架是使用的什么技术方案?
```ruby
使用的 OkHttp3,该框架基于池化技术进行了优化。
```
#### 3.如何实现负载均衡策略?
```ruby
手写负载均衡实现算法,Hash 算法、随机算法、轮询算法、加权随机、加权轮询。
```
#### 4.如何从主流的注册中心组件中拉取服务列表?
```ruby
有多种思路,比如
集成 Nacos 的客户端,根据服务名手动拉取;
或者通过反射加载 Eureka 扩展接口类,调用相关实现,
如果该服务本身集成 Eureka,那么可以成功拉取;
或者基于定时配合事件监听去拉取配置都可以。
```
#### 5.是否支持 ribbon 的负载均衡策略,配置是否生效?
```ruby
支持部分,且生效。基于环境配置判断匹配去选择对应负载均衡策略。
```
## 快速上手(集成案例分享)
### 一、准备一个服务提供者
#### 1.服务提供者项目
```ruby
spring-boot-mytest
这里集成了 nacos ,当然也可以不用集成 nacos,可以手动指定服务信息,
这里只是以微服务的方式举例。
```

#### 2. application.yml
```ruby
微服务项目,可以参考这里集成 Nacos 基础的配置。
```
```ruby
spring:
application:
name: base-info
# nacos相关配置
nacos:
discovery:
server-addr: 127.0.0.1:8848
#是否将本应用注册到nacos服务列表,默认是false
auto-register: true
#本服务所属的命名空间的ID,默认是空,也就是public
namespace: public
register:
#本服务是否接受外部的请求,默认true
enabled: true
#本服务要注册到命名空间下的哪个组,默认DEFAULT_GROUP
group-name: DEFAULT_GROUP
#服务的名称,会展示在nacos服务列表,要求唯一,可以不写,默认是spring.application.name的值
service-name:
```
#### 3.注册到 nacos

### 二、搭建一个消费者服务
#### 1.服务消费者项目
```ruby
spring-boot-mylocal
```

#### 2.集成 rpc 框架依赖
```ruby
com.gitee_1
speed-rpc
1.0.0
```
#### 3.声明服务调用接口
```ruby
SysUserInterface.java 接口
@Fetch 注解介绍
- name:提供者服务名称,必输
- pattern:请求路径匹配,
如 pattern = "/order" 可以匹配到 /order-info,可选
- url:手动指定的单个服务请求地址,设置该属性则不走负载均衡策略,可选
```
```ruby
package com.example.interfaces;
import com.example.model.SysUser;
import com.tlgen.rpc.annotation.Fetch;
import com.tlgen.rpc.comon.R;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Fetch(name = "base-info")
public interface SysUserInterface {
@GetMapping("/selectByUsername")
R selectByUsername(String username);
@GetMapping("/selectByCondition/{mobile}/{email}")
R selectByCondition(@PathVariable("mobile") String mobile, @PathVariable("email") String email);
@PostMapping("/sendMsg")
R sendMsg(@RequestParam("username") String username, @RequestParam("mobile") String mobile);
@PostMapping("/saveUser")
R saveUser(@RequestBody SysUser user);
@PutMapping("/updateUser")
R updateUser(@RequestBody SysUser user);
@DeleteMapping("/deleteUser")
R deleteUser(@RequestBody SysUser user);
@DeleteMapping("/deleteUserPath/{mobile}/{email}")
R deleteUserPath(@PathVariable("mobile") String mobile, @PathVariable("email") String email);
@PostMapping("/updateRole")
R updateRole(@RequestBody Map map);
@PostMapping("/send")
R send(@RequestBody String jsonString);
@PutMapping("/saveBatch")
R saveBatch(@RequestBody List userList);
}
```
#### 4.写个接口测试调用
```ruby
SysUserController.java 控制类
以 Bean 的方式把 SysUserInterface 接口进行注入调用
```
```ruby
package com.example.controller;
import com.example.interfaces.SysUserInterface;
import com.example.model.SysUser;
import com.tlgen.rpc.comon.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class SysUserController {
@Autowired
private SysUserInterface userInterface;
@GetMapping("/testApi")
public String testApi() {
// 测试 get 条件查询 ? 的方式
R ret1 = userInterface.selectByUsername("小e");
// 测试 get 条件查询 @PathVariable 方式
R ret2 = userInterface.selectByCondition("13617786374", "abc@163.com");
// 测试 post 传多个参数, @RequestParam 修饰参数
R ret3 = userInterface.sendMsg("13617786374", "abc@163.com");
// 测试 post 传对象类型 SysUser, @RequestBody 修饰参数
SysUser user = new SysUser();
user.setId("1");
user.setUsername("小e");
user.setMobile("13617786374");
user.setEmail("abc@163.com");
user.setGender(1);
R ret4 = userInterface.saveUser(user);
// 测试 put 传对象类型 SysUser, @RequestBody 修饰参数
R ret5 = userInterface.updateUser(user);
// 测试 delete 传对象类型 SysUser, @RequestBody 修饰参数
R ret6 = userInterface.deleteUser(user);
// 测试 delete 使用 @PathVariable 修饰参数的方式
R ret7 = userInterface.deleteUserPath("13617786374", "abc@163.com");
// 测试 post 使用 @RequestBody 修饰参数, 传一个 Map 的方式
Map map = new HashMap<>();
map.put("id", "1");
map.put("username", "小e");
map.put("email", "abc@163.com");
// 测试 post 使用 @RequestBody 修饰参数, 传一个 JSON 字符串的方式
R ret8 = userInterface.updateRole(map);
String jsonString = "{\n" +
" \"userInfo\":{\n" +
" \"id\":\"1\",\n" +
" \"username\":\"小e\",\n" +
" \"mobile\":\"13617786374\",\n" +
" \"email\":\"abc@163.com\"\n" +
" }\n" +
"}";
R ret9 = userInterface.send(jsonString);
// 测试 put 使用 @RequestBody 修饰参数, 传一个 List 的方式
List userList = new ArrayList<>();
userList.add(user);
R ret10 = userInterface.saveBatch(userList);
System.out.println(ret1);
System.out.println(ret2);
System.out.println(ret3);
System.out.println(ret4);
System.out.println(ret5);
System.out.println(ret6);
System.out.println(ret7);
System.out.println(ret8);
System.out.println(ret9);
System.out.println(ret10);
return "SUCCESS";
}
}
```
#### 5.测试获取输出结果
```ruby
R(code=200, msg=操作成功!, data=小e)
R(code=200, msg=操作成功!, data=13617786374 ==> abc@163.com)
R(code=200, msg=操作成功!, data=13617786374 ==> abc@163.com)
R(code=200, msg=操作成功!, data={"id":"1","username":"小e","password":null,"email":"abc@163.com","gender":1})
R(code=200, msg=操作成功!, data={"id":"1","username":"小e","password":null,"email":"abc@163.com","gender":1})
R(code=200, msg=操作成功!, data=delete:{"email":"abc@163.com","gender":1,"id":"1","username":"小e"})
R(code=200, msg=操作成功!, data=delete:13617786374 ==> abc@163.com)
R(code=200, msg=操作成功!, data=map:{id=1, email=abc@163.com, username=小e})
R(code=200, msg=操作成功!, data="{\n \"userInfo\":{\n \"id\":\"1\",\n \"username\":\"小e\",\n \"mobile\":\"13617786374\",\n \"email\":\"abc@163.com\"\n }\n}")
R(code=200, msg=操作成功!, data=saveBatch:[{"email":"abc@163.com","gender":1,"id":"1","username":"小e"}])
```
#### 6.可选配置负载均衡
```ruby
# 兼容 ribbon 的配置方式
base-info:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
```
#### 7.可选请求相关配置
```ruby
okhttp:
connectTimeout: 3
readTimeout: 3
writeTimeout: 3
```
#### 8.指定转发到某个服务
```ruby
# 设置 url 属性
@Fetch(name = "my-test", url = "http://localhost:8081")
public interface SysUserInterface {
...
}
```
#### 9.指定请求匹配规则
```ruby
# 设置 pattern 属性
@Fetch(name = "my-test", pattern = "/test/**", url = "http://localhost:8081")
```