推迟对远程对象的调用请求的制作方法

文档序号:18744692发布日期:2019-09-21 02:11阅读:149来源:国知局
推迟对远程对象的调用请求的制作方法

基于云的服务通常通过经由web浏览器执行的应用来提供。为了开发这样的应用,程序员可以开发应用,该应用将由客户端的web浏览器执行并且访问云数据中心的服务器的服务。应用(或客户端代码)通常提供用户接口,用户可以通过该用户接口访问应用的特征。例如,如果应用是文字处理器,则应用显示正在编辑的文档的内容、用于访问文字处理器的特征的菜单栏(例如,打开文档和插入脚注)等。正在编辑的文档存储在服务器上。因此,当用户请求打开文档时,应用向服务器发送打开请求并且接收文档的至少一部分的副本。应用显示文档的内容。当用户对文档进行改变时,应用可以更新所显示的内容并且向服务器发送消息。然后,服务器改变文档。客户端与服务器之间的通信通常基于表述性状态转移(“REST”)或RESTful模型,诸如超文本传输协议(“HTTP”)请求消息(例如,GET方法)和响应消息。

这样的应用通常用JavaScript编写,因为大多数浏览器都支持JavaScript应用的执行。相反,出于提高效率的原因,服务器代码通常用除了JavaScript之外的编程语言编写,例如C#或C++,因为JavaScript是一种解释型语言,而C#和C++是用可执行文件编译的。JavaScript以及C#和C++是面向对象的编程语言。面向对象的编程语言支持其中程序指定定义可以实例化的对象类型的类的编程模型。类定义该类的对象的数据成员和方法(也称为成员函数)。每个方法具有指定方法的名称的签名、要传递给方法的输入参数的类型、以及由方法返回的输出参数的类型。例如,文档类可以被定义为包括用于打开文档、修改文档和保存文档的方法。在应用的执行期间,在实例化某个类的对象之后,应用可以通过指定对对象的引用、调用方法的指示、以及要传递给方法的输入参数(如果有)来调用对象的方法。在方法完成时,该方法返回输出参数(如果有)。

这样的应用历来使用由Web服务描述语言(“WSDL”)定义的web服务接口或使用某种其他类型的远程过程调用(“RPC”)机制来访问服务器的服务。不幸的是,从这样的应用到服务器,对面向对象的RPC(也称为远程方法调用(“RMI”))的支持很少。因此,在访问由服务器托管的对象时,这样的应用通常不使用面向对象的方法。



技术实现要素:

提供了一种用于由客户端执行的应用调用由服务器托管的远程对象类的远程对象的远程对象方法的系统。应用实例化包括代理成员函数的代理类的代理,该代理成员函数的签名与远程对象成员函数相同。应用将对象标识符与代理相关联。应用调用代理的代理成员函数。在所调用的代理成员函数的控制下,应用向服务器发送调用请求消息,调用请求消息包括与代理相关联的对象标识符和远程对象成员函数的标识符。应用依赖于由浏览器提供的功能,而无需浏览器在执行应用时访问附加功能。

提供了一种用于在客户端的应用的代理与服务器的对应远程对象之间同步属性值的系统。应用实例化与远程对象类的远程对象相对应的代理类的代理,其中代理类指定具有代理获取器的属性。应用向服务器发送用于调用远程对象的远程对象成员函数的调用请求消息,并且接收调用响应消息。当调用响应消息包括属性更新时,应用从调用响应消息中提取来自属性更新的属性的值,并且将所提取的值存储在代理中。当调用属性的代理获取器时,可以从代理检索属性的值,而不必向服务器发送调用请求消息。

提供了一种用于在客户端处执行的应用向服务器的远程对象发送调用请求的系统。当应用调用与远程对象类的远程对象相对应的应用的代理类的代理时,生成每个调用请求。对于每个调用请求,当调用请求可推迟时,系统存储调用请求。当调用请求不可推迟时,系统向服务器发送包括先前未发送的每个存储的调用请求和当前调用请求的调用请求消息。系统从服务器接收调用响应消息。对于所接收的调用响应消息的每个调用响应,系统向应用提供调用响应已经被接收到的指示。

提供本“发明内容”是为了以简化的形式介绍一些概念,这些概念将在下面的“具体实施方式”中进一步描述。本“发明内容”不旨在标识所要求保护的主题的关键特征或必要特征,也不旨在用于限制所要求保护的主题的范围。

附图说明

图1是示出一些实施例中的应用和服务器组件的数据结构的框图。

图2是示出一些实施例中的同步调用的代理的方法的处理的流程图。

图3是示出一些实施例中的服务器组件的接收请求组件的处理的流程图。

图4是示出一些实施例中的用于客户端组件的响应对象的响应对象类的提取参数方法的处理的流程图。

图5是示出一些实施例中的用于服务器组件的响应对象的响应对象类的存储参数方法的处理的流程图。

图6是示出一些实施例中的ID表对象类的检索ID方法的处理的流程图。

图7是示出一些实施例中的ID表对象的检索引用方法的处理的流程图。

图8是示出一些实施例中的传递引用的ID表对象的添加条目方法的处理的流程图。

图9是示出一些实施例中的传递条目的ID表对象的添加条目方法的处理的流程图。

图10是示出一些实施例中的支持远程对象的对等托管的OORPC系统的实现的框图。

图11是示出一些实施例中的支持OORPC系统对属性值的同步的数据结构的框图。

图12是示出一些实施例中的服务器组件的获取属性值组件的处理的流程图。

图13是示出一些实施例中的客户端组件的存储属性值组件的处理的流程图。

图14是示出一些实施例中的用于为代理类自动生成代码的OORPC系统的代码生成器的流程图。

图15是示出一些实施例中的代码生成器的进程代理属性组件的处理的流程图。

图16是示出一些实施例中的客户端组件的发送请求组件的处理的流程图。

图17是示出一些实施例中的服务器组件的接收请求组件的处理的流程图。

具体实施方式

