手写RPC通信和服务发现——模拟Dubbo的实现机制(下篇)

接上篇。

服务端如何将某些指定的方法暴露出去,比如服务端有:
类A,方法A1()
类B,方法B1()、B2()
类C,方法C1()

我们想把类中的方法暴露一部分出来,供客户端调用,如:
A :暴露,方法A1():暴露
B:暴露,方法B1():暴露,方法B2():不暴露
类C:不暴露,方法C1()
怎么才能做到动态方便的进行设置?
这种情况下,注解方式则派上了用场。

注解定义

我们编写2个注解,1个为类注解,表示该类暴露给客户端;另一个为方法注解,表示该方法暴露给客户端。

类级别的注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.leehao.rpc.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @ClassName RpcClazz
* @Description RpcClazz
* @Author lihao
* @Date 2019/9/25 14:58
* @Version 1.0
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE )
public @interface RpcClazz {
}
JAVA

方法级别的注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.leehao.rpc.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @ClassName RpcClazz
* @Description RpcClazz
* @Author lihao
* @Date 2019/9/25 14:58
* @Version 1.0
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD )
public @interface RpcMethod {
}
JAVA

注解解析

具体步骤为:
1、传入包名,得到all业务类,得到文件名
2、通过反射得到类
3、解析类是否有注解
4、解析方法是否有注解
5、发布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.leehao.rpc.utils;

import com.leehao.rpc.anno.RpcClazz;
import com.leehao.rpc.anno.RpcMethod;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @ClassName AnnoUtil
* @Description AnnoUtil
* @Author lihao
* @Date 2019/9/25 15:02
* @Version 1.0
**/
public class AnnoUtil {


//传入包名packageName
//返回多个类 Map表示
//一个类、多个方法
//一个方法,多个参数
//所以最后返回结果为:Map<String, List<Map<String,List<String>>>>
public static Map<String, List<Map<String,List<String>>>> parseAnno(String packageName) throws Exception {
Map<String, List<Map<String,List<String>>>> map = new HashMap<>();
//获取项目跟路径
String basePath = AnnoUtil.class.getResource("/").getPath();
//获取当前包所在的路径,将.替换为/
File file = new File(basePath + packageName.replace(".", "/"));
//获取包路径下所有的文件(严格讲,需要获取.class后缀的文件)
String[] names = file.list();
for (String name : names) {
//去掉后缀,拿到类名
name = name.replaceAll(".class", "");
//通过反射获取该类
Class<?> clazz = Class.forName(packageName + "." + name);
//是否有类级别的注解
if (clazz.isAnnotationPresent(RpcClazz.class)) {
List<Map<String,List<String>>> methodsReturn = new ArrayList<>();
//通过反射获取所有方法,包括私有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//判断方法是否有注解
if (method.isAnnotationPresent(RpcMethod.class)){
Map<String,List<String>> methodReturn = new HashMap<>();
List<String> paras = new ArrayList<>();
//遍历方法类型
Class<?>[] paraTypes = method.getParameterTypes();
for (Class<?> paraType:paraTypes){
//将变量类型加入list中
paras.add(paraType.getSimpleName());
}
//将该方法中所有的参数类型加入map中,其中key为方法名
methodReturn.put(method.getName(),paras);
//将方法都加入到list中去
methodsReturn.add(methodReturn);
}

}
//将该类下的所有暴露方法都加入到map中,其中key为包全名
map.put(packageName+"."+ name,methodsReturn);
}
}
//返回最终结果
return map;
}
}
JAVA

编写几个类和方法,加入我们的注解来测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.leehao.rpc.server.dao;

import com.leehao.rpc.anno.RpcClazz;
import com.leehao.rpc.anno.RpcMethod;

/**
* @ClassName OrderDao
* @Description OrderDao
* @Author lihao
* @Date 2019/9/24 18:58
* @Version 1.0
**/
@RpcClazz
public class OrderDao {
@RpcMethod
public void query(String name){
System.out.println("name="+name);
}
@RpcMethod
public void query1(String name1,String name2){
System.out.println("name="+name1+name2);
}
@RpcMethod
public void query2(int name){
System.out.println("name="+name);
}

public void query3(int name){
System.out.println("name="+name);
}
}
JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.leehao.rpc.server.dao;

import com.leehao.rpc.anno.RpcClazz;
import com.leehao.rpc.anno.RpcMethod;

/**
* @ClassName UserDao
* @Description UserDao
* @Author lihao
* @Date 2019/9/24 18:58
* @Version 1.0
**/
@RpcClazz
public class UserDao {
@RpcMethod
public void sayHello(String name){
System.out.println("name="+name);
}
@RpcMethod
public void sayBye(String name1,String name2){
System.out.println("name="+name1+name2);
}

}
JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.leehao.rpc.server.dao;

import com.leehao.rpc.anno.RpcClazz;
import com.leehao.rpc.anno.RpcMethod;

/**
* @ClassName UserDao
* @Description UserDao
* @Author lihao
* @Date 2019/9/24 18:58
* @Version 1.0
**/
public class OrgDao {
@RpcMethod
public void test(){
System.out.println("name");
}

}
JAVA

预期

  1. OrderDao类有注解,query、query1、query2方法有注解:
    所以query、query1、query2暴露,query3不暴露出来。
  2. UserDao类有注解,sayHello、sayBye有注解:
    所以2个均暴露出来。
  3. OrgDao类没有注解,所以下面的方法无论是否加注解,均无法暴露。

测试

在这里我们采用SpringBoot作为web容器,调用上述的接口,来将结果显示到页面上,如对springboot了解不够,可自行学习,默认大家都了解Maven、springboot等相关知识。

pom中引入依赖:

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
XML

编写Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.leehao.rpc.controller;

import com.leehao.rpc.utils.AnnoUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;

/**
* @ClassName RegistryController
* @Description RegistryController
* @Author lihao
* @Date 2019/9/25 15:36
* @Version 1.0
**/
@Controller
@RequestMapping("/")
public class RegistryController {

@ResponseBody
@GetMapping("/getInstance")
public Map<String, List<Map<String, List<String>>>> getInstance(@RequestParam("packageName") String packageName) throws Exception {
return AnnoUtil.parseAnno(packageName);
}
}
JAVA

启动类启动

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.leehao.rpc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RpcServerApplication {

public static void main(String[] args) {
SpringApplication.run(RpcServerApplication.class, args);
}

}
JAVA

在浏览器地址栏输入(端口号根据实际情况而定):
http://localhost:8765/getInstance?packageName=com.leehao.rpc.server.dao  
即可发现已暴露的所有接口方法:


和我们的预期是一致的,至此服务注册和发现已完成。


手写RPC通信和服务发现——模拟Dubbo的实现机制(下篇)
https://leehoward.cn/2019/11/27/手写RPC通信和服务发现——模拟Dubbo的实现机制(下篇)/
作者
lihao
发布于
2019年11月27日
许可协议