GWT how can I reduce the size of code serializers for RPC calls

前端 未结 5 605
谎友^
谎友^ 2021-01-02 11:21

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

相关标签:
5条回答
  • 2021-01-02 11:53

    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.

    0 讨论(0)
  • 2021-01-02 11:54

    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 each FieldSerializer 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 reference instantiate(), deserialize() and serialize() 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 named GeneralService, and a matching GeneralServiceAsync, 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 with GeneralService, 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:

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

    The rebind result from GeneralService will implement GeneralServiceAsync, which is itself assignable to AccountingServiceAsync. If memory serves, you said that you have static methods/fields that provide these services - change those sites to always create a GeneralServiceAsync instance. As long as you do not invoke GWT.create on any RemoteService subtype but GeneralService, you will limit the number of TypeSerializers 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 your RemoteServices might save a very small amount of working memory.

    0 讨论(0)
  • 2021-01-02 11:54

    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

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

    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?

    0 讨论(0)
  • 2021-01-02 12:13

    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:

    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) 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:

    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;
        }
    }
    

    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:

    IncompatibleRemoteServiceException Blocked attempt to access interface GenericBigService 
    

    , 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:

      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");
              }
        }
    

    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

    0 讨论(0)
  • 2021-01-02 12:15

    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.

    0 讨论(0)
提交回复
热议问题