提供了一种用于为由客户端执行的应用自动生成代码以访问由服务器或其他远程设备以面向对象的方式远程托管的对象的方法和系统。术语“客户端”指的是计算设备,并且术语“服务器”指的是计算设备。在一些实施例中,面向对象的远程过程调用(“OORPC”)系统输入用于由服务器远程托管的对象(“远程对象”)的远程对象类的接口的定义。对于每个远程对象类,OORPC系统自动生成用作远程对象类的代理的代理类。“代理”是一种对象,其方法主要向托管对应的远程对象的服务器发送调用请求消息。代理与远程对象之间通常存在一对一的对应关系。代理类具有与对应的远程对象类相同的接口(例如,相同的方法和方法签名),但是具有与远程对象类的代码不同的代码。对于代理类的每个方法,OORPC系统为该方法生成代码,该代码在被调用时向托管与代理类的代理相对应的远程对象的服务器发送调用请求消息。调用请求消息标识远程对象、方法以及传递给方法的任何输入参数。OORPC系统还为该方法生成代码,在从远程设备接收到调用响应消息之后,该代码使用在调用响应消息中标识的任何输出参数从方法的调用返回。替代地,如果方法的调用是异步调用,则OORPC系统包括客户端组件,该客户端组件用于处理可能不是该方法的一部分而是回调方法的一部分的调用响应消息,该回调方法将被调用以向应用发信号通知方法的执行已经完成。

在一些实施例中,OORPC系统为应用提供将每个代理映射到其对应的远程对象的客户端组件。应用可以具有与用于实例化远程对象的创建远程对象相对应的创建代理。可以调用创建代理的每个方法以实例化某个远程对象类的远程对象。每个方法向服务器组件(称为“存根(stub)”)发送调用请求消息以调用创建远程对象上的对应方法来实例化远程对象类的远程对象。每个方法还实例化远程对象的代理并且返回对代理的引用。例如,创建代理可以具有创建文档方法,该方法向服务器组件发送调用请求消息,该调用请求消息请求调用创建远程对象的创建文档方法以实例化文档远程对象。可以为实例化的文档远程对象分配对象标识符(通过应用或服务器代码)。创建代理的创建文档方法还实例化与文档远程对象相对应的文档代理,并且将对文档代理的引用(例如,文档代理的地址)映射到对象标识符。然后,创建代理的创建文档方法返回对文档代理的引用。当应用随后调用由引用指示的文档代理的方法时,该方法使用其引用来检索文档远程对象的对象标识符。该方法然后向服务器组件发送如上所述的包括用于向服务器组件标识文档远程对象的对象标识符的调用请求消息。

当服务器组件接收到调用请求消息时,服务器组件从调用请求消息中提取对象标识符、方法标识符和任何输入参数。服务器组件检索对由对象标识符标识的远程对象的引用,并且调用传递输入参数的所标识的方法。当该方法返回时,服务器组件向客户端发送包括对象标识符、方法标识符和任何输出参数的调用响应消息。

在一些实施例中,创建代理和创建远程对象分别在应用和服务器组件的初始化期间彼此独立地被实例化。也就是说,应用在不与服务器组件通信的情况下实例化创建代理,并且服务器组件在不与应用通信的情况下实例化创建远程对象。应用和服务器组件分配相同的对象标识符(例如,预定义的)以标识创建代理和创建远程对象,使得当应用调用创建代理的方法时,发送到服务器的调用请求消息包括创建远程对象的对象标识符。在服务器组件调用创建远程对象的方法之后,服务器组件被返回对新创建的远程对象的引用。然后,服务器组件为新创建的远程对象生成对象标识符,将对象标识符映射到引用,并且发送包括对象标识符和所调用的方法的方法标识符的调用响应消息。当应用接收到调用响应消息时,应用将对象标识符映射到对相对应的代理的引用。以这种方式,当随后调用相对应的代理的方法时,该方法可以在调用请求消息中包括远程对象的对象标识符。

在一些实施例中,OORPC系统可以不发送调用请求,直到发送调用请求消息标准满足。当发送调用请求消息标准满足时,OORPC系统发送包括尚未发送的每个调用请求的调用请求消息。例如,代理类的方法可以被指定为可推迟的或不可推迟的(例如,经由与远程对象类的接口相关联的元数据)。当应用调用可推迟的方法时,OORPC系统生成包括对象标识符、方法标识符以及任何输入参数的调用请求,并且对调用请求进行排队。当应用调用不可推迟的方法时,OORPC系统发送包括排队的调用请求和对当前调用的调用请求的调用请求消息。当调用不可推迟的方法时,可以满足发送调用请求消息标准。类似地,当调用请求已经排队达特定时段时,当对特定数目的调用请求排队时,当应用指示发送调用请求消息时,等等,可以满足发送调用请求消息。

当服务器组件接收到具有多个调用请求的调用请求消息时,服务器组件按照调用请求被排队的顺序处理调用请求。在调用请求完成之后,服务器组件发送包括对调用请求消息的每个调用请求的调用响应的调用响应消息。当应用接收到调用响应消息时,应用按照调用响应的对应调用请求的顺序来处理调用响应。

在一些实施例中,OORPC系统提供对在客户端处本地存储远程对象的属性的值的支持。当OORPC系统为远程对象类生成代理类时,OORPC系统为属性生成与其他方法不同的获取器方法(“获取器”)。OORPC系统生成获取器方法,该获取器方法不是向服务器发送调用请求消息以检索属性的值,而是检索属性的本地存储值并且返回该值。为了确保代理的属性的本地存储值与由对应远程对象存储的值同步,服务器组件可以向每个调用响应消息附加对于自从发送最后的调用响应消息以来其值已经改变的远程对象的每个属性的属性更新。服务器组件可以保持上次为每个属性发送的值的列表,并且在发送调用响应消息时,服务器组件可以为每个远程对象的每个属性调用获取器方法以标识已经改变的值。

当应用接收到具有属性更新的调用响应消息时,应用将属性更新的新值存储在对应的代理中。当OORPC系统从远程对象类的接口生成代理类时,OORPC系统可以为每个代理类生成存储类。存储类可以包括将每个属性的标识符(例如,属性名称的散列)映射到属性的类型(例如,整数)的表和用于存储该属性的值的存储类的方法。OORPC系统可以向代理类添加静态数据成员,该静态数据成员是对作为该代理类的实例存储类的存储对象的引用。当OORPC系统接收属性的新值时,它从调用响应消息中的属性更新中检索远程对象的对象标识符、属性的标识符和属性的值。OORPC系统使用对象标识符来检索对相对应的代理的引用,并且从该代理中检索对存储对象的引用。然后,OORPC系统调用与传递对代理的引用和值的属性相对应的存储对象的存储方法。存储方法将值存储在代理中。

