GWT, как я могу уменьшить размер сериализаторов кода для вызовов RPC - PullRequest
8 голосов
/ 21 июля 2011

Я обнаружил, что более 60% кода javaScript, сгенерированного GWT в моем приложении, предназначен для сериализаторов RPC. Также я обнаружил, что сериализаторы не разделяются между сервисными интерфейсами, я имею в виду, если у меня, например, тип AccountDTO, на который ссылаются 2 сервисных интерфейса rpc, я получу 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>
    ...
    ...

Но в данный момент я получаю сообщение об ошибке:

[ОШИБКА] Ошибки в 'файле: / C: /Users/Daniel/EclipseWorkspace/ADK/src/com/arballon/gwt/core/client/FinancialService.java' [ОШИБКА] Строка 31: не удалось найти результат повторной привязки com.arballon.gwt.core.client.GenericService

Любые мысли по этому вопросу будут оценены. Привет

Daniel

Ответы [ 5 ]

4 голосов
/ 25 июля 2011

Код генерации RPC GWT строит несколько классов для выполнения своей работы, как вы заметили: *_FieldSerializer для каждого типа, который проходит по проводам, и *_Proxy класс для асинхронного типа RemoteService.Этот тип прокси требует *_TypeSerializer, что является корнем вашей проблемы - по какой-то причине GWT связывает все методы сериализации / десериализации в карте функции string-> js, вероятно, для облегчения быстрого поиска - но этот код установкипроисходит за счет строк кода, которые должны быть в окончательной сборке.В более оптимизированном подходе каждый FieldSerializer мог бы иметь метод регистрации, в котором он добавляет свои методы к статической карте, принадлежащей Прокси-серверу, однако это мешает, но оптимизация GWT состоит в том, чтобы не ссылаться на instantiate(), deserialize() иserialize() методов, если они не появляются, они будут вызваны.

Ваша проблема связана с наличием множества типов, которые можно сериализовать, и с тем, что вы пытались создать RemoteService типов, каждый из которых описывает определенные единицы функциональности, но повторно использовать многие типы моделей.Замечательная цель, особенно потому, что она, вероятно, сделает ваш серверный код более приятным, но, очевидно, GWT вас за это кусает.

Решение, которое я пытался предложить вам на freenode (как niloc132), заключалось в создании одного большогоRemoteService тип, который вы назвали GeneralService, и соответствующий GeneralServiceAsync, каждый из которых расширяет все существующие типы служб rpc.Моей первой мыслью было использование <replace-with>, чтобы сообщить системе генератора, что когда вы хотите, чтобы каждый тип RemoteService заменял его на GeneralService, но, как отмечает Тахир, это не имеет смысла - GWT не передает результаты повторного связывания.вернуться в себя, чтобы продолжать делать поиски.Вместо этого я хотел бы предложить, чтобы, когда вы хотите использовать асинхронный тип службы, выполните следующее:

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

В результате повторного связывания с GeneralService будет реализовано GeneralServiceAsync, которое само по себе присваивается AccountingServiceAsync.Если память служит, вы сказали, что у вас есть статические методы / поля, которые предоставляют эти услуги - измените эти сайты, чтобы всегда создавать GeneralServiceAsync экземпляр.Пока вы не вызываете GWT.create для любого подтипа RemoteService, кроме GeneralService, вы ограничите число TypeSerializers до одного.

В качестве примечания, подтипы RemoteServiceProxyбез сохранения состояния, поэтому обеспечение того, что вы создаете только один экземпляр, может упростить последовательную сборку, но не экономит оперативную память или время, поскольку они почти наверняка скомпилированы в статические методы.Классы *_TypeSerializer действительно имеют состояние, но существует только один экземпляр каждого, поэтому объединение всех ваших RemoteService может сэкономить очень небольшой объем рабочей памяти.

3 голосов
/ 05 сентября 2011

Что ж, после пары туров мы наконец нашли решение нашей проблемы, с которой я хочу поделиться в случае, если она может помочь другим. Прежде всего я должен упомянуть помощь Колина Алворта, без его поддержки это решение было бы вообще невозможно. Также я должен отметить, что я не очень горжусь окончательным решением, но оно работает для нас, и на данный момент это лучшее, что у нас есть.

Что мы наконец-то сделали, так это то, что, как отмечает Колин в прошлом посте, заменял GWT.create каждого из наших интерфейсов службы, чтобы создать вместо него интерфейс GenericBigService.

Итак, наш первый патч выглядит так:

1) Создайте интерфейс GenericBigService, который расширяет все имеющиеся у нас Сервисные интерфейсы (на данный момент 52 интерфейса), а также создайте его брата Async. мы сделали это через фитомный скрипт.

