当前位置: 首页 > 科技观察

一种支持泛型分析的PHPScf无痕技术方案

时间:2023-03-13 03:44:09 科技观察

1背景介绍PHP调用Java提供的接口,需要进行代码转换,使用scf调用。目前有两种技术方案:架构组方案和安居客方案。架构组方案如下:右上图是转换代码需要填写的信息,左上图是整个接口调用需要的步骤,一共需要8个步骤转动。架构组的这个方案存在缺陷:1.步骤繁琐,耗时长,沟通成本高。2.泛型无法解析,需要另外开发一个不包含泛型的接口,比如XXXforPHP。3、一个接口涉及多个服务调用,本地调试时不能同时调试在线服务和沙盒服务。安居客的解决方案如下:右上图是代码转换工具的使用方法,左上图是实现接口调用的全部步骤,一共需要7个步骤。从架构组方案到安居客方案的迭代,相对减少了沟通成本,但也有其缺陷:1、步骤相对复杂,耗时。2.遇到泛型需要向服务器询问具体是什么泛型,无法自动解析。3、一个接口涉及多个服务调用,本地调试时不能同时调试在线服务和沙盒服务。4、转码为离线模式,不能同时下载多个服务。存在的问题如下:1.步骤多,耗时长,开发效率低。2.代码转换后,需要自己将代码复制到工程中的指定目录下(很多同学因为目录复制错误导致界面无法沟通)。3.泛型无法自动解析。4、当本地接口涉及多个服务时,不能同时调试在线服务和沙盒服务。2、重构思路从现有方案可以看出,PHP调用Java接口的步骤非常繁琐,开发效率低下。并且存在两个问题:无法解析泛型,不方便本地调试。因此需要重构来解决这些问题。重构应围绕以下三个方向进行:3过程分析围绕存在的问题进行重构:具体实施过程如下。第一步:实现代码工程管理。开发PHPbase服务解决Java代码下载问题。解析pom坐标信息。根据坐标拼接需要下载的jar包的URL,通过copyURLToFile将URL的信息下载到指定目录,即下载服务的jar包。解析下载的jar包。循环读取jar包信息,遇到pom.xml文件读取依赖信息,下载。有些依赖版本写在父pom的dependency中,需要通过父pom读取,还要考虑版本写在attribute中的情况。父pom的依赖信息也下载了。获取所有的jar包,放入一个URL数组中,然后通过URLClassLoader解析。遇到以.class结尾的文件,你能判断它是不是合约?实体?enum的流程图?如下:解析合约。对于注解中的serverContract,就是合约文件。通过反射获取类中包含的方法名,以及输入输出参数。参数的类型包括一些基本类型如string、int、bool等,也有一些复杂类型如map、list、set等,也可能是泛型的。对于map、list等复杂类型,会继续分析子元素的类型信息。如果是map类型,其子元素信息为key_type:XXX,value_type:XXX。如果子元素中的key_type是一个对象,那么它的key_class就是这个对象的包名。同样,如果子元素的value_type是一个对象,那么它的value_class就是这个对象的包名。list、set等复杂类型也是如此。如果是泛型,则为其elem_type赋值object,elem_class赋值java.lang.object。将方法名和哈希值(方法名+入参+出参)拼接起来,再拼接一个字符串params来表示入参。将该字段作为键,将输入参数的类型信息作为值存入变量paramVar。同理,方法名拼接一个hash值(方法名+入参+出参)再拼接一个字符串return代表出参。该字段作为键,输出参数的类型信息作为值。它也被放置在变量paramVar中。输入输出参数的具体信息见下图。最后将合约类的包名信息作为key,paramVar作为value放入变量contractMap中。解析实体。如果注解SCFSerializable=true,SCFMember=true,则为实体文件。通过SCFMember获取字段信息:var、orderID、generic,把这些信息放到变量TSPEC中,key就是orderId的值,value就是这些字段的信息。字段类型解析与合约中的类型解析一致,这里不再赘述。如果这个实体有继承关系,继承信息也应该放到变量TSPEC中。键为999,值为继承信息。如果注释SCFSerializable.name不为空,则将此值放入变量TSPEC。键为998,值为注解SCFSerializable.name的值。最后将实体类的包名信息作为key,TSPCE作为value放入entityMap中。解析枚举。如果isEnum=true,则为枚举文件。通过反射获取字段信息,即常量信息,放入enumMap中。key是枚举类的包名,value是各个常量值。返回结果。将contract、entity、enum这三个信息组装成Json并作为结果返回,如下图,也就是最下面的图。开发build.php,实现Java代码到PHP代码的转换,解决需要手动复制代码的问题。流程图如下:从PHPbase中获取坐标解析后的数据,即JSON数据,通过反向解析得到contract、entity、enum三个数组。下面三张图展示了这三个数组的一些具体内容:第一张图是截获的合约数组中的部分信息。key是一个完整的包名,value是这个类中所有的函数信息。我们来看第一种方法。key由方法名+hash值+params字符串组成。value是入参的类型信息。可以看到有string和object。如果类型为object,则有该对象对应的类信息。params为入参,带return的为输出参数信息。第二张图是截获的实体数组中的部分信息。我们看到key是一个完整的包名,value是实体的字段信息。比如第一个字段的排序是第一个,不是泛型,类型是list,子元素类型是object,elem_class就是这个对象的包名信息。998这个key对应的值就是Java注解的SCFSerializer.name的值。第三张图是截获的枚举数组中的部分信息。key是完整的包名,value是每个常量的值。分析完数组信息后,看具体的分析过程。合约数组解析流程图如下:获取合约数组并循环遍历。对于class文件的目录,我们使用包名+固定路径“libs/wscfcore/package”。为了防止和项目中已有的代码有差异,我们将代码放在libs/wscfcore/package下。命名空间也就是包名信息+刚才的固定路径。你可以看到右上角的图片。对class文件的依赖也包括两部分,一是固定依赖“uselibswscfcoreclient”,这是因为类中的构造方法使用了scfclient的实例化。依赖的另一部分是参数中使用的引用。生成方法需要考虑两个问题,一个是重名问题,一个是序列化问题。当方法重名时,我们用0和1来区分,比如getInfo0和getInfo1。这里有一个坑,就是每次生成的时候都要考虑方法的顺序,因为同名的方法中的参数个数不一样,所以一定不能改变名字的顺序。为了防止顺序改变问题,我们在生成Java代码时使用了一个哈希值。就是把方法名、入参、出参做一个hash值。这必须是唯一的,这用于确保顺序保持不变。在生成方法时,还要考虑第二个问题,即序列化问题。让我们看一下右上角的图。第四个参数的类型是非基本类型,所以序列化时这个类型对应的typeID也是hash值。从该类的包名中获取。这里通过创建一个全局数组,在后面生成typeID的时候获取到该参数的包名信息。$params该参数包含lookup、methodName、入参信息。实体数组解析的流程图如下:在生成实体的过程中,还会生成目录、命名空间和依赖项。这些和合约的分析一样,就不细说了。实体的构造方法,我们从右上图可以看出是给静态变量TSPEC赋值的过程。这个变量是一个数组,里面包含了各个字段的信息,名称var,序列orderID,是否泛型isGeneric,类型type。对于复杂类型列表,还会生成其子元素的类型。该类型信息在从服务端获取二进制流数据进行反序列化时会用到,类型必须与其一致才能被正确解析。如果实体类有继承,那么这个类的继承类也应该生成。enum数组的解析如下图所示:Enum的解析比较简单。生成目录、命名空间、常量信息。const信息可以遍历枚举数组。至此,Java代码到PHP代码的转换就完成了,并在指定位置生成。我们不再需要手动复制代码。避免了因为复制代码路径错误导致无法调整界面的问题。引入一个service.xml文件来管理服务版本信息。所有要下载的服务都写在这里。使用XML格式文件。开发同学可以直接复制这里的坐标信息,方便开发同学使用。第二步:解决泛型解析问题。Java同学知道泛型对应什么类型,也就是自己开发的实体之一,但是PHP同学不知道。但是我们可以得到泛型序列化后的typeID值,也就是hashcode值,然后我们对所有的实体做一个hash计算,然后倒过来就能知道泛型是哪个实体了?所以优化build.php文件,收集服务实体和枚举信息,进行hash计算。我们现在有两个协议scfv1和scfv3,不同的协议对应的typeID不同,所以对这两个协议进行hash计算。对于scfv1协议,如果实体中有scheme域(998),则执行scheme域。如果不是,则对类名进行哈希计算。对于scfv3协议,计算整个包名称。使用此哈希文件,可以解析泛型。第三步:解决本地无法同时调试在线和沙盒服务的问题。添加沙盒配置文件,方便开发者调试。文件如下图所示:服务名和沙箱IP要准确匹配。底层在获取服务信息的时候,判断有这样一个文件,获取到服务信息后,就可以直接去沙箱服务了。路由此文件中不存在的服务。如果把代码放在沙盒环境下,灰度应用可以用这个文件代替,免去应用操作。第四步:将步骤从8步减为3步,将Javapom坐标信息放入service.xml文件中。执行构建文件,响应信息如下图所示。从service.xml中获取坐标信息,查看服务是否已经下载。如果没有下载,拖拽PHPbase拉取Java代码,然后转成PHP代码,放到项目中指定位置。接口调用。使用反射机制实例化调用类,然后调用类中的方法。下图是IHouseService类的内容。可以看到这个类的实例化需要传入服务名serviceName,查找类lookup,分别对应上图中的hmc和HouseService4。无痕调用从群方案迭代到租房方案,调用步骤从原来的八步到现在的三步。租用程序实现代码的目录结构如下所示:wscfcore目录包含了所有scf相关的文件。Hashdata目录包含散列文件。package.lock存储了所有项目中目前已经下载的服务坐标信息,并以字符串的形式存储。包目录包含所有下载的代码。Build文件是整个代码的核心。Java代码根据坐标拉取然后转换成PHP代码放在指定位置。service.xml文件是存放我们要下载的服务的pom坐标。xml格式方便您直接复制粘贴坐标信息。wscfcore目录会放在composer中,供大家下载使用。当PHP同学使用这套代码调用某个服务的接口时,体会到PHP同学调用Java接口就像Java同学调用Java接口一样,属于无痕调用。