图1是示出一些实施例中的应用和服务器组件的数据结构的框图。客户端110执行应用111,并且服务器120执行服务器组件121。应用和服务器组件被表示为伪代码。在初始化期间,在客户端处执行的OORPC系统的客户端组件119可以实例化ID表对象113,并且作为OORPC系统的组件的服务器组件可以实例化ID表对象123。ID表对象提供用于存储和检索代理和远程对象的对象标识符到ID表对象的ID表中的它们的对应引用的映射的方法。应用实例化创建代理112,并且服务器组件实例化对应的创建远程对象122。客户端组件向ID表对象113添加创建代理的对象标识符(例如,1)到对创建代理(“S”)(例如,其地址)的引用的映射,并且服务器组件向ID表对象123添加创建远程对象的对象标识符到对创建远程对象的引用的映射。然后,服务器组件等待从客户端接收调用请求。客户端的代理的对象标识符和服务器的对应远程对象的对象标识符具有相同的值。

为了创建远程对象类的远程对象,应用调用创建代理的方法来创建远程对象类的远程对象。例如,如果远程对象类名称为“X”,则应用可以调用创建代理的createX方法来创建远程对象类X的远程对象。创建代理的createX方法向服务器发送包括创建远程对象的对象标识符(“req.ID”)、createX方法的方法标识符(“req.method”)和任何输入参数(“req.inparam”)的调用请求消息(“req”)。客户端组件可以实例化请求对象117以存储要发送的调用请求消息的数据,并且可以实例化响应对象118以存储所接收的调用响应消息的数据。类似地,服务器组件可以实例化请求对象127以存储所接收的调用请求消息的数据,并且可以实例化响应对象128以存储要发送的调用响应消息的数据。

在接收到调用请求消息时,服务器组件从调用请求消息中提取对象标识符,并且调用传递对象标识符的其ID表对象的检索引用方法(“retrieveveref”)以检索对创建远程对象的引用。然后,服务器组件调用请求对象的提取方法(“提取”)以提取任何输入参数(“inparam”)。然后,服务器组件从请求对象中提取createX方法的方法标识符。然后,服务器组件使用对创建远程对象的引用和createX方法的标识符来调用传递输入参数的创建远程对象的createX方法。createX方法实例化类X的远程对象124(“X2对象”)并且返回作为对远程对象124的引用的输出参数。虽然没有通过伪代码示出,但是服务器组件也调用ID表对象123的添加条目方法以向其ID表添加将远程对象124的对象标识符(“2”)映射到远程对象124的引用(“X2”)的条目,并且向响应对象(“res”)添加对象标识符(“res.ID”)和createX方法的标识符(“res.method”)。然后,服务器组件调用响应对象的存储方法(“store”)以存储任何输出参数。然后,服务器组件基于响应对象来向客户端发送调用响应消息。

在接收到调用响应消息时,客户端组件基于调用响应消息来实例化响应对象。然后,客户端组件实例化代理114(“X2代理”)并且调用ID表对象113的添加条目方法以向其ID表添加代理114的对象标识符(“2”)到代理114(“X2”)的引用的映射。如果createX方法的调用是同步的,则createX方法返回对代理114的引用。

在远程对象124及其对应的代理114被实例化之后,应用然后调用传递输入参数的指示(“in”)的代理114的方法。所调用的方法可以调用客户端组件以生成包括代理114的对象标识符(“2”)、方法标识符和输入参数的请求对象,并且基于请求对象来向服务器发送调用请求消息。在接收到调用响应消息时,服务器组件从ID表对象中检索远程对象124的引用,并且调用传递输入参数的远程对象124的所标识的方法。当该方法返回时,服务器组件生成包括远程对象124的对象标识符(“2”)、方法标识符和任何输出参数的响应对象。然后,服务器组件基于响应对象来向客户端发送调用响应消息。在接收到调用响应消息时,客户端组件提取输出参数并且发信号通知该方法已经返回以影响同步或异步调用处理。

然后,应用可以调用创建代理的createX方法和createY方法以实现在服务器处创建远程对象125(“X3对象”)和远程对象126(“Y4对象”)并且在客户端处创建对应代理115(“X3代理”)和对应代理116(“Y4代理”)。然后,应用可以通过调用代理115和代理116的对应方法来调用远程对象125和远程对象126的方法。

可以在其上实现OORPC系统的计算系统(例如,客户端、服务器、客户端设备、服务器设备)可以包括中央处理单元、输入设备、输出设备(例如,显示设备和扬声器)、存储设备(例如,存储器和磁盘驱动器)、网络接口、图形处理单元、加速度计、蜂窝无线电链路接口、全球定位系统设备等。计算系统可以包括数据中心的服务器、大规模并行系统等。计算系统可以访问计算机可读介质,包括计算机可读存储介质和数据传输介质。计算机可读存储介质是有形存储装置,不包括暂态传播信号。计算机可读存储介质的示例包括诸如主存储器、高速缓冲存储器和辅助存储器(例如,DVD)等存储器以及其他存储装置。计算机可读存储介质可以记录有或者可以编码有实现OORPC系统的计算机可执行指令或逻辑。数据传输介质用于经由有线或无线连接经由暂态传播信号或载波(例如,电磁)传输数据。

OORPC系统可以在由一个或多个计算机、处理器或其他设备执行的计算机可执行指令(诸如程序模块和组件)的一般上下文中描述。通常,程序模块或组件包括执行特定任务或实现特定数据类型的例程、程序、对象、数据结构等。通常,程序模块的功能可以在各种实施例中根据需要组合或分布。OORPC系统的各方面可以使用例如专用集成电路(ASIC)以硬件实现。

图2是示出一些实施例中的同步调用的代理的方法的处理的流程图。由OORPC系统的代码生成器自动生成的方法200被传递一个或多个输入参数并且返回一个或多个输出参数。该方法向服务器发送调用请求消息并且接收调用响应消息。在框202中,该方法调用传递对代理的引用(“this”)的ID表对象的检索标识符方法以检索代理的对象标识符。该方法将代理的对象标识符存储在请求对象中。在框204中,该方法将方法标识符添加到请求对象。在框206中,该方法调用请求对象的存储参数方法以存储输入参数。在框208中,该方法向服务器发送基于请求对象的调用请求消息。在框210中,该方法从服务器接收调用响应消息并且生成响应对象。在框212中,该方法调用响应对象的提取参数方法以提取输出参数。然后该方法完成。在一些实施例中,该方法可以调用客户端组件以执行框202-212的处理。