Итак, наш 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) У нас есть внутренний статический класс 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 минут на состязание, а на 6 перестановок - всего 20 минут.

2) В devMode все начинает работать быстрее. Запуск остается более или менее неизменным, но выполнение после его запуска идет очень хорошо.

3) Другим немаловажным результатом было уменьшение размера компиляции: мы сократили оставшийся сегмент с 6 МБ до 1,2 МБ, мы сократили размер всей компиляции JS в aprox. От 50% до 60%.

Мы действительно довольны GWT-RPC и не хотим его оставлять, но typeSerializer на самом деле была проблемой в основном из-за размера JS, который получается. С этим решением, я знаю, не очень элегантно, но оно работает, и оно работает хорошо. Еще раз спасибо Колин за вашу помощь!

С уважением Daniel

1 голос
/ 14 февраля 2013

Если вы хотите иметь более удачное решение, почему бы не использовать шаблон команды. таким образом, вам нужен только один сервис GWT, который принимает подтип Command и возвращает подтип Result (вы можете сделать его безопасным с помощью обобщений).

Приятно то, что вам нужно объявить только один метод в одном сервлете gwt, и оттуда вы можете отправлять его на любой другой серверный сервис.

Шаблон команды также может принести вам много дополнительных преимуществ, поскольку у вас есть центральная точка контроля для выполнения проверок безопасности или позволяет прозрачно пакетировать запросы

Таким образом, воздействие на вас GWT становится намного меньше на стороне сервера.

1 голос
/ 21 июля 2011

Для любой службы GWT-RPC GWt сгенерирует один прокси-сервер, один TypeSerializer. И для каждого объекта, который возможно может быть передан через GWT, у вас будет один класс FieldSerializer. И может быть только один FieldSerializer на класс. Таким образом, вы не сможете использовать два FieldSerializer для одного AccountDTO.

Отложенное правило привязки, которое вы пытаетесь использовать, не будет работать. Например, у вас есть что-то вроде этого: MyServiceAsync sync = GWT.create (MyService.class);

Отложенные правила привязки изменят его на:

MyServiceAsync sync = new MyServiceAsync_Proxy ();

Ваши правила на самом деле будут делать что-то вроде этого:

MyServiceAsync sync = new MyGenericService (); // недопустимо, поскольку MyGenericService является интерфейсом

Так что ваше решение не будет работать.

Поскольку вы говорите, что 60% кода, сгенерированного вашим приложением, связаны с RPC, я подозреваю, что у вас проблема взрыва типа RPC.

Проверьте, не выдает ли GWT какие-либо предупреждения во время компиляции, или сгенерируйте заглушки для RPC TypeSerializer, скорее всего, у вас есть очень распространенный интерфейс в обслуживании.

0 голосов
/ 21 июля 2011

Насколько я понимаю, генерация кода GWT должна обеспечивать конкретные реализации интерфейса.Эта реализация затем преобразуется в JavaScript для конкретных перестановок.

Ваш образец, с другой стороны, заменяет один интерфейс другим.Если вы видите это глазами компилятора GWT, возможно, вы увидите проблему с этой конфигурацией.

Предположим, вы - компилятор GWT, и вы видите следующую строку в коде на стороне клиента, который вы конвертируете в JavaScript

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

Итак, вам нужно выяснить, что должно произойти, в строке2. В частности, вам нужно знать, где найти реализацию accountingServiceAsync.recordTransaction ().Итак, вы просматриваете всю свою конфигурацию, чтобы найти, существует ли правило, определяющее, какой класс реализации следует использовать для AccountingService (не Async).Но, к сожалению, вы не найдете ни одного.Но затем вы замечаете, что AccountingService также является RemoteService.Итак, вы снова погрузитесь в свою конфигурацию.И, ага, вот оно, правило, указывающее, что вы можете генерировать реализации RemoteService с ServiceInterfaceProxyGenerator .Вы с радостью передаете задачу предоставления реализации AccountingService для ServiceInterfaceProxyGenerator.

Но предположим, что вместо этого счастливого конца ваша конфигурация говорит вам, что AccountingService можно заменить на GenericService, и вы говорите: "Эй, круто, включи".Но именно тогда вы обнаружите, что GenericService также является интерфейсом.Ясно, что вы будете отключены, говоря: «Теперь, что я собираюсь сделать с другим интерфейсом, все, что мне нужно, это реализация AccountingService».В этот момент вы захотите покончить с программистом, выдав ему зашифрованную ошибку.

Итак, пока все это объясняет, почему ваше решение (теоретически) не будет работать.Что касается вашей реальной озабоченности раздутым javascript, я удивлен, что эта проблема даже существует, учитывая количество усилий, приложенных людьми GWT для оптимизации скомпилированного JavaScript.Как вы проверили ваш скомпилированный вывод на дублирование?

...