无需服务器包装,如何在非GWT桌面Java应用调用GWT服务?
当然可以做到!不用额外编写服务器端包装代码,就能让普通Java桌面应用调用你的GWT服务——核心是搞懂GWT RPC的通信本质,然后模拟它的请求格式就行。
GWT RPC本质上是基于HTTP POST的自定义序列化协议:GWT客户端把调用的方法名、参数序列化成特定格式的字符串,发送到服务器端的RemoteServiceServlet;服务器处理后再把结果或异常序列化返回。普通Java应用只要能复现这个请求和解析流程,就能直接调用服务。
第一步:确认服务的请求地址
先找到你的GWT服务对应的HTTP端点。通常这个地址是/你的应用上下文/服务接口的全限定名,比如你的服务接口是com.example.MyService,应用部署在http://your-server/myapp,那么请求地址就是http://your-server/myapp/com.example.MyService。你也可以通过浏览器开发者工具查看GWT客户端发送的请求URL来确认。第二步:复用GWT的序列化工具
你需要在普通Java项目中引入GWT的gwt-servlet依赖(这是服务器端的jar,包含了序列化相关的API)。然后直接复用你现有的服务接口、参数类和结果类——这些类本来就属于服务器端代码,直接拷贝到桌面应用的类路径里即可。下面是一个手动实现的示例代码:
import com.google.gwt.user.server.rpc.ServerSerializationStreamReader; import com.google.gwt.user.server.rpc.ServerSerializationStreamWriter; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; public class GwtRpcClientExample { public static void main(String[] args) throws Exception { // 1. 构造序列化的请求内容 ServerSerializationStreamWriter writer = new ServerSerializationStreamWriter(); writer.prepareToWrite(); // 写入要调用的方法签名:全限定接口名.方法名 writer.writeString("com.example.MyService.myBusinessMethod"); // 写入方法参数,顺序和接口定义一致 writer.writeObject("参数1"); writer.writeObject(123); byte[] requestBody = writer.toString().getBytes(StandardCharsets.UTF_8); // 2. 发送HTTP POST请求 HttpClient httpClient = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://your-server/myapp/com.example.MyService")) // GWT RPC要求的Content-Type .header("Content-Type", "text/x-gwt-rpc; charset=utf-8") .POST(HttpRequest.BodyPublishers.ofByteArray(requestBody)) .build(); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); // 3. 解析响应内容 ServerSerializationStreamReader reader = new ServerSerializationStreamReader(); reader.prepareToRead(response.body()); String responseStatus = reader.readString(); if ("OK".equals(responseStatus)) { // 读取返回结果,强转为对应的类型 MyServiceResult result = (MyServiceResult) reader.readObject(); System.out.println("调用成功:" + result); } else { // 读取服务抛出的异常 Throwable exception = (Throwable) reader.readObject(); System.err.println("调用失败:" + exception.getMessage()); } } }第三步:注意关键细节
- 序列化白名单:确保你的参数类、结果类都在GWT的序列化白名单里——既然原来的GWT客户端能正常调用,说明这些类已经被包含在服务器端的
SerializationPolicy中,不用额外配置。 - 认证与Session:如果你的服务需要用户登录,要在请求中带上对应的Session Cookie(比如
JSESSIONID),和浏览器请求中的Cookie保持一致,可以通过HttpRequest.header("Cookie", "JSESSIONID=xxx")添加。 - 依赖兼容:确保桌面应用中
gwt-servlet的版本和你的GWT应用版本一致,避免序列化格式不兼容的问题。
- 序列化白名单:确保你的参数类、结果类都在GWT的序列化白名单里——既然原来的GWT客户端能正常调用,说明这些类已经被包含在服务器端的
如果不想手动处理HTTP请求和序列化,可以使用第三方的gwt-rpc-client库,它封装了GWT RPC的客户端逻辑,让调用更贴近GWT客户端的写法:
import com.google.gwt.user.client.rpc.AsyncCallback; import de.novanic.gwt.rpc.client.GwtRpcClient; import de.novanic.gwt.rpc.client.GwtRpcProxy; public class SimplifiedGwtRpcClient { public static void main(String[] args) { GwtRpcClient rpcClient = new GwtRpcClient("http://your-server/myapp/"); MyServiceAsync service = GwtRpcProxy.create(MyServiceAsync.class, rpcClient, "com.example.MyService"); service.myBusinessMethod("参数1", 123, new AsyncCallback<MyServiceResult>() { @Override public void onSuccess(MyServiceResult result) { System.out.println("调用成功:" + result); } @Override public void onFailure(Throwable caught) { System.err.println("调用失败:" + caught.getMessage()); } }); } }
内容的提问来源于stack exchange,提问作者MTilsted