图3是示出一些实施例中的服务器组件的接收请求组件的处理的流程图。当从客户端接收到调用请求消息时,调用接收请求组件300,并且接收请求组件300被传递基于调用请求消息的请求对象。该组件调用远程对象的方法,并且向客户端发送调用响应消息。在框302中,该组件从请求对象中提取远程对象的对象标识符,并且调用传递对象标识符的ID表对象的检索引用方法,并且接收对远程对象的引用。在框304中,该组件从请求对象中提取方法标识符。在框306中,该组件调用请求对象的提取参数方法以提取输入参数。在框308中,该组件调用远程对象的标识符方法,传递输入参数并且在返回时接收任何输出参数。在框310中,该组件生成响应对象,该响应对象包括对象标识符、方法标识符和输出参数。在框312中,该组件向客户端发送基于响应对象的调用响应消息,并且然后完成。

图4是示出一些实施例中的用于客户端组件的响应对象的响应对象类的提取参数方法的处理的流程图。由客户端组件调用提取参数方法400以提取由所调用的方法返回的输出参数。在框402中,该方法从第一输出参数开始选择下一输出参数。在判定框404中,如果已经选择了所有输出参数,则该方法完成,否则该方法在框406处继续。在判定框406中,如果所选择的输出参数是远程对象的标识符,则该方法在框410处继续,否则该方法在框408处继续。在框408中,该组件将所选择的输出参数存储为输出参数,并且然后循环到框402以选择下一输出参数。在框410中,该方法调用ID表对象的检索引用方法以检索对在响应对象中标识的远程对象的引用。在判定框412中,如果引用为空,则远程对象的代理尚未实例化,并且该方法在框414处继续,否则该方法在框422处继续。在框414中,该方法实例化远程对象的类的代理。OORPC系统的代码生成器可以从所调用的方法的签名中标识远程对象的类并且更一般地标识任何参数的类型。在框416和418中,该方法为ID表创建条目。在框420中,该方法调用ID表对象的添加条目方法以存储条目,并且然后在框422处继续。在框422中,该组件将输出参数设置为所返回的引用,并且然后循环到框402以选择响应对象的下一输出参数。

图5是示出一些实施例中的用于服务器组件的响应对象的响应对象类的存储参数方法的处理的流程图。存储参数方法500被传递输出参数以存储在响应对象中。在框502中,该方法选择下一输出参数。在判定框504中,如果已经选择了所有输出参数,则该方法完成,否则该方法在框506处继续。在判定框506中,如果输出参数是对对象的引用,则该组件在框508处继续,否则该组件在框510处继续。在框508中,该组件调用ID表对象的检索ID方法以检索与引用相对应的对象标识符并且将输出参数设置为引用。在框510中,该组件将输出参数存储在响应对象中,并且然后循环到框502以选择下一输出参数。尽管未示出,但是请求对象还具有以与响应对象的方法类似的方式起作用的提取参数方法和存储参数方法。

图6-9是示出一些实施例中的ID表对象类的方法的流程图。图6是示出一些实施例中的ID表对象类的检索ID方法的处理的流程图。检索ID方法600被传递对对象的引用,并且返回与该引用相对应的对象标识符。在框602中,该方法选择ID表的下一条目。在判定框604中,如果已经选择了所有条目,则该方法返回空值以指示该引用的条目不在ID表中,否则该方法在框606处继续。在判定框606中,如果所选择的条目中的引用与所传递的引用相匹配,然后该方法返回该条目的对象标识符的指示,否则该方法循环到框602以选择ID表的下一条目。

图7是示出一些实施例中的ID表对象的检索引用方法的处理的流程图。检索引用方法700被传递对象标识符并且返回与该对象标识符相对应的引用。在框702中,该方法检索ID表的下一条目。在判定框704中,如果已经选择了所有条目,则该方法返回空值以指示对象标识符的条目不在ID表中,否则该方法在框706继续。在判定框706中,如果所选择的条目的对象标识符与所传递的对象标识符相匹配,则该方法返回所选择的条目的引用,否则该方法循环到框702以选择ID表的下一条目。

图8是示出一些实施例中的传递引用的ID表对象的添加条目方法的处理的流程图。添加条目方法800被传递对对象(即,代理或远程对象)的引用,并且如果ID表还没有包含该引用的条目,则向ID表添加该引用的条目。在框802中,如果ID表包含与引用相对应的条目,则该方法调用传递引用的该ID表对象的检索ID方法以检索对象标识符。在判定框804中,如果所返回的对象标识符为空,则ID表不包含对应的条目,并且该方法在框806处继续,否则该方法返回对象标识符。在框806中,该方法将对象标识符设置为ID表对象的下一对象标识符字段,并且递增下一对象标识符字段。在框808-810中,该方法初始化引用的条目。在框812中,该方法将条目附加到ID表,并且然后返回对象标识符的指示。

图9是示出一些实施例中的传递条目的ID表对象的添加条目方法的处理的流程图。如果条目不在ID表中,则添加条目方法900被传递要添加到ID表的条目。在框902中,该方法调用传递条目的引用的该ID表对象的检索ID方法。在判定框904中,如果条目为空,则ID表不包含该条目,并且该方法在框906处继续,否则该方法返回。在框906中,该方法将条目添加到ID表并且然后返回。

在一些实施例中,OORPC系统允许客户端和服务器两者托管由彼此远程访问的远程对象。因此,客户端和服务器在两个主机远程对象的意义上可以被认为是对等的。例如,如果多个客户端正在协作环境中处理文档,则每个客户端可以向服务器注册对象以接收对文档的改变的事件通知。在这种情况下,客户端可以调用文档代理的注册方法,以传递由客户端实例化的事件监听器对象。客户端可以保持用于将对象标识符映射到对在服务器处远程托管的对象的代理的引用的代理ID表、以及将对象标识符映射到对由客户端本地托管的对象的引用的对象ID表。类似地,服务器可以保持用于将对象标识符映射到对由客户端远程托管的对象的代理的引用的代理ID表、以及将对象标识符映射到对由服务器本地托管的对象的引用的对象ID表。文档代理的注册方法可以将事件监听器对象的条目添加到对象ID表,并且在注册方法的调用请求消息中包括条目的对象标识符作为输入参数。当服务器接收到调用请求消息时,服务器组件可以在对象ID表和代理ID表两者中搜索具有匹配的对象标识符的条目。如果找到这样的条目,则服务器组件将该对象标识符替换为该条目的引用作为注册方法的输入参数。如果未找到这样的条目,则服务器组件实例化事件监听器对象的事件监听器代理,并且向代理ID表添加条目,该条目将对象标识符映射到对事件监听器对象代理的引用。然后,服务器组件调用文档远程对象的注册方法,以将该引用传递给监听器代理。当服务器组件向应用发送事件时,服务器代码调用监听器代理的事件方法,该方法向客户端发送调用请求消息。当客户端接收到调用请求消息时,客户端组件以与服务器的服务器组件处理调用请求消息的方式非常相同的方式处理调用请求消息。以这种方式,客户端和服务器都可以调用由彼此远程托管的远程对象的方法。

