從0 開始手寫一個 RPC 框架,大多數都不清楚的技術
RPC 框架底層到底什么原理得知了RPC(遠程過程調用)簡單來說就是調用遠程的服務就像調用本地方法一樣,其中用到的知識有序列化和反序列化、動態代理、網絡傳輸、動態加載、反射這些知識點。

發現這些知識都了解一些。所以就想著試試自己實現一個簡單的RPC框架,即鞏固了基礎的知識,也能更加深入的了解RPC原理。
當然一個完整的RPC框架包含了許多的功能,例如服務的發現與治理,網關等等,本篇只是簡單的實現了一個調用的過程。
傳參出參分析
一個簡單請求可以抽象為兩步

那么就根據這兩步進行分析,在請求之前我們應該發送給服務端什么信息?而服務端處理完以后應該返回客戶端什么信息?
在請求之前我們應該發送給服務端什么信息?
由于我們在客戶端調用的是服務端提供的接口,所以我們需要將客戶端調用的信息傳輸過去,那么我們可以將要傳輸的信息分為兩類
第一類是服務端可以根據這個信息找到相應的接口實現類和方法
第二類是調用此方法傳輸的參數信息
那么我們就根據要傳輸的兩類信息進行分析,什么信息能夠找到相應的實現類的相應的方法?要找到方法必須要先找到類,這里我們可以簡單的用Spring提供的Bean實例管理ApplicationContext進行類的尋找。
所以要找到類的實例只需要知道此類的名字就行,找到了類的實例,那么如何找到方法呢?
在反射中通過反射能夠根據方法名和參數類型從而找到這個方法。那么此時第一類的信息我們就明了了,那么就建立相應的是實體類存儲這些信息。
- @Data
- public class Request implements Serializable {
- private static final long serialVersionUID = 3933918042687238629L;
- private String className;
- private String methodName;
- private Class<?> [] parameTypes;
- private Object [] parameters;
- }
服務端處理完以后應該返回客戶端什么信息?
上面我們分析了客戶端應該傳輸什么信息給服務端,那么服務端處理完以后應該傳什么樣的返回值呢?
這里我們只考慮最簡單的情況,客戶端請求的線程也會一直在等著,不會有異步處理這一說,所以這么分析的話就簡單了,直接將得到的處理結果返回就行了。
- @Data
- public class Response implements Serializable {
- private static final long serialVersionUID = -2393333111247658778L;
- private Object result;
- }
由于都涉及到了網絡傳輸,所以都要實現序列化的接口
如何獲得傳參信息并執行?-客戶端
上面我們分析了客戶端向服務端發送的信息都有哪些?那么我們如何獲得這些信息呢?
首先我們調用的是接口,所以我們需要寫自定義注解然后在程序啟動的時候將這些信息加載在Spring容器中。
有了這些信息那么我們就需要傳輸了,調用接口但是實際上執行的確實網絡傳輸的過程,所以我們需要動態代理。那么就可以分為以下兩步
- 初始化信息階段:將key為接口名,value為動態接口類注冊進Spring容器中
- 執行階段:通過動態代理,實際執行網絡傳輸
初始化信息階段
由于我們使用Spring作為Bean的管理,所以要將接口和對應的代理類注冊進Spring容器中。而我們如何找到我們想要調用的接口類呢?我們可以自定義注解進行掃描。將想要調用的接口全部注冊進容器中。
創建一個注解類,用于標注哪些接口是可以進行Rpc的。
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface RpcClient {
- }
然后創建對于@RpcClient注解的掃描類RpcInitConfig,將其注冊進Spring容器中。
- public class RpcInitConfig implements ImportBeanDefinitionRegistrar{
- @Override
- public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
- ClassPathScanningCandidateComponentProvider provider = getScanner();
- //設置掃描器
- provider.addIncludeFilter(new AnnotationTypeFilter(RpcClient.class));
- //掃描此包下的所有帶有@RpcClient的注解的類
- Set<BeanDefinition> beanDefinitionSet = provider.findCandidateComponents("com.example.rpcclient.client");
- for (BeanDefinition beanDefinition : beanDefinitionSet){
- if (beanDefinition instanceof AnnotatedBeanDefinition){
- //獲得注解上的參數信息
- AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
- String beanClassAllName = beanDefinition.getBeanClassName();
- Map<String, Object> paraMap = annotatedBeanDefinition.getMetadata()
- .getAnnotationAttributes(RpcClient.class.getCanonicalName());
- //將RpcClient的工廠類注冊進去
- BeanDefinitionBuilder builder = BeanDefinitionBuilder
- .genericBeanDefinition(RpcClinetFactoryBean.class);
- //設置RpcClinetFactoryBean工廠類中的構造函數的值
- builder.addConstructorArgValue(beanClassAllName);
- builder.getBeanDefinition().setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
- //將其注冊進容器中
- registry.registerBeanDefinition(
- beanClassAllName ,
- builder.getBeanDefinition());
- }
- }
- }
- //允許Spring掃描接口上的注解
- protected ClassPathScanningCandidateComponentProvider getScanner() {
- return new ClassPathScanningCandidateComponentProvider(false) {
- @Override
- protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
- return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
- }
- };
- }
- }
由于上面注冊的是工廠類,所以我們建立一個工廠類RpcClinetFactoryBean繼承Spring中的FactoryBean類,由其統一創建@RpcClient注解的代理類。推薦閱讀:Spring零配置之@Configuration注解詳解。
- @Data
- public class RpcClinetFactoryBean implements FactoryBean {
- @Autowired
- private RpcDynamicPro rpcDynamicPro;
- private Class<?> classType;
- public RpcClinetFactoryBean(Class<?> classType) {
- this.classType = classType;
- }
- @Override
- public Object getObject(){
- ClassLoader classLoader = classType.getClassLoader();
- Object object = Proxy.newProxyInstance(classLoader,new Class<?>[]{classType},rpcDynamicPro);
- return object;
- }
- @Override
- public Class<?> getObjectType() {
- return this.classType;
- }
- @Override
- public boolean isSingleton() {
- return false;
- }
- }
注意此處的getObjectType方法,在將工廠類注入到容器中的時候,這個方法返回的是什么Class類型那么注冊進容器中就是什么Class類型。
然后看一下我們創建的代理類RpcDynamicPro。
- @Component
- @Slf4j
- public class RpcDynamicPro implements InvocationHandler {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- String requestJson = objectToJson(method,args);
- Socket client = new Socket("127.0.0.1", 20006);
- client.setSoTimeout(10000);
- //獲取Socket的輸出流,用來發送數據到服務端
- PrintStream out = new PrintStream(client.getOutputStream());
- //獲取Socket的輸入流,用來接收從服務端發送過來的數據
- BufferedReader buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
- //發送數據到服務端
- out.println(requestJson);
- Response response = new Response();
- Gson gson =new Gson();
- try{
- //從服務器端接收數據有個時間限制(系統自設,也可以自己設置),超過了這個時間,便會拋出該異常
- String responsJson = buf.readLine();
- response = gson.fromJson(responsJson, Response.class);
- }catch(SocketTimeoutException e){
- log.info("Time out, No response");
- }
- if(client != null){
- //如果構造函數建立起了連接,則關閉套接字,如果沒有建立起連接,自然不用關閉
- client.close(); //只關閉socket,其關聯的輸入輸出流也會被關閉
- }
- return response.getResult();
- }
- public String objectToJson(Method method,Object [] args){
- Request request = new Request();
- String methodName = method.getName();
- Class<?>[] parameterTypes = method.getParameterTypes();
- String className = method.getDeclaringClass().getName();
- request.setMethodName(methodName);
- request.setParameTypes(parameterTypes);
- request.setParameters(args);
- request.setClassName(getClassName(className));
- GsonBuilder gsonBuilder = new GsonBuilder();
- gsonBuilder.registerTypeAdapterFactory(new ClassTypeAdapterFactory());
- Gson gson = gsonBuilder.create();
- return gson.toJson(request);
- }
- private String getClassName(String beanClassName){
- String className = beanClassName.substring(beanClassName.lastIndexOf(".")+1);
- className = className.substring(0,1).toLowerCase() + className.substring(1);
- return className;
- }
- }
我們的客戶端已經寫完了,傳給服務端的信息我們也已經拼裝完畢了。剩下的工作就簡單了,開始編寫服務端的代碼。
服務端處理完以后應該返回客戶端什么信息?-服務端
服務端的代碼相比較客戶端來說要簡單一些。可以簡單分為下面三步
- 拿到接口名以后,通過接口名找到實現類
- 通過反射進行對應方法的執行
- 返回執行完的信息
那么我們就根據這三步進行編寫代碼
拿到接口名以后,通過接口名找到實現類
如何通過接口名拿到對應接口的實現類呢?這就需要我們在服務端啟動的時候將其對應信息加載進去。
- @Component
- @Log4j
- public class InitRpcConfig implements CommandLineRunner {
- @Autowired
- private ApplicationContext applicationContext;
- public static Map<String,Object> rpcServiceMap = new HashMap<>();
- @Override
- public void run(String... args) throws Exception {
- Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(Service.class);
- for (Object bean: beansWithAnnotation.values()){
- Class<?> clazz = bean.getClass();
- Class<?>[] interfaces = clazz.getInterfaces();
- for (Class<?> inter : interfaces){
- rpcServiceMap.put(getClassName(inter.getName()),bean);
- log.info("已經加載的服務:"+inter.getName());
- }
- }
- }
- private String getClassName(String beanClassName){
- String className = beanClassName.substring(beanClassName.lastIndexOf(".")+1);
- className = className.substring(0,1).toLowerCase() + className.substring(1);
- return className;
- }
- }
此時rpcServiceMap存儲的就是接口名和其對應的實現類的對應關系。
通過反射進行對應方法的執行
此時拿到了對應關系以后就能根據客戶端傳過來的信息找到相應的實現類中的方法。然后進行執行并返回信息就行。
- public Response invokeMethod(Request request){
- String className = request.getClassName();
- String methodName = request.getMethodName();
- Object[] parameters = request.getParameters();
- Class<?>[] parameTypes = request.getParameTypes();
- Object o = InitRpcConfig.rpcServiceMap.get(className);
- Response response = new Response();
- try {
- Method method = o.getClass().getDeclaredMethod(methodName, parameTypes);
- Object invokeMethod = method.invoke(o, parameters);
- response.setResult(invokeMethod);
- } catch (NoSuchMethodException e) {
- log.info("沒有找到"+methodName);
- } catch (IllegalAccessException e) {
- log.info("執行錯誤"+parameters);
- } catch (InvocationTargetException e) {
- log.info("執行錯誤"+parameters);
- }
- return response;
- }
現在我們兩個服務都啟動起來并且在客戶端進行調用就發現只是調用接口就能調用過來了。
總結
到現在一個簡單的RPC就完成了,但是其中還有很多的功能需要完善,例如一個完整RPC框架肯定還需要服務注冊與發現,而且雙方通信肯定也不能是直接開啟一個線程一直在等著,肯定需要是異步的等等的各種功能。
后面隨著學習的深入,這個框架也會慢慢增加一些東西。不僅是對所學知識的一個應用,更是一個總結。有時候學一個東西學起來覺得很簡單,但是真正應用的時候就會發現各種各樣的小問題。
比如在寫這個例子的時候碰到一個問題就是@Autowired的時候一直找不到SendMessage的類型,最后才發現是工廠類
- public Class<?> getObjectType() {
- return this.getClass();;
- }
這樣的話注冊進容器的就是RpcClinetFactoryBean類型的而不是SendMessage的類型。

























