GWT 如何减少 RPC 调用的代码序列化器的大小
我发现我的应用程序上 GWT 生成的 60% 以上的 javaScript 代码都是针对 RPC 序列化器的。 我还发现序列化程序不在服务接口之间共享,我的意思是,如果我在 2 个 rpc 服务接口上引用了 AccountDTO 类型,我将获得 2 个序列化程序类,而不是 1 个相同类型的序列化程序类。 为了减少编译代码的大小,我想也许我可以使用延迟绑定来替换一个大接口的所有服务接口。如果可能的话,也许 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>
...
...
但目前我收到错误:
[ERROR] 'file:/C:/Users/Daniel/EclipseWorkspace/ADK/src/com/arballon/gwt/core 中的错误/client/FinancialService.java' [错误]第 31 行:找不到重新绑定结果“com.arballon.gwt.core.client.GenericService”
任何有关此问题的想法将不胜感激。 问候
丹尼尔
I found that the more than 60% of the javaScript code generated by GWT on my application is for RPC serializers.
Also I found that serializers are not shared between service interfaces, I mean if I have for example AccountDTO type referenced on 2 rpc service interfaces, I will get 2 serializer classes instead of 1 for the same type.
In Order to reduce the size of the compiled code I was thinking that maybe I could use Deferred Binding in order to do a replacement of all the services interfaces I have for one big interface. If that could be possible, maybe then GWTCompiler will produce only one AccountDTO serializer instead of 2.
I'm not sure this is a good idea or if there is a better solution for my problem.
What I was trying to implement was something like this:
// 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>
...
...
But at the moment I was receiving the error:
[ERROR] Errors in 'file:/C:/Users/Daniel/EclipseWorkspace/ADK/src/com/arballon/gwt/core/client/FinancialService.java'
[ERROR] Line 31: Rebind result 'com.arballon.gwt.core.client.GenericService' could not be found
Any thoughts about the issue will be appreciated.
Regards
Daniel
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
正如您所注意到的,GWT 的 RPC 生成代码构建了几个类来完成其工作:为每种通过网络传输的类型创建一个
*_FieldSerializer
类,为通过网络传输的每种类型创建一个*_Proxy
类。 RemoteService 异步类型。该代理类型需要一个 *_TypeSerializer,这是问题的根源 - 出于某种原因,GWT 将所有序列化/反序列化方法连接到 string->js 函数映射中,可能是为了方便快速查找 - 但此设置代码是以最终构建中需要的代码行为代价的。更优化的方法可以让每个FieldSerializer
都有一个注册方法,它将其方法添加到代理拥有的静态映射中 - 然而,这受到了困扰,但是 GWT 的优化尝试不引用instantiate()、
deserialize()
和serialize()
方法(如果不出现)将被调用。您的问题源于有许多可以序列化的类型,以及您尝试构建每个描述特定功能单元的
RemoteService
类型,但重复使用许多模型类型。令人钦佩的目标,特别是因为它可能会让您的服务器端代码看起来更好,但显然 GWT 会因此而困扰您。我试图在 freenode(如 niloc132)上为您提供的解决方案是构建一个大型
RemoteService
类型(您将其命名为GeneralService
),以及一个匹配的GeneralServiceAsync
code>,每个都扩展了所有现有的 rpc 服务类型。我的第一个想法是使用
告诉生成器系统,当您希望每个 RemoteService 类型将其替换为GeneralService
时,但正如 Tahir 指出的那样,这没有意义 - GWT 不会将重新绑定结果传递回自身以继续进行查找。相反,我建议当您需要服务异步类型时,请执行以下操作:GeneralService
的重新绑定结果将实现GeneralServiceAsync
,它本身可分配给AccountingServiceAsync
。如果没记错的话,您说过您有提供这些服务的静态方法/字段 - 更改这些站点以始终创建 GeneralServiceAsync 实例。只要您不在除GeneralService
之外的任何RemoteService
子类型上调用GWT.create
,您就会限制TypeSerializers
的数量代码> 为一。附带说明一下,
RemoteServiceProxy
子类型是无状态的,因此确保仅创建一个实例可能会更容易一致地构建,但不会节省运行时内存或时间,因为它们几乎肯定会被编译为静态方法。然而,*_TypeSerializer 类确实有状态,但每个类只有一个实例,因此组合所有 RemoteService 可能会节省非常少量的工作内存。GWT's RPC generation code builds several classes to do its work as you've noted: a
*_FieldSerializer
for each type that goes over the wire, and a*_Proxy
class for the RemoteService async type. That proxy type requires a*_TypeSerializer
, which is the root of your problem - for some reason, GWT wires up all of the serialization/deserialization methods in a string->js function map, probably to facilitate fast lookups - but this setup code comes at the cost of lines of code that need to be in the final build. A more optimized approach could have eachFieldSerializer
have a registration method where it adds its methods to the static map owned by the Proxy - this is plagued, however, but GWT's optimization of attempting to not referenceinstantiate()
,deserialize()
andserialize()
methods if it doesnt appear they will be called.Your issue stems from having many types that can be serialized, and from your having attempted to build out
RemoteService
types that each describe specific units of functionality, but re-use many model types. Admirable goal, especially as it will probably make your server-side code look nicer, but apparently GWT bites you for it.The solution I attempted to offer you on freenode (as niloc132) was to build a single large
RemoteService
type, which you namedGeneralService
, and a matchingGeneralServiceAsync
, each extending all of the existing rpc service types. My first thought was to use a<replace-with>
to tell the generator system that when you want each RemoteService type to replace it withGeneralService
, but as Tahir points out, this doesn't make sense - GWT doesn't pass rebind results back into itself to keep doing lookups. Instead, I would suggest that when you want a service async type, do the following:The rebind result from
GeneralService
will implementGeneralServiceAsync
, which is itself assignable toAccountingServiceAsync
. If memory serves, you said that you have static methods/fields that provide these services - change those sites to always create aGeneralServiceAsync
instance. As long as you do not invokeGWT.create
on anyRemoteService
subtype butGeneralService
, you will limit the number ofTypeSerializers
to one.As a side note, the
RemoteServiceProxy
subtypes are stateless, so ensuring that you create only one instance might make it easier to build consistently, but saves no runtime memory or time, as they are almost certainly compiled out to static methods. The*_TypeSerializer
classes do have state however, but there is only one instance of each, so combining all of yourRemoteService
s might save a very small amount of working memory.好吧,经过几次往返之后,我们终于找到了解决问题的方法,我想与大家分享,以防它可以帮助其他人。
首先我必须提到 Colin Alworth 的帮助,没有他的支持,这个解决方案根本不可能实现。
另外我必须提到,我对最终的解决方案并不感到自豪,但它对我们有用,而且目前是我们拥有的最好的解决方案。
正如 Colin 在上一篇文章中所说,我们最终所做的是替换每个服务接口的 GWT.create 以创建 GenericBigService 接口。
所以我们的第一个补丁是这样的:
1)创建 GenericBigService 接口,它扩展了我们拥有的所有 Service 接口(目前有 52 个接口),并创建了它的 Async 兄弟。我们通过 phytom 脚本完成此操作。
所以我们的 GenericBigInterface 看起来像这样:
2) 我们在每个 Service 接口中都有一个 Util 内部静态类来实例化 Async 实例,在其中我们替换 GWT.create 来创建 GenericBigInterface。
我们的服务接口之一看起来像这样:
我们必须执行 serServiceEntyPoint 调用才能保持 web.xml 不被修改。
当我们第一次编译它时,它编译正常,但它不起作用,因为在运行时服务器调用抛出一个 Exception:
,这不是由 FinancialPeriodBalanceCategoryService 实现的
。这是绝对正确的,我们正在使用它未实现的接口调用服务,这就是丑陋的部分出现的时候。
我们无法找到更好的解决方案,我们可以编码,我们决定实现的解决方案是:
我们用我们自己的副本替换 RPC.java,并替换如下代码:
方法中,我们做了:
在decodeRequest 这样做的好处是:
1)我们花了 1 小时 20 分钟来比赛,而 6 个排列只需要 20 分钟。
2) 在 devMode 下,一切都开始运行得更快。启动过程或多或少保持不变,但启动后的执行非常顺利。
3)编译大小的减少是另一个不小的有趣结果,我们将剩余段从 6Mb 减少到 1.2Mb,我们减少了 aprox 中的整个 JS 编译大小。 50% 到 60%。
我们对 GWT-RPC 非常满意,并且不想离开它,但是 typeSerializers 确实是一个问题,主要是因为生成的 JS 的大小。
我知道这个解决方案不是很优雅,但它有效,而且效果很好。
再次感谢科林的帮助!
问候
丹尼尔
Well, after a pair of roundtrips we finally found a solution to our problem I want to share with in case it could help others.
First I have to mention the help of Colin Alworth, without his support this solution wouldn't be possible at all.
Also I have to mention that I'm not really proud of the final solution but it works for us and for the moment is the best we have.
What we finally did was, as Colin remarks on last post was replacing the GWT.create of each of our service interfaces to create instead the GenericBigService interface.
So our first patch goes like this:
1) Create GenericBigService interface which extends all Service interfaces we have (at the moment 52 interfaces), and also create its Async brother. we done this thru a phytom script.
So our GenericBigInterface looks like this:
2) We have an Util inner static class in each Service interface to instanciate the Async instance, in there we replace the GWT.create to create the GenericBigInterface.
One of our Service interfaces so looks like this:
we have to do the serServiceEntyPoint call in order to maintain our web.xml unmodified.
When we first compiles this it compiles ok, but it doesn't work because at runtime the server call throws an Exception:
, which is not implemented by FinancialPeriodBalanceCategoryService
Well that was absolutelly right we are calling the service with an interface it doesn't implement, and here is when the ugly part cames in.
We couldn't found a the moment a better solution we can code, that the one we decided to implement that is:
We replace RPC.java with our own copy and we replace the code like this:
in the decodeRequest method we did:
The benefit of doing this was :
1) we went to take an 1 hour and 20 minutes to compite to take only 20 minutes for 6 permutarions.
2) In devMode all starts to run more quickly. Startup remains more or less the same but execution once it starts goes really well.
3) Reduction in the size of compilation was other not minor interesting result, we reduce the left over segment from 6Mb to 1.2Mb, we reduce the whole compilation of JS size in aprox. 50% to 60%.
We are really happy with GWT-RPC and we don't want to leave it, but typeSerializers was really a problem basically because of the size of the JS that results.
With this solution, I know is not very elegant but it works, and it works grate.
Thanks again Colin for your help!
Regards
Daniel
对于任何 GWT-RPC Service,GWt 都会生成一个 Proxy、一个 TypeSerializer。对于每个可以通过 GWT 传递的对象,您将有一个 FieldSerializer 类。并且每个类只能有一个 FieldSerializer。因此,一个 AccountDTO 不可能有两个 FieldSerializer。
您尝试使用的延迟绑定规则将不起作用。例如你有这样的东西:
MyServiceAsync 同步 = GWT.create(MyService.class);
延迟绑定规则会将其更改为:
MyServiceAsyncsync = new MyServiceAsync_Proxy();
您的规则实际上会执行类似这样的操作:
MyServiceAsyncsync = new MyGenericService() ;//无效,因为 MyGenericService 是一个接口
所以您的解决方案将不起作用。
既然你说你的应用程序生成的代码中有 60% 是 RPC 相关的东西,我怀疑你有 RPC 类型爆炸问题。
检查 GWT 在编译期间是否不会抛出任何警告,或者为 RPC TypeSerializers 生成存根,很可能您在服务中有一些非常常见的接口。
For any GWT-RPC Service, GWt will generate one Proxy, one TypeSerializer. And for each object which possibly can be passed via GWT you will have one FieldSerializer class. And there can be only one FieldSerializer per class. So there is no way you can have two FieldSerializers for one AccountDTO.
Deferred binding rule which you trying to use will not work. For example you have something like this:
MyServiceAsync sync = GWT.create(MyService.class);
Deferred binding rules will change it into:
MyServiceAsync sync = new MyServiceAsync_Proxy();
Your rules will actually do something like this:
MyServiceAsync sync = new MyGenericService() ;//not valid since MyGenericService is an interface
So your solution will not work.
Since you are saying that 60% of you application generated code is RPC related stuff, I suspect you have RPC type explosion problem.
Check if GWT doesn't throws any warnings during compilation, or generate stubs for RPC TypeSerializers, most likely you've some very common interface in service.
如果您想要更好的解决方案,为什么不使用命令模式。这样,您只需要一个接受 Command 子类型并返回 Result 子类型的 GWT 服务(您可以通过使用泛型使其类型安全)。
好处是您只需要在一个 gwt servlet 中声明一种方法,然后您就可以分派到任何其他服务器端服务。
命令模式还可以给您带来很多额外的好处,因为您有一个中心控制点来执行安全检查或允许您透明地批处理请求,
因此您在服务器端对 GWT 的暴露变得小得多。
If you want to have a nicer solution, why not use a command pattern. that way you only need one GWT service that accepts a Command subtype and returns a Result subtype (you can make it typesafe by using generics).
The nice thing is that you only need to declare one method in one gwt servlet and from there on you can dispatch to any other server side service.
The command pattern can give you a lot of added benefit as well since you have a central point of control to do security checks or allows you to transparently batch requests
You exposure to GWT thus becomes much smaller on the server side.
据我了解,GWT 代码生成应该提供接口的具体实现。然后这个实现被转换成 JavaScript 来进行特定的排列。
另一方面,您的示例正在用一个接口替换另一个接口。如果您从 GWT 编译器的角度来看,也许您会发现此配置的问题。
假设您是 GWT 编译器,并且您在正在转换为 JavaScript 的客户端代码中看到以下行,
因此您需要在第 2 行找出应该发生什么。具体来说,您需要知道在哪里可以找到accountingServiceAsync 的实现。记录交易()。因此,您需要检查所有配置,以查找是否有规则指定应将哪个实现类用于 AccountingService(而不是异步)。但遗憾的是你没有找到。但随后您会注意到 AccountingService 也是一个 RemoteService。所以你再次深入你的配置。啊哈,这就是一条规则,指定您可以生成 RemoteService 实现使用 ServiceInterfaceProxyGenerator。您愉快地将提供 AccountingService 实现的任务移交给 ServiceInterfaceProxyGenerator。
但假设您的配置告诉您 AccountingService 可以替换,而不是这个幸福的结局使用 GenericService,你会说,“嘿,酷,开始吧”。但就在这时你发现GenericService也是一个接口。显然,您会被拒绝,并说“现在,我要使用另一个接口做什么,我需要的只是 AccountingService 的实现”。此时,您可能想通过向程序员抛出一个神秘的错误来报复他。
到目前为止,所有这些都解释了为什么您的解决方案(理论上)不起作用。就您对臃肿的 javascript 的实际担忧而言,考虑到 GWT 人们在优化编译的 JavaScript 方面付出的努力,这个问题甚至存在,我感到很惊讶。您如何测试编译输出的重复情况?
As far as I understand, the GWT code generation is supposed to supply concrete implementations of an interface. This implementation is then transformed into javascript for specific permutations.
Your sample, on the other hand, is replacing one interface with the other. If you see it from GWT compiler's eyes, perhaps you will see the problem with this configuration.
Suppose, you are the GWT compiler, and you see following line in client side code that you are converting into JavaScript
So you need to find out what should happen, at line 2. Specifically, you need to know where to find implementation of accountingServiceAsync.recordTransaction(). So you go looking into all your configuration to find if there is a rule specifying which implementation class should be used for AccountingService (not Async). But sadly you don't find any. But then you notice that AccountingService is also a RemoteService. So you dive into your configuration again. And, aha, there it is, a rule specifying that you can generate RemoteService implementations with ServiceInterfaceProxyGenerator. You happily hand over the task of providing an implementation of AccountingService to ServiceInterfaceProxyGenerator.
But suppose instead of this happy ending, your configuration tells you that AccountingService can be replaced with GenericService, and you say, "hey cool, bring it on". But just then you find out that GenericService is also an interface. Clearly, you'll be turned off, saying "now, what am I going to with another interface, all I needed was an implementation of AccountingService". At this point you'd want to get even with the programmer by throwing a cryptic error at him.
So, far all this explains why your solution (theoretically) won't work . As far as your actual concern of bloated javascript, I am amazed that this problem even exists given the amount of effort that GWT folks put in optimizing the compiled JavaScript. How did you tested your compiled output for duplication?