在一些实施例中,远程对象的对象方法可以具有通过值传递的输入参数和/或输出参数。为了通过值来传递输入参数,与远程对象相对应的代理的代理方法检索输入参数的值,并且将检索到的值添加到调用请求消息。如果输入参数本身是由服务器托管的远程对象,则代理方法可以向服务器发送调用请求消息以检索该值。类似地,当通过值返回输出参数时,对象方法检索输出参数的值,并且将检索到的值添加到调用响应消息。在一些实施例中,输入参数或输出参数可以是包含多个对象的数据结构。为了传递作为这种数据结构的输入参数,代理方法检索数据结构中的每个对象的对象标识符,并且将每个对象标识符添加到调用请求消息。例如,如果数据结构是数组,则数组的每个元素的代理方法选择元素,检索所选择的元素的对象标识符,并且将检索到的对象标识符添加到调用请求消息。为了传递作为数组的输出参数,对象方法以类似的方式处理数组的元素。输入参数或输出参数的数据结构可以包含托管在服务器上的对象和托管在客户端上的对象。当处理这样的输入参数时,代理方法将每个对象的对象标识符添加到调用请求消息。在接收到调用请求消息时,服务器组件从对象标识符表或代理标识符表中检索对对象的引用,并且调用传递检索到的引用的对象方法。如果尚未实例化,则服务器组件可以实例化对应的对象或代理。

图10是示出一些实施例中的支持远程对象的对等托管的OORPC系统的实现的框图。客户端1010包括代理ID表对象1011和对象ID表对象1015。代理ID表对象1011包括包含代理1012的条目的ID表,并且对象ID表对象1015包括包含远程对象1016的条目的ID表。服务器1020包括对象ID表对象1021和代理ID表对象1025。对象ID表对象1021包括包含每个对象1022的条目的ID表,并且代理ID表对象1025包括包含每个代理1026的条目的ID表。

图11是示出一些实施例中的支持OORPC系统的属性的值的同步的数据结构的框图。客户端1110包括具有ID表的ID表对象1111,该ID表包括用于远程对象类X的远程对象的代理1112和1113的条目,并且包括用于远程对象类Y的远程对象的代理1114的条目。每个代理包括用于引用其远程对象类的存储对象的静态数据成员。代理1112和1113包括对远程对象类X的存储对象1115的引用,并且代理1114包括对远程对象类Y的存储对象1116的引用。远程对象类的存储对象可以包括用于将属性的值本地存储在代理中的远程对象类的每个获取器方法的方法。服务器1120包括具有ID表的ID表对象1121,该ID表包括远程对象类X的远程对象1122和1123的条目,并且包括远程对象类Y的对象1124的条目。服务器还包括具有属性表的属性表对象1125,该属性表包括每个远程对象的每个属性的条目。每个条目包括远程对象的对象标识符、远程对象的每个属性的最后检索值、以及对远程对象的远程对象类的获取器表的引用。在一些实施例中,每个条目可以包含对存储远程对象的最后检索的值的数据结构的引用。此外,ID表和属性表可以组合成单个表。如图所示,属性表的第一条目对应于远程对象1122,并且包括指向远程对象类X的获取器表1126的指针。获取器表1126包括远程对象类X的每个获取器方法1128和1129的条目。获取器表1127包括远程对象类Y的每个获取器方法1130和1131的条目。条目指向调用远程对象的对应获取器方法以检索属性的值的代码。

在发送响应调用消息之前,服务器组件可以选择属性表的每个条目并且调用每个条目的远程对象的获取器方法以检索每个属性的值。如果对象的获取器方法返回的属性的值与存储在属性表中的该属性的值不同,则服务器组件向调用响应消息添加属性更新。属性更新包括对象标识符、属性标识符和新值。当客户端接收到调用响应消息时,客户端组件处理每个属性更新。对于每个属性更新,客户端组件检索与属性更新的对象标识符相对应的代理的引用。客户端组件从代理中检索对存储对象的引用,并且为传递引用的指示的存储对象的该属性调用对应的存储方法。然后,存储方法将属性的值存储在代理中。在一些实施例中,每个属性更新可以包含远程对象的所有已经改变的属性的值。也就是说,调用响应消息仅包含远程对象的一个属性更新,而不是远程对象的每个已经改变的属性的单独的属性更新。

图12是示出一些实施例中的服务器组件的获取属性值组件的处理的流程图。调用获取属性值组件1200以将属性更新添加到其值已经改变的每个远程对象的每个属性的调用响应消息。在框1202中,该组件选择属性表的下一条目。在判定框1204中,如果已经选择了所有条目,则该组件完成,否则该组件在框1206处继续。在框1206中,该组件调用ID表对象的检索引用方法以检索与所选择的条目的对象标识符相对应的引用。在框1208-1218中,该组件循环处理所引用的远程对象的每个属性。在框1208中,该组件从由所选择的条目引用的获取器表中选择下一获取器方法。在判定框1210中,如果已经选择了所有获取器方法,则该组件循环到框1202以选择属性表中的下一条目,否则该组件在框1212处继续。在框1212中,该组件调用所引用的远程对象的所选择的获取器方法。在判定框1214中,如果属性的当前值与属性的所选择的条目中的值相同,则该组件循环到框1208以选择下一获取器方法,否则该组件在框1216处继续。在框1216中,该组件将属性的条目中的值设置为当前值。在框1218中,该组件将具有对象标识符、属性标识符和属性的当前值的属性更新添加到调用响应消息,并且然后循环到框1208以选择下一获取器方法。

