GWT如何减少RPC调用的代码序列化程序的大小

时间:2011-07-21 15:32:45

标签: gwt gwt-rpc

我发现GWT在我的应用程序上生成的超过60%的javaScript代码是用于RPC序列化程序的。 另外我发现服务接口之间没有共享序列化器,我的意思是如果我在2个rpc服务接口上引用了例如AccountDTO类型,我会得到2个序列化器类而不是相同类型的1。 为了减少编译代码的大小,我想也许我可以使用Deferred Binding来替换我拥有的一个大接口的所有服务接口。如果可能,那么GWTCompiler可能只生成一个AccountDTO序列化器而不是2。

我不确定这是个好主意,还是我的问题有更好的解决方案。

我试图实现的是这样的:

// Define new interface that extends all service interfaces
public interface GenericService extends RemoteService,
                    AccountingService,
                    FinancialService,..., { }

public interface GenericServiceAsync extends AccountingServiceAsync,
                         FinancialServiceAsync, ..., { }

// At Application.gwt.xml do:

<module>
...
...
    <replace-with class="com.arballon.gwt.core.client.GenericService">
        <when-this-is class="com.arballon.gwt.core.client.AccountingService>
    </replace-with>
    <replace-with class="com.arballon.gwt.core.client.GenericService">
        <when-this-is class="com.arballon.gwt.core.client.FinancialService>
    </replace-with>
    ...
    ...

但此刻我收到了错误:

[错误]'文件中的错误:/ C:/Users/Daniel/EclipseWorkspace/ADK/src/com/arballon/gwt/core/client/FinancialService.java'       [错误]第31行:无法找到重新绑定结果'com.arballon.gwt.core.client.GenericService'

对此问题的任何想法将不胜感激。 此致

丹尼尔

5 个答案:

答案 0 :(得分:4)

GWT的RPC生成代码构建了几个类来完成它的工作,正如您所指出的那样:每个类型的*_FieldSerializer通过线路,以及RemoteService异步类型的*_Proxy类。该代理类型需要*_TypeSerializer,这是您问题的根源 - 由于某种原因,GWT连接了字符串 - > js函数映射中的所有序列化/反序列化方法,可能有助于快速查找 - 但是此设置代码的代价是需要在最终构建中的代码行。一种更优化的方法可以让每个FieldSerializer都有一个注册方法,它将其方法添加到代理所拥有的静态映射中 - 然而,这是困扰的,但是GWT优化了尝试不引用instantiate()deserialize()serialize()方法,如果它们看起来不会被调用。

您的问题源于可以序列化的许多类型,以及您尝试构建每个描述特定功能单元的RemoteService类型,但重用了许多模型类型。令人钦佩的目标,特别是因为它可能会使您的服务器端代码看起来更好,但显然GWT会叮咬您。

我尝试在freenode(作为niloc132)上提供的解决方案是构建一个名为RemoteService的大型GeneralService类型和一个匹配的GeneralServiceAsync,每个类型都扩展了所有类型现有的rpc服务类型。我的第一个想法是使用<replace-with>告诉生成器系统当你希望每个RemoteService类型用GeneralService替换它时,但正如Tahir指出的那样,这没有意义 - GWT没有' t将重新绑定结果传回自身以继续进行查找。相反,我建议当您需要服务异步类型时,请执行以下操作:

AccountingServiceAsync service = (AccountingServiceAsync) GWT.create(GeneralService.class)

GeneralService的重新绑定结果将实现GeneralServiceAsyncAccountingServiceAsync本身可分配给GeneralServiceAsync。如果内存服务,则表示您拥有提供这些服务的静态方法/字段 - 将这些站点更改为始终创建GWT.create实例。只要您不在任何RemoteService子类型GeneralService上调用TypeSerializers,就会将RemoteServiceProxy的数量限制为一个。

作为旁注,*_TypeSerializer子类型是无状态的,因此确保只创建一个实例可能会更容易一致地构建,但不会节省运行时内存或时间,因为它们几乎可以肯定地编译为静态方法。 RemoteService类确实具有状态,但每个类只有一个实例,因此组合所有{{1}}可能会节省很少的工作内存。

答案 1 :(得分:3)

好吧,经过一对往返后,我们终于找到了解决我想解决的问题的方案,以防它可以帮助别人。 首先我要提到Colin Alworth的帮助,没有他的支持,这个解决方案根本不可能。 另外我要提到的是,我并不为最终解决方案感到自豪,但它对我们有用,而且目前是我们最好的。

我们最终做的是,正如Colin在上一篇文章中所说的那样,正在替换我们每个服务接口的GWT.create来创建GenericBigService接口。

所以我们的第一个补丁是这样的:

1)创建GenericBigService接口,扩展我们拥有的所有服务接口(目前有52个接口),并创建其Async兄弟。我们通过一个phytom脚本完成了这个。

所以我们的GenericBigInterface看起来像这样:

package com.arballon.gwt.core.client;

import com.google.gwt.user.client.rpc.RemoteService;

public interface GenericBigService extends RemoteService,
                                       AccountingService,
                                       ActionClassifierService,
                                       AFIPWebService,
                                       AnalyticalService,
                                       AuthorizationService,
                                       BudgetService,
                                       BusinessUnitService,
                                       CatalogPartService,
                                       CategoryService,
                                       ClientDepositService,
                                       .....
                                       .....
{ }

2)我们在每个Service接口中都有一个Util内部静态类来实现Async实例,在那里我们替换GWT.create来创建GenericBigInterface。

我们的一个服务接口如下所示:

public interface FinancialPeriodBalanceCategoryService extends RemoteService {
    /**
 * Utility class for simplifying access to the instance of async service.
 */
public static class Util {
    private static FinancialPeriodBalanceCategoryServiceAsync instance;
    public static FinancialPeriodBalanceCategoryServiceAsync getInstance() {
        if (instance == null) {
            instance = GWT.create(GenericBigService.class);
((ServiceDefTarget)instance).setServiceEntryPoint(GWT.getModuleBaseURL()+"FinancialPeriodBalanceCategoryService");
        }
        return instance;
    }
}

我们必须执行serServiceEntyPoint调用,以便不修改我们的web.xml。

当我们第一次编译它时,它编译好,但它不起作用,因为在运行时服务器调用抛出异常:

IncompatibleRemoteServiceException Blocked attempt to access interface GenericBigService 

,这不是由FinancialPeriodBalanceCategoryService

实现的

那是绝对正确的,我们用一个它没有实现的接口调用服务,这就是丑陋的部分出现的时候。 我们无法找到一个更好的解决方案,我们可以编码,我们决定实施的是:

我们用自己的副本替换RPC.java,我们替换代码如下:

在我们做的decodeRequest方法中

  if (type != null) {
    /*if (!implementsInterface(type, serviceIntfName)) {
      // The service does not implement the requested interface
      throw new IncompatibleRemoteServiceException(
          "Blocked attempt to access interface '" + serviceIntfName
              + "', which is not implemented by '" + printTypeName(type)
              + "'; this is either misconfiguration or a hack attempt");
    }*/
    if (!implementsInterface(type, serviceIntfName)) {
          if(!serviceIntfName.contains("GenericBigService")){
              throw new IncompatibleRemoteServiceException(
                      "Blocked attempt to access interface '" + serviceIntfName
                          + "', which is not implemented by '" + printTypeName(type)
                          + "'; this is either misconfiguration or a hack attempt");
          }
    }

这样做的好处是:

1)我们花了1小时20分钟来计算,只需要20分钟即可获得6个permutarions。

2)在devMode中,所有开始运行得更快。启动或多或少保持不变,但一旦启动就执行得非常好。

3)减少编译的大小是其他不小的有趣结果,我们将剩余的段从6Mb减少到1.2Mb,我们减少了aprox中JS大小的整个编译。 50%至60%。

我们对GWT-RPC非常满意并且我们不想离开它,但是typeSerializers实际上是一个问题,主要是因为JS的大小。 有了这个解决方案,我知道它不是很优雅,但是它有效,并且它可以工作。 再次感谢科林的帮助!

此致 丹尼尔

答案 2 :(得分:1)

对于任何GWT-RPC服务,GWt将生成一个Proxy,一个TypeSerializer。对于可能通过GWT传递的每个对象,您将拥有一个FieldSerializer类。并且每个类只能有一个FieldSerializer。因此,对于一个AccountDTO,您无法拥有两个FieldSerializer。

您尝试使用的延迟绑定规则将不起作用。例如,你有这样的事情: MyServiceAsync sync = GWT.create(MyService.class);

延迟绑定规则会将其更改为:

MyServiceAsync sync = new MyServiceAsync_Proxy();

你的规则实际上会做这样的事情:

MyServiceAsync sync = new MyGenericService(); //无效,因为MyGenericService是一个接口

所以你的解决方案不起作用。

既然你说有60%的应用程序生成代码是RPC相关的东西,我怀疑你有RPC类型爆炸问题。

检查GWT在编译期间是否没有抛出任何警告,或者为RPC TypeSerializers生成存根,很可能你在服务中有一些非常常见的接口。

答案 3 :(得分:1)

如果您想拥有更好的解决方案,为什么不使用命令模式。这样,您只需要一个接受Command子类型并返回Result子类型的GWT服务(您可以使用泛型使其类型安全)。

好消息是你只需要在一个gwt servlet中声明一个方法,然后从那里就可以调度到任何其他服务器端服务。

命令模式可以为您提供许多额外的好处,因为您有一个集中的控制点来进行安全检查或允许您透明地批量请求

因此,您在服务器端暴露于GWT会变得更小。

答案 4 :(得分:0)

据我所知,GWT代码生成应该提供接口的具体实现。然后将该实现转换为用于特定排列的javascript。

另一方面,您的样本正在用另一个接口替换一个接口。如果你从GWT编译器的眼睛看到它,也许你会看到这个配置的问题。

假设您是GWT编译器,并且您在客户端代码中看到要转换为JavaScript的以下行

AccountingServiceAsync accountingServiceAsync = (AccountingServiceAsync) GWT.create(AccountingService.class);
accountingServiceAsync.recordTransaction(transaction,callback);

所以你需要在第2行找出应该发生的事情。具体来说,你需要知道在哪里可以找到accountingServiceAsync.recordTransaction()的实现。因此,您将查看所有配置,以查找是否有规则指定应将哪个实现类用于AccountingService(而不是Async)。但遗憾的是你找不到任何东西。但是你注意到AccountingService也是一个RemoteService。所以你再次深入了解你的配置。而且,啊哈,有一条规则规定你可以generate RemoteService implementations with ServiceInterfaceProxyGenerator。您很高兴地将ServiceService的实现交给ServiceInterfaceProxyGenerator。

但是假设不是这个快乐的结局,你的配置告诉你AccountingService可以是replaced with GenericService,你说,“嘿,很酷,把它带上”。但就在那时你会发现GenericService也是一个接口。显然,你会被关闭,说“现在,我要用另一个接口做什么,我需要的只是AccountingService的一个实现”。在这一点上,你想通过向他发出一个神秘的错误来与程序员达成协议。

所以,这远远解释了为什么你的解决方案(理论上)不起作用的原因。至于你对膨胀的javascript的实际关注,我很惊讶这个问题甚至存在,因为GWT人员在优化编译的JavaScript时投入了大量精力。您是如何测试编译后的输出以进行复制的?