RPC框架都看不懂。RPC框架的底层原理是什么?知道RPC(RemoteProcedureCall)就是像调用本地方法一样调用远程服务。获得的知识包括序列化和反序列化、动态代理、网络传输、动态加载和反射。发现其中的一些知识。于是想自己尝试实现一个简单的RPC框架,既巩固了基础知识,又对RPC原理有了更深的理解。当然,一个完整的RPC框架包括服务发现和管理、网关等很多功能,本文简单实现一个调用过程。传递参数和输出参数分析一个简单的请求可以抽象为两步,那么根据这两步来分析,在请求之前我们应该向服务器发送哪些信息呢?而服务端处理后应该返回给客户端什么信息呢?在请求之前,我们应该向服务器发送什么信息?由于我们在客户端调用的是服务端提供的接口,所以我们需要将客户端调用的信息传递给它。那么我们可以将要传输的信息分为两类。第一类是服务端可以根据这些信息找到对应的接口实现类和方法。第二类是调用该方法传递的参数信息。那么我们根据要传输的两类信息来分析。有什么资料可以找到对应实现类的对应方法呢??要找到方法,必须先找到类。这里我们可以简单的使用Spring提供的Bean实例来管理ApplicationContext来搜索类。所以要找到一个类的实例,只需要知道类的名字即可。找到类的实例后,如何找到方法呢?在反射中,可以根据方法名和参数类型找到方法。那么此时我们就了解第一类的信息,然后创建相应的实体类来存储这些信息。@DatapublicclassRequestimplementsSerializable{privatestaticfinallongserialVersionUID=3933918042687238629L;privateStringclassName;privateStringmethodName;privateClass>[]parameTypes;privateObject[]parameters;}服务端处理后应该返回给客户端什么信息?上面我们分析了客户端应该向服务器发送什么信息,那么服务器处理后应该发送什么样的返回值呢?这里我们只考虑最简单的情况,客户端请求的线程会一直等待,不会存在异步处理的情况,所以如果我们这样分析就简单了,直接返回获取到的处理结果即可。@DatapublicclassResponseimplementsSerializable{privatestaticfinallongserialVersionUID=-2393333111247658778L;privateObjectresult;}因为都涉及网络传输,如何获取并执行序列化接口?-Client上面我们分析了客户端向服务端发送的信息有哪些?那么我们如何获得这些信息呢?首先我们调用的是接口,所以需要写自定义注解,然后在程序启动的时候在Spring容器中加载这些信息。有了这些信息,我们就需要进行传输,调用接口但实际执行网络传输的过程,所以我们需要一个动态代理。那么可以分为以下两个初始化信息阶段:key是接口名,value是在Spring容器中注册为动态接口类。执行阶段:通过动态代理,真正执行网络传输初始化信息阶段是因为我们使用Spring作为Bean管理,所以将接口和对应的代理类注册到Spring容器中。而我们如何找到我们要调用的接口类呢?我们可以使用自定义注释进行扫描。将所有要调用的接口注册到容器中。创建一个注解类来标记哪些接口可以用于Rpc。@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public@interfaceRpcClient{}然后创建一个用@RpcClient注解的扫描类RpcInitConfig注册到Spring容器中。publicclassRpcInitConfigimplementsImportBeanDefinitionRegistrar{@OverridepublicvoidregisterBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry){ClassPathScanningCandidateComponentProviderprovider=getScanner();//设置扫描器provider.addIncludeFilter(newAnnotationTypeFilter(RpcClient.class));//扫描此包下的所有带有@RpcClient的注解的类SetbeanDefinitionSet=provider.findCandidateComponents("com.example.rpcclient.client");for(BeanDefinitionbeanDefinition:beanDefinitionSet){if(beanDefinitioninstanceofAnnotatedBeanDefinition){//获取annotatedBeanDefinition上的参数信息annotatedBeanDefinition=fin(AnnotatedBeanDefinition=Finition;AllbeanDefinition)StringDefinition).getBeanClassName();MapparaMap=annotatedBeanDefinition.getMetadata().getAnnotationAttributes(RpcClient.class.getCanonicalName());//将RpcClient的工厂类注册到BeanDefinitionBuilderbuilder=BeanDefinitionBuilder.genericBeanDefinition(RpcClientFactoryBean.class);//在RpcClinetFactoryBean工厂类中设置构造函数的值,builder.getBeanDefinition());}}}//允许Spring扫描接口上的注解protectedClassPathScanningCandidateComponentProvidergetScanner(){returnnewClassPathScanningCandidateComponentProvider(false){@OverrideprotectedbooleanisCandidateComponent(AnnotatedBeanDefinitionbeanDefinition){returnbeandatadefinition().isIndependent();}};}}因为上面注册了工厂类,所以我们创建一个工厂类RpcClinetFactoryBean来继承Spring中的FactoryBean类,统一创建一个注解@RpcClient的代理类。推荐阅读:Spring零配置注解之@Configuration详解。@DatapublicclassRpcClinetFactoryBeanimplementsFactoryBean{@AutowiredprivateRpcDynamicProrpcDynamicPro;privateClass>classType;publicRpcClinetFactoryBean(Class>classType){this.classType=classType;}@OverridepublicObjectgetObject(){ClassLoaderclassLoader=classType.getClassLoaderProjectLoaderxy();>[]{classType},rpcDynamicPro);returnobject;}@OverridepublicClass>getObjectType(){returnthis.classType;}@OverridepublicbooleanisSingleton(){returnfalse;}}注意这里的getObjectType方法,注入工厂的时候class在进入容器的时候,这个方法返回的是什么类型的Class,那么就是把什么类型的Class注册到容器中。再看看我们创建的代理类RpcDynamicPro。@Component@Slf4jpublicclassRpcDynamicProimplementsInvocationHandler{@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{StringrequestJson=objectToJson(method,args);Socketclient=newSocket("127.0.0.1",200006);out(0Time)得到的输出流Socket向服务器发送数据PrintStreamout=newPrintStream(client.getOutputStream());//获取Socket的输入流以接收服务器发送的数据BufferedReaderbuf=newBufferedReader(newInputStreamReader(client.getInputStream()));//向服务端发送数据out.println(requestJson);Responseresponse=newResponse();Gsongson=newGson();try{//从服务端接收数据有时间限制(系统设置,也可以自己设置),这个时候之后会抛出异常);}if(client!=null){//如果构造函数建立连接ion、关闭套接字。如果没有建立连接,则不需要关闭client.close();//只关闭socket,其关联的输入输出流也会关闭}returnresponse.getResult();}publicStringobjectToJson(Methodmethod,Object[]args){请求请求=newRequest();StringmethodName=method.getName();Class>[]parameterTypes=method.getParameterTypes();StringclassName=method.getDeclaringClass().getName();request.setMethodName(methodName);request.setParameterTypes(parameterTypes);request.setParameters(args);request.setClassName(getClassName(className));GsonBuildergsonBuilder=newGsonBuilder();gsonBuilder.registerTypeAdapterFactory(newClassTypeAdapterFactory());Gsongson=gsonBuilder.create();returngson.toJson(请求);}privateStringgetClassName(StringbeanClassName){StringclassName=beanClassName.substring(beanClassName.lastIndexOf(".")+1);className=className.substring(0,1).toLowerCase()+className.substring(1);returnclassName;}}我们客户端写好了,发给服务端的信息也组装好了,剩下的工作就简单了,开始写服务端的代码。服务端处理后应该返回给客户端什么信息?-服务器的代码比客户端的代码简单。可以简单分为以下三个步骤。拿到接口名后,通过接口名找到实现类。通过反射执行对应的方法,执行后返回信息。那么我们就按照这三个步骤来写代码。获取接口名后,使用接口名查找实现类如何通过接口名获取对应接口的实现类?这就需要我们在服务器启动的时候加载相应的信息进去。@Component@Log4jpublicclassInitRpcConfigimplementsCommandLineRunner{@AutowiredprivateApplicationContextapplicationContext;publicstaticMaprpcServiceMap=newHashMap<>();@Overridepublicvoidrun(String...args)throwsException{MapbeansWithAnnotation=applicationServiceContext.getBeansWithAnclassfor(Objectbean:beansWithAnnotation.values()){Class>clazz=bean.getClass();Class>[]interfaces=clazz.getInterfaces();for(Class>inter:interfaces){rpcServiceMap。put(getClassName(inter.getName()),bean);log.info("加载服务:"+inter.getName());}}}privateStringgetClassName(StringbeanClassName){StringclassName=beanClassName.substring(beanClassName.lastIndexOf(".")+1);className=className.substring(0,1).toLowerCase()+className.substring(1);returnclassName;}}此时rpcServiceMap中存储的是接口名与其对应的实现类的对应关系关系。通过反射执行相应的方法。这时候拿到对应关系后,就可以根据客户端发来的信息在实现类中找到对应的方法了。然后执行并返回信息。publicResponseinvokeMethod(Requestrequest){StringclassName=request.getClassName();StringmethodName=request.getMethodName();Object[]parameters=request.getParameters();Class>[]parameTypes=request.getParamTypes();Objecto=InitRpcConfig.rpcServiceMap.get(className);Responseresponse=newResponse();try{Methodmethod=o.getClass().getDeclaredMethod(methodName,parameTypes);ObjectinvokeMethod=method.invoke(o,parameters);response.setResult(invokeMethod);}catch(NoSuchMethodExceptione){log.info("Notfound"+methodName);}catch(IllegalAccessExceptione){log.info("执行错误"+参数);}catch(InvocationTargetExceptione){log.info("执行错误"+parameters);}returnresponse;}现在我们两个服务都启动了,我们在客户端调用它们,发现只要调用接口就可以调用了。综上所述,一个简单的RPC到此就完成了,但是还有很多功能需要完善。比如一个完整的RPC框架,肯定还需要服务注册和发现,双方的通信一定不能直接开一个线程等待。肯定需要各种异步的功能等等。后面随着学习的深入,这个框架会逐渐加入一些东西。它既是所学知识的应用,又是总结。有时候学点东西很容易,但是真正去应用的时候,就会发现各种小问题。比如我在写这个例子的时候遇到了一个问题,就是我在@Autowired的时候找不到SendMessage的类型,最后发现是一个工厂类publicClass>getObjectType(){returnthis.getClass();;}如果注册了这个,就进入容器是RpcClinetFactoryBean类型而不是SendMessage类型。