图13是示出一些实施例中的客户端组件的存储属性值组件的处理的流程图。当接收到包括属性更新的调用响应消息时,调用存储属性值组件1300,并且存储属性值组件1300更新属性的本地存储值。在框1302中,该组件选择调用响应消息的下一属性更新。在判定框1304中,如果已经选择了所有属性更新,则该组件完成,否则该组件在框1306处继续。在框1306中,该组件调用ID表对象的检索引用方法,以传递所选择的条目的对象标识符,并且接收对相对应代理的引用。在框1308中,该组件调用由传递条目的值的代理的静态数据成员引用的存储对象的存储方法。然后,该组件循环到框1302以选择响应调用消息的下一属性更新条目。存储对象可以具有生成的单独的存储方法来处理每个属性。替代地,存储对象可以具有单个存储方法,该方法使用诸如对象类型和属性名称等信息来访问具有每个属性的条目的表。

图14是示出一些实施例中的用于为代理类自动生成代码的OORPC系统的代码生成器的流程图。代理代码组件1400被传递由服务器托管的每类对象的接口,并且生成代理类(例如,JavaScript)。在框1402中,该组件选择下一接口。在判定框1404中,如果已经选择了所有接口,则该组件完成,否则该组件在框1406处继续。在框1406中,该组件为代理类生成初始代码,该初始代码可以包括类模板,该类模板包括类的名称、类的开始和结束符号(例如,括号)等。在框1408-1412中,该组件循环处理不是获取器方法的接口的每个方法。在框1408中,该组件选择下一方法。在判定框1410中,如果已经选择了所有方法,则该组件在框1414处继续,否则该组件在框1412处继续。在框1412中,该组件为代理类的所选择的方法生成代码,并且然后循环到框1408以选择下一方法。在框1414中,该组件调用传递所选择的接口的指示的代码生成器的进程代理属性组件以为代理类的存储类生成代码。然后,该组件循环到框1402以选择下一接口。

图15是示出一些实施例中的代码生成器的进程代理属性组件的处理的流程图。进程代理属性组件1500被调用以为代理类生成存储类,并且向代理类添加用于存储对代理类的存储对象的引用的静态数据成员。在框1502中,该组件为存储类生成初始代码。在框1504中,该组件向代理类添加用于引用存储对象的静态数据成员。在框1506中,该组件选择接口的下一属性。在判定框1508中,如果已经选择了接口的所有属性,则该组件完成,否则该组件在框1510继续。在框1510中,该组件为向服务器发送调用请求消息的所选择的属性的代理类的设置器方法生成代码。在框1512中,该组件为检索该属性的本地值的所选择的属性的代理类的获取器方法生成代码。在框1514中,该组件为存储类的存储方法生成用于将在调用响应消息中接收的所选择的属性的值存储在代理中的代码,并且然后循环到框1506以选择接口的下一属性。

在一些实施例中,OORPC系统的代码生成器可以自动生成服务器的服务器组件以支持远程对象的调用。可以为代码生成器提供由服务器托管的远程对象的接口。代码生成器生成用于从客户端接收调用请求消息并且调用传递所标识的输入参数的所标识的远程对象的所标识的远程对象方法的代码。代码生成器还生成用于在调用的远程对象方法返回之后向客户端发送调用响应消息的代码,该调用响应消息具有远程对象的对象标识符、远程对象方法的方法标识符以及由所调用的远程对象方法返回的输出参数。代码生成器还可以自动生成基于接口来支持属性值同步的代码。

图16和17是示出一些实施例中的推迟调用请求的流程图。图16是示出一些实施例中的客户端组件的发送请求组件的处理的流程图。发送请求组件1600被调用以传递调用请求(例如,经由请求对象)以调用远程对象的方法。当方法的调用不可推迟时,该组件向服务器发送具有任何排队的调用请求的调用请求消息。在框1602中,该组件将调用请求附加到请求队列。在判定框1604中,如果方法的调用是可推迟的,则该组件完成,否则该组件在框1606处继续。在框1606中,该组件将调用请求队列的调用请求添加到调用请求消息。在框1608中,该组件向服务器发送调用请求消息。在框1610中,该组件清空请求队列并且然后完成。

图17是示出一些实施例中的服务器组件的接收请求组件的处理的流程图。当服务器组件接收到调用请求消息时,接收请求组件1700被调用。在框1702中,该组件选择调用请求消息的下一调用请求。在判定框1704中,如果已经选择了所有调用请求,则该组件完成,否则该组件在框1706处继续。在框1706中,该组件通过调用由调用请求标识的远程对象的方法来处理所选择的调用请求。然后,该组件循环到框1702以选择下一调用请求。

以下段落描述OORPC系统的各方面的各种实施例。OORPC系统的实现可以采用实施例的任何组合。下面描述的处理可以由具有处理器的计算设备执行,该处理器执行存储在计算机可读存储介质上的实现OORPC系统的计算机可执行指令。

在一些实施例中,提供了一种由客户端执行以调用远程对象类的远程对象的远程对象成员函数的方法,其中远程对象由服务器托管。在浏览器的控制下,该方法执行以下操作。该方法检索包括应用的网页。在应用的控制下,该方法实例化代理类的代理,其中代理类包括具有与远程对象成员函数相同的签名的代理成员函数。该方法将对象标识符与代理相关联,其中对象标识符用于向服务器标识远程对象。该方法调用代理对象的代理成员函数。在所调用的代理成员函数的控制下,该方法向服务器发送包括与代理相关联的对象标识符和远程对象成员函数的标识符的调用请求消息。应用依赖于浏览器提供的功能,而无需浏览器在执行应用时访问附加功能。在一些实施例中,该方法还向服务器发送请求调用创建远程对象的成员函数以实例化远程对象的调用请求消息。在一些实施例中,在应用的控制下,该方法还从服务器接收指示远程对象已经被实例化的调用响应消息。在一些实施例中,调用响应消息包括由服务器分配给远程对象的对象标识符。在一些实施例中,调用请求消息包括由客户端分配给远程对象的对象标识符。在一些实施例中,调用请求消息包括传递给代理成员函数的输入参数。在一些实施例中,调用响应消息包括由代理成员函数返回的输出参数。在一些实施例中,代理由代理引用标识,并且该方法还包括将代理引用与对象标识符相关联。在一些实施例中,关联包括向对象标识符表添加包括代理引用和对象标识符的条目。在一些实施例中,远程对象类和代理类以不同的编程语言实现。在一些实施例中,代理类基于远程对象类的接口定义自动生成,代理类包括代理成员函数的实现。在一些实施例中,代理成员函数的调用是异步调用。

