Проблемы при передаче объектов класса через 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:
- Серверные классы могут импортировать клиентские классы, но не наоборот (обычно).
- Клиентская сторона не может импортировать любые библиотеки 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), я получил полезные сообщения об ошибках.
Несколько вещей, которые я должен был исправить:
- Сделать объект, который отправляется с клиента на сервер, реализовать Serializable
- Добавить конструктор пустого аргумента к тому же объекту
Да, мы уверены, что нам нужно использовать сериализацию для передачи объектов сервера клиенту. Эти модилы?? Настройки файла не будут работать для использования класса 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 и начните игру.