Проблемы при передаче объектов класса через GWT RPC

Я пробежался по учебному пособию по Google Web Toolkit StockWatcher, используя Eclipse и плагин Google, и пытаюсь внести в него некоторые базовые изменения, чтобы лучше понять структуру RPC.

Я изменил метод getStocks в серверном классе StockServiceImpl, чтобы он возвращал массив объектов Stock вместо объектов String. Приложение отлично компилируется, но Google Web Toolkit возвращает следующую ошибку:

"Исходный код для типа com.google.gwt.sample.stockwatcher.server.Stock недоступен; вы забыли унаследовать необходимый модуль?"

http://i44.tinypic.com/a47r83.jpg

Кажется, что клиентские классы не могут найти реализацию объекта Stock, даже если класс был импортирован. Для справки вот скриншот моей иерархии пакетов:

http://i43.tinypic.com/14tr5gk.jpg

Я подозреваю, что мне чего-то не хватает в web.xml, но я понятия не имею, что это такое. Может кто-то указать мне верное направление?

РЕДАКТИРОВАТЬ: забыл упомянуть, что класс Stock является постоянным, поэтому он должен оставаться на стороне сервера.

8 ответов

Решение

После долгих проб и ошибок мне удалось найти способ сделать это. Это может быть не лучшим способом, но это работает. Надеюсь, этот пост может сэкономить кому-то еще много времени и сил.

В этих инструкциях предполагается, что вы завершили как базовое руководство по StockWatcher, так и модификации StockWatcher для Google App Engine.

Создание клиентской реализации класса акций

Есть несколько вещей, которые нужно помнить о GWT:

  1. Серверные классы могут импортировать клиентские классы, но не наоборот (обычно).
  2. Клиентская сторона не может импортировать любые библиотеки Google App Engine (например, com.google.appengine.api.users.User).

Благодаря обоим пунктам выше, клиент никогда не сможет реализовать класс Stock, который мы создали в com.google.gwt.sample.stockwatcher.server. Вместо этого мы создадим новый класс Stock на стороне клиента с именем StockClient.

StockClient.java:

package com.google.gwt.sample.stockwatcher.client;

import java.io.Serializable;
import java.util.Date;

public class StockClient implements Serializable {

  private Long id;
  private String symbol;
  private Date createDate;

  public StockClient() {
    this.createDate = new Date();
  }

  public StockClient(String symbol) {
    this.symbol = symbol;
    this.createDate = new Date();
  }

  public StockClient(Long id, String symbol, Date createDate) {
    this();
    this.id = id;
    this.symbol = symbol;
    this.createDate = createDate;
  }

  public Long getId() {
      return this.id;
  }

  public String getSymbol() {
      return this.symbol;
  }