在一些实施例中,提供了一种由计算系统执行以为在客户端处执行的应用生成代理类以访问由服务器托管的远程对象类的远程对象的方法。该方法访问远程对象类的接口,其中每个接口包括远程类的远程对象成员函数的签名。对于每个远程对象类,该方法为远程对象类生成代理类。代理类包括用于远程对象类的每个远程对象成员函数的代理成员函数。生成代理类的每个代理成员函数以在调用代理成员函数时向服务器发送调用请求消息,该调用请求消息包括远程对象的对象标识符、远程对象成员函数的成员函数标识符、以及传递给代理成员函数的输入参数。该方法提供用于以下操作的代码:在从服务器接收到调用响应消息之后,从调用响应消息中提取远程对象的对象标识符、远程对象成员函数的成员函数标识符和输出参数,并且指示远程对象成员函数的调用已经完成。在一些实施例中,该方法还为服务器生成服务器组件,其中服务器组件基于所托管的远程对象的接口生成,以从客户端接收调用请求消息,调用传递所标识的输入参数的所标识的远程对象的所标识的远程对象成员函数,并且在所调用的远程对象成员函数返回之后,向客户端发送调用响应消息,该调用响应消息具有远程对象的对象标识符、远程对象成员函数的成员函数标识符和由所调用的远程对象成员函数返回的输出参数。在一些实施例中,应用的代码依赖于由浏览器提供的功能,而无需浏览器在执行应用时访问附加功能。

在一些实施例中,提供了一种客户端,其被配置为使得由程序的应用执行引擎执行的应用能够调用由服务器托管的远程对象类的远程对象的远程对象成员函数。客户端包括用于执行计算机可执行指令的处理器和存储计算机可执行指令的计算机可读存储介质,这些计算机可执行指令在由处理器执行时控制客户端执行以下处理。该处理实例化代理类的代理,其中代理类包括具有与远程对象成员函数相同的签名的代理成员函数。该处理将对象标识符与代理相关联,其中对象标识符用于向服务器标识远程对象。该处理调用代理的代理成员函数。在所调用的代理成员函数的控制下,该处理向服务器发送包括标识远程对象的对象标识符和标识远程对象成员函数的成员函数标识符的调用请求消息。在一些实施例中,应用依赖于由程序提供的功能,而无需程序在执行应用时访问附加功能,并且应用的代理类从远程对象类的接口自动生成以支持由远程对象的客户端访问。在一些实施例中,该处理还向服务器发送调用请求消息,该调用请求消息请求实例化远程对象类的实例,并且从服务器接收指示远程对象已经被实例化的调用响应消息。在一些实施例中,该处理实例化具有成员函数的创建代理,该成员函数在被调用时指示代理的实例化和调用请求消息的发送。在一些实施例中,调用响应消息包括对象标识符。在一些实施例中,程序是浏览器,并且应用响应于用户经由浏览器访问网页而被下载和执行。

在一些实施例中,提供了一种客户端,其被配置为使得由具有应用执行引擎的程序执行的应用能够调用由服务器托管的远程对象类的远程对象的远程对象成员函数,并且支持由服务器调用由客户端托管的本地对象类的本地对象的本地对象成员函数。客户端包括执行计算机可执行指令的处理器和存储计算机可执行指令的计算机可读存储介质,这些计算机可执行指令在由处理器执行时控制客户端执行以下处理。在由程序执行的应用的控制下,该处理实例化代理类的代理,其中代理类包括具有与远程对象成员函数相同的签名的代理成员函数。该处理实例化本地对象类的本地对象。该处理将远程对象标识符与对代理的代理引用相关联,并且将本地对象标识符与对本地对象的本地对象引用相关联。在所调用的代理成员函数的控制下,该处理向服务器发送包括标识远程对象的远程对象标识符和标识远程对象成员函数的成员函数标识符的调用请求消息。在从服务器接收到包括本地对象标识符并且标识本地对象成员函数的调用请求消息时,该处理检索与所包括的本地对象标识符相关联的本地对象引用,并且调用由检索到的本地对象引用引用的本地对象的本地对象成员函数。

在一些实施例中,提供了一种由客户端执行以同步属性值的方法。该方法实例化与远程对象类的远程对象相对应的代理类的代理。远程对象在服务器处实例化。代理类指定具有代理获取器的属性。代理的代理获取器用于检索存储在代理中的属性的值。该方法向服务器发送调用请求消息以调用远程对象的远程对象成员函数。该方法从服务器接收对调用请求消息的调用响应消息。当调用响应消息包括属性更新时,该方法从调用响应消息中提取来自属性更新的属性的值,并且将所提取的值存储在代理中。当调用属性的代理获取器时,该方法允许从代理中检索属性的值,而不必向服务器发送调用请求消息。在一些实施例中,代理是由浏览器执行的应用的一部分。在一些实施例中,向服务器发送和从服务器接收是经由HTTP消息来进行的。在一些实施例中,代理类包括对具有属性的存储成员函数的存储类的存储对象的静态引用,并且其中提取和存储由存储成员函数执行。在一些实施例中,存储类包括代理类的每个属性的存储成员函数。在一些实施例中,存储类包括获取器表,该获取器表将属性的标识符映射到用于存储所提取的值的属性的存储类的存储成员函数的标识符以及属性的类型的标识符。在一些实施例中,存储成员函数被传递对代理的引用,并且执行基于属性的类型来提取属性的值并且将值存储在所引用的代理中。在一些实施例中,存储成员函数被传递代理的标识符。在一些实施例中,调用响应消息包括代理的多个属性的值。在一些实施例中,调用响应消息包括不同代理的多个属性的值。在一些实施例中,调用响应消息仅包括自从服务器发送先前的调用响应消息以来其值已经改变的属性的值。

在一些实施例中,提供了一种由服务器执行以同步存储在客户端处的属性值的方法。该方法从客户端接收调用由服务器托管的远程对象的远程对象成员函数的请求。该方法调用远程对象的远程对象成员函数。在调用远程对象的远程对象成员函数之后,该方法检索远程对象的属性的值。当检索到的值与属性的最后检索到的值不同时,该方法向调用响应消息添加远程对象的对象标识符、属性的属性标识符和检索到的值。该方法向客户端发送调用响应消息以指示远程对象成员函数被调用并且该属性的值已经改变,以便远程对象的客户端的代理的属性的获取器成员函数可以通过访问服务器本地检索属性的值。在一些实施例中,针对多个属性执行检索和添加。在一些实施例中,多个属性包括不同远程对象的属性。在一些实施例中,针对要被发送以指示远程对象的远程对象成员函数被调用的每个调用响应消息执行检索和添加。

在一些实施例中,提供了一种由计算系统执行以支持远程对象类的远程对象的属性的值与代理类的代理的属性的值的同步的方法。该方法输入每个远程对象类的接口的描述。对于远程对象类的每个接口,该方法为代理类生成代码。该方法为代理类的每个代理成员函数生成代码以发送调用请求消息以调用对应的远程对象上的对应的远程对象成员函数。该方法为代理类的属性的每个代理获取器成员函数生成代码以检索和返回在代理类的代理处本地存储的属性的值,而无需访问对应的远程对象。该方法生成用于存储从服务器接收的代理类的属性的值的代码,使得代理类的属性的每个代理获取器成员函数可以检索该属性的本地存储的值。在一些实施例中,该方法还针对每个代理类向代理类添加对用于存储属性的值的代码的静态引用。在一些实施例中,用于存储属性的值的代码被实现为存储类的一部分。在一些实施例中,存储类包括表,该表针对代理类的每个属性,将该属性的标识符映射到存储该属性的值的存储成员函数,以便该属性的代理获取器成员函数可以检索该值。在一些实施例中,属性的标识符还被映射到属性的类型的指示,以用于从调用响应消息中提取属性的值,该调用响应消息服务器接收。

在一些实施例中,提供了一种由客户端执行以向服务器的远程对象发送应用的调用请求的方法。该方法接收调用请求。每个调用请求来自与远程对象类的远程对象相对应的应用的代理类的代理。对于每个接收的调用请求,当调用请求可推迟时,该方法存储调用请求。当调用请求不可推迟时,该方法向服务器发送包括先前未发送的每个存储的调用请求和所接收的调用请求的调用请求消息。该方法从服务器接收调用响应消息。每个调用响应消息响应于包括一个或多个调用请求的调用请求消息。至少一个调用响应消息包括多个调用响应。对于所接收的调用响应消息的每个调用响应,当调用响应包括输出参数时,该方法从调用响应中提取输出参数。该方法向应用提供调用响应已经被接收到的指示和任何提取的输出参数。在一些实施例中,调用请求消息指示接收到调用请求的顺序。在一些实施例中,按照接收到对应调用请求的顺序处理调用响应消息的调用响应。在一些实施例中,调用请求是异步调用请求。在一些实施例中,当调用请求已经被存储超过指定时间量时,该方法发送包括调用请求的调用请求消息。在一些实施例中,响应于从应用接收到发送未发送的调用请求的请求,该方法还发送包括调用请求的调用请求消息。在一些实施例中,针对由不同服务器托管的远程对象接收调用请求,并且其中发送到服务器的调用请求消息仅包括对由该服务器托管的远程对象的调用请求。在一些实施例中,应用在浏览器的控制下执行。

在一些实施例中,提供了一种用于发送应用的调用请求以调用由服务器托管的远程对象的成员函数的客户端。客户端包括执行计算机可执行指令的处理器和存储计算机可执行指令的计算机可读存储介质,这些计算机可执行指令在由处理器执行时控制客户端执行以下处理。该处理对调用请求进行排队。每个调用请求来自与应用的远程对象类的远程对象相对应的应用的代理类的代理。调用请求用于调用远程对象的远程对象成员函数。当发送调用请求标准满足时,该处理向服务器发送包括每个排队的调用请求的调用请求消息。该处理指示每个排队的调用请求不再排队。该处理从服务器接收响应于调用请求消息的调用响应消息。对于所接收的调用响应消息的每个调用响应,该处理向应用提供调用响应已经被接收到的指示。在一些实施例中,当接收到未排队的调用请求时,发送调用请求标准满足。在一些实施例中,当调用请求已经被存储超过指定时间量时,发送调用请求标准满足。在一些实施例中,当从应用接收到发送排队的调用请求的请求时,发送调用请求标准满足。在一些实施例中,调用请求消息指示应用生成调用请求的顺序。在一些实施例中,按照生成对应调用请求的顺序处理调用响应消息的调用响应。在一些实施例中,调用请求是异步调用请求。在一些实施例中,针对由不同服务器托管的远程对象接收调用请求,并且其中发送到服务器的调用请求消息仅包括由该服务器托管的远程对象的调用请求。在一些实施例中,应用在具有应用执行引擎的程序的控制下执行。

在一些实施例中,一种由服务器执行以处理应用的调用请求的方法。该方法从客户端接收包括调用请求的调用请求消息。每个调用请求来自与应用的远程对象类的远程对象相对应的应用的代理类的代理。调用请求用于调用远程对象的远程对象成员函数。对于调用请求消息的每个调用请求,处理调用请求。该处理包括从调用请求中提取远程对象的对象标识符、远程对象成员函数的成员函数标识符和输入参数。该处理调用传递输入参数的所标识的远程对象的所标识的远程对象成员函数。在从所调用的远程对象成员函数返回时,该处理存储调用请求的调用响应,该调用响应包括由所调用的远程对象成员函数返回的输出参数。在处理调用请求之后,该方法向客户端发送包括调用响应的调用响应消息。在一些实施例中,按照调用请求消息指定的顺序处理调用请求。在一些实施例中,与调用响应相对应的调用请求可以从调用响应中标识。在一些实施例中,

尽管用结构特征和/或动作专用的语言描述了本主题,但应当理解,所附权利要求书中定义的主题不必限于上述具体特征或动作。而是,上述具体特征和动作被公开作为实现权利要求的示例形式。例如,尽管OORPC系统主要在执行应用的web浏览器的上下文中描述,但是OORPC可以在其他上下文中使用。例如,客户关系管理(“CRM”)系统可以允许开发应用以定制CRM系统。在这种情况下,CRM系统的客户端组件执行访问由具有CRM系统的服务器端组件的服务器托管的对象的应用。Web浏览器、CRM系统和执行这样的应用的其他程序可以称为具有应用执行引擎的程序。因此,除了所附权利要求之外,本发明不受限制。

当前第1页1 2 3 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1