  public Date getCreateDate() {
      return this.createDate;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public void setSymbol(String symbol) {
      this.symbol = symbol;
  }
}

Измените классы клиента, чтобы использовать StockClient[] вместо String[]

Теперь мы сделаем несколько простых изменений в клиентских классах, чтобы они знали, что вызов RPC возвращает StockClient[] вместо String[].

StockService.java:

package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.sample.stockwatcher.client.NotLoggedInException;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("stock")
public interface StockService extends RemoteService {
  public Long addStock(String symbol) throws NotLoggedInException;
  public void removeStock(String symbol) throws NotLoggedInException;
  public StockClient[] getStocks() throws NotLoggedInException;
}

StockServiceAsync.java:

package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.sample.stockwatcher.client.StockClient;
import com.google.gwt.user.client.rpc.AsyncCallback;

public interface StockServiceAsync {
  public void addStock(String symbol, AsyncCallback<Long> async);
  public void removeStock(String symbol, AsyncCallback<Void> async);
  public void getStocks(AsyncCallback<StockClient[]> async);
}

StockWatcher.java:

Добавить один импорт:

import com.google.gwt.sample.stockwatcher.client.StockClient;

Все остальные коды остаются прежними, кроме addStock, loadStocks и displayStocks:

private void loadStocks() {
    stockService = GWT.create(StockService.class);
    stockService.getStocks(new AsyncCallback<String[]>() {
        public void onFailure(Throwable error) {
            handleError(error);
        }

        public void onSuccess(String[] symbols) {
            displayStocks(symbols);
        }
    });
}

private void displayStocks(String[] symbols) {
    for (String symbol : symbols) {
        displayStock(symbol);
    }
}

private void addStock() {
    final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
    newSymbolTextBox.setFocus(true);

    // Stock code must be between 1 and 10 chars that are numbers, letters,
    // or dots.
    if (!symbol.matches("^[0-9a-zA-Z\\.]{1,10}$")) {
        Window.alert("'" + symbol + "' is not a valid symbol.");
        newSymbolTextBox.selectAll();
        return;
    }

    newSymbolTextBox.setText("");

    // Don't add the stock if it's already in the table.
    if (stocks.contains(symbol))
        return;

    addStock(new StockClient(symbol));
}

private void addStock(final StockClient stock) {
    stockService.addStock(stock.getSymbol(), new AsyncCallback<Long>() {
        public void onFailure(Throwable error) {
            handleError(error);
        }

        public void onSuccess(Long id) {
            stock.setId(id);
            displayStock(stock.getSymbol());
        }
    });
}

Измените класс StockServiceImpl, чтобы он возвращал StockClient[]

Наконец, мы модифицируем метод getStocks класса StockServiceImpl, чтобы он преобразовывал классы Stock на стороне сервера в классы StockClient на стороне клиента перед возвратом массива.

StockServiceImpl.java

import com.google.gwt.sample.stockwatcher.client.StockClient;

Нам нужно немного изменить метод addStock, чтобы сгенерированный идентификатор был возвращен:

public Long addStock(String symbol) throws NotLoggedInException {
  Stock stock = new Stock(getUser(), symbol);
  checkLoggedIn();
  PersistenceManager pm = getPersistenceManager();
  try {
    pm.makePersistent(stock);
  } finally {
    pm.close();
  }
  return stock.getId();
}

Все остальные методы остаются прежними, кроме getStocks:

public StockClient[] getStocks() throws NotLoggedInException {
  checkLoggedIn();
  PersistenceManager pm = getPersistenceManager();
  List<StockClient> stockclients = new ArrayList<StockClient>();
  try {
    Query q = pm.newQuery(Stock.class, "user == u");
    q.declareParameters("com.google.appengine.api.users.User u");
    q.setOrdering("createDate");
    List<Stock> stocks = (List<Stock>) q.execute(getUser());
    for (Stock stock : stocks)
    {
       stockclients.add(new StockClient(stock.getId(), stock.getSymbol(), stock.getCreateDate()));
    }
  } finally {
    pm.close();
  }
  return (StockClient[]) stockclients.toArray(new StockClient[0]);
}

Резюме

Приведенный выше код отлично работает для меня при развертывании в Google App Engine, но вызывает ошибку в режиме хостинга Google Web Toolkit:

SEVERE: [1244408678890000] javax.servlet.ServletContext log: Exception while dispatching incoming RPC call
com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract com.google.gwt.sample.stockwatcher.client.StockClient[] com.google.gwt.sample.stockwatcher.client.StockService.getStocks() throws com.google.gwt.sample.stockwatcher.client.NotLoggedInException' threw an unexpected exception: java.lang.NullPointerException: Name is null

Дайте мне знать, если вы столкнулись с той же проблемой или нет. Тот факт, что он работает в Google App Engine, указывает на ошибку в размещенном режиме.

GWT нужен файл.java в дополнение к файлу.class. Кроме того, Stock должен находиться в "клиентском" месте модуля GWT.

Компилятор GWT не знает о Stock, потому что он находится не в том месте, в котором он просматривает. Вы можете либо переместить его в папку клиента, либо, если это имеет больше смысла, оставить его там, где он находится, и создать ModuleName.gwt.xml, который ссылается на любые другие классы, которые вы хотите, и получите ваш файл Main.gwt.xml для наследования от него.

например: DomainGwt.gwt.xml

<module>
    <inherits name='com.google.gwt.user.User'/>
    <source path="javapackagesabovethispackagegohere"/>
</module>

а также:

<module rename-to="gwt_ui">
    <inherits name="com.google.gwt.user.User"/>
    <inherits name="au.com.groundhog.groundpics.DomainGwt"/>

    <entry-point class="au.com.groundhog.groundpics.gwt.client.GPicsUIEntryPoint"/>
</module>

Здесь есть лучший ответ: проблема варианта использования GWT Simple RPC: включенный код

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

Я получил ту же проблему, и вывод "mvn gwt: compile" не очень помог. Вместо этого, когда я попытался выполнить развертывание на tomcat (через плагин maven tomcat: mvn tomcat:deploy), я получил полезные сообщения об ошибках.

Несколько вещей, которые я должен был исправить:

  1. Сделать объект, который отправляется с клиента на сервер, реализовать Serializable
  2. Добавить конструктор пустого аргумента к тому же объекту

Да, мы уверены, что нам нужно использовать сериализацию для передачи объектов сервера клиенту. Эти модилы?? Настройки файла не будут работать для использования класса Stock на стороне клиента.

В вашем случае у вас есть только один класс Stock, и вы можете создать StockClient на стороне клиента. Это легко. Но каким будет решение, если у кого-то будет больше занятий. Нечто похожее на свойства этого класса также является другим типом классов.

Пример: stock.getEOD(date).getHigh();

getEOD вернет другой класс с указанной датой, и этот класс имеет getHigh метод.

Что делать в таких больших случаях? Я не думаю, что создание всех классов, реализующих сериализацию на стороне клиента, хорошо для этого. Затем мы должны написать код на сервере и клиенте. все занятия два раза.

Уклоняясь от ответа Рустишельфа выше...

В моем случае мне нужно было отредактировать файл ModuleName.gwt.xml и добавить следующее:

<source path='client'/>
<source path='shared'/>

Я создал свой проект с помощью мастера New->Web Application Project, но снял отметку с опции " Создать пример кода проекта". Затем я создал общий пакет. Если бы я не убрал галочку, пакет был бы создан для меня, а файл xml был изменен в соответствии с вышеизложенным.

Существует гораздо более простое и легкое решение для этого. Если вы хотите отправить объект вашего пользовательского класса с серверной стороны на клиентскую, вы должны определить этот пользовательский класс в общем пакете.

Например, для вашего случая вам просто нужно перенести класс Stock.java (путем перетаскивания) в

com.google.gwt.sample.stockwatcher.shared

пакет. Однако из скриншота иерархии вашего пакета кажется, что вы удалили этот общий пакет. Просто заново создайте этот пакет, поместите в него Stock.java и начните игру.

Другие вопросы по тегам