当我使用 ArrayList 时,如何防止 GWT 尝试包含每个可序列化的类

Posted

技术标签:

【中文标题】当我使用 ArrayList 时,如何防止 GWT 尝试包含每个可序列化的类【英文标题】:How can I keep GWT from trying to include every serializable class when I use ArrayList 【发布时间】:2010-02-05 20:33:24 【问题描述】:

我在 GWT 中有一个需要返回列表的 RPC 服务。 List 可以填充各种类型的对象,所有这些对象都是可序列化的,并且都在我的服务中的其他地方引用,因此它们应该可供 GWT RPC 使用。但是,除非我使用泛型类型参数(例如 ArrayList<String>),否则 GWT 会给我警告:

返回类型:java.util.ArrayList java.util.ArrayList 验证实例化 java.util.ArrayList [警告] 检查所有符合序列化条件的 Object 子类型 添加“465”个新生成的单位

本质上,我只是想要一种声明 List 或 ArrayList 的方法,而不需要 GWT 尝试为类路径上的每个可序列化对象生成代码。难道没有办法告诉 GWT 我知道自己在做什么,不要发疯吗?

【问题讨论】:

【参考方案1】:

让我扩展一下 David Nouls 所说的话。 GWT 编译器无法读懂您的想法,因此当您未能指定返回类型可以是什么时,GWT 假定它可以是任何东西,并且必须做额外的工作以确保可以在 javascript 客户端发生这种情况。

您确实应该指定能够返回的类型。这样做只有一个好处——因为编译器会生成更优化的代码,而不是生成处理“465 个类型化单元”的代码,因此您的下载速度会更快。

我建议创建一个名为“BaseResult”的空接口,然后让您返回的所有对象都实现该接口。

/**
 * Marker interface 
 */
public interface BaseResult 

然后你指定你的rpc方法的返回类型是ArrayList:

public interface MyRpcService extends RemoteService 
  public ArrayList<BaseResult> doRpc();

然后确保您的返回对象都实现了该接口。

public class UserInfo implements BaseResult 
public class Order implements BaseResult 

现在 GWT 编译器可以更轻松地优化您的代码。

【讨论】:

这似乎是一种合理的方法,缺点是它不适用于 Integer 或 String 之类的东西。我认为我正在尝试做的是一个有效的用例。我在服务器上有一个 API,通过 EJB、SOAP、AMF 和 GWT-RPC 公开。 API 上有一个“批处理”方法,它将采用多个方法名称和参数列表,并通过反射在服务器上的一次往返中以事务方式执行服务器上的所有 API 调用。不幸的是,由于这些方法可以接受任何参数,并且因为它们可以返回任何东西,所以我不能声明一个强泛型类型。【参考方案2】:

让 GWT 编译器为所有事物构建类型序列化器是不可取的;在最坏的情况下,它会完全失败,因为例如,可能有一个类(假设是您正在使用的第三方 GWT 库)在实现 java.io.Serializable 的“客户端”包中声明了一个类型.如果您尝试在代码中使用该类型,它将成为 GWT 编译器分析以构建类型序列化程序的类路径的一部分;但是,在运行时,该类不是服务器上类路径的一部分,因为该类型是在“客户端”包中定义的,因此不是为服务器编译的! RPC 调用,无论它们是否尝试使用该特定类型,都会失败并出现 ClassNotFound 异常。完美!

正如海报所阐明的那样,不可能使现有的原始类型实现一些标记接口,无论是 IsSerializable 还是自定义标记接口(例如上面建议的 BaseResult)。

但是,我们需要一个解决方案!所以这就是我想出的: 1) 使用 IsSerializable(或它的某个子类),而不是在所有自定义传输对象上使用 java.io.Serializable。

2) 在您需要通用对象类型来保存您知道将是 GWT-RPC 可序列化的值的那些实例中使用以下 RpcObject 实现(无论它是实现 IsSerializable 的自定义传输对象之一还是更多GWT 已经知道如何序列化的“原始”类型,例如 java.lang.String [请参阅下面 RpcObject 实现中的 cmets 以了解那些已被列入白名单的类型!)

这个解决方案对我有用...它既可以防止 GWT 为阳光下的每个 java.io.Serializable 类构建类型序列化器,同时又允许我作为开发人员使用单个 /基元的统一类型(我无法添加 IsSerializable 标记接口)以及我自己的自定义 IsSerializable 传输对象。下面是一个使用RpcObject的例子(虽然使用起来很简单,但是包含这样的例子我感觉有点奇怪):

RpcObject rpcObject = new RpcObject();
rpcObject.setValue("This is a test string");

感谢 getValue() 方法的 java-generics 技巧,可以将强制转换保持在最低限度,因此要检索值(无论是在客户端还是服务器上),您只需执行以下操作即可需要演员:

String value = rpcObject.getValue();

您可以轻松地传输您的自定义 IsSerializable 类型之一:

CustomDTO customDto= new CustomDTO(); // CustomDTO implements IsSerializable
customDto.setYourProperty(to_some_value);
RpcObject rpcObject = new RpcObject();
rpcObject.setValue(customDto);

同样,稍后在客户端或服务器上,可以轻松获取值(无需强制转换):

CustomDTO customDto = rpcObject.getValue();

你可以很容易地包装一些东西,比如 java.util.ArrayList:

List list = new ArrayList();  // Notice: no generics parameterization needed!
list.add("This is a string");
list.add(10);
list.add(new CustomDTO());

RpcObject rpcObject = new RpcObject();
rpcObject.setValue(list);

再一次,稍后在客户端或服务器代码中,您可以通过以下方式获取列表:

List list = rpcObject.getValue();

查看 RpcObject 中的“白名单”后,您可能倾向于认为只有 List&lt;String&gt; 会被列入白名单;你会错的 ;-) 只要添加到 List 的所有值都是 IsSerializable 或 GWT-RPC 只知道如何序列化的 JRE 类型的对象,那么你就会可以了,好了。但是,如果您确实需要将其他类型列入白名单,例如使用 java.io.Serializable 而不是 IsSerializable 的第三方库中的类型可能需要单独列入白名单(有关详细信息,请参阅 RpcObject 的实现) , 它们可以作为新字段直接添加到 RpcObject 中,或者为了在常见情况下保持较低的开销,将它们添加到 RpcObject 的子类中并仅在需要时使用该子类(因为它是子类,您的客户端或服务器都不需要)方法签名将需要更改为使用通用 RpcObject 类型)。

我正在使用这种策略来解决与原始海报描述的问题几乎相同的问题。我希望其他人也能发现它是一种有用的技术,但与往常一样,您的里程可能会有所不同......如果 GWT 思想学派已经超越了这种技术,请发表评论并让我知道!

-杰夫

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.gwt.user.client.rpc.IsSerializable;

public class RpcObject implements IsSerializable 
    protected HashMap<String, IsSerializable> rpcObjectWrapper = new HashMap<String, IsSerializable>();

    /*
     * NOTE: The following fields are here to
     * trick/fool/work-around/whatever-you-want-to-call-it GWT-RPC's
     * serialization policy. Having these types present, even though their
     * corresponding fields are never used directly, enables GWT-RPC to
     * serialize/deserialize these primitive types if they are encountered in
     * the rpcWrapperObject! Of course GWT-RPC already knows how to serialize
     * all these primitive types, but since, for example, String doesn't
     * implement GWT's IsSerializable interface, GWT has no expectation that it
     * should ever be allowed in the rpcWrapperObject instance (and thus String,
     * as well as all the other Java primitives plus Arrays of such types as
     * well as List, Set, and Map, won't be part of the serialization policy of
     * the RpcObject type). This is unfortunate because thanks to java type
     * erasure, we can easily stuff Strings, Integers, etc into the wrapper
     * without any issues; however, GWT-RPC will cowardly refuse to serialize
     * them. Thankfully, it appears that the serialization policy is for the
     * RpcObject type as a whole rather than for the rpcObjectWrapper field
     * specifically. So, if we just add some dummy fields with these "primitive"
     * types they will get added to the serialization policy (they are
     * effectively white-listed) of the type as a whole, and alas, GWT-RPC stops
     * cowardly refusing to serialize them.
     */
    protected Byte _byte;
    protected Short _short;
    protected Integer _integer;
    protected Long _long;
    protected Float _float;
    protected Double _double;
    protected Date _date;
    protected Boolean _boolean;

    protected Byte[] _bytes;
    protected Short[] _shorts;
    protected Integer[] _integers;
    protected Long[] _longs;
    protected Float[] _floats;
    protected Double[] _doubles;
    protected Date[] _dates;
    protected Boolean[] _booleans;

    protected List<String> _list;
    protected Set<String> _set;
    protected Map<String, String> _map;

    public RpcObject() 
        super();
    

    @SuppressWarnings("unchecked")
    public <X> X getValue() 
        HashMap h = (HashMap) rpcObjectWrapper;
        X value = (X) h.get("value");
        return value;
    

    @SuppressWarnings("unchecked")
    public void setValue(Object value) 
        HashMap h = (HashMap) rpcObjectWrapper;
        h.put("value", value);
    

【讨论】:

@Robert - 我应该更清楚地利用这种技术来解决您的具体问题。有很多方法;例如,您可以模仿 RpcObject 使用的技巧以某种方式创建自定义 ArrayList 子类...但是,解决方案的重点是使用 RpcObject 而不是 ArrayList 作为方法参数的类型或传输对象中的字段类型,然后通过 RpcObject.setValue() 方法“包装”您的“通用”ArrayList(不是java-generics参数化的)。只要你的 ArrayList 中的所有值都可以被 GWT 序列化,你就应该全部设置好。【参考方案3】:

如果将ArrayList 或类似的Object 字段添加到可序列化对象,GWT 编译器别无选择,只能在其编译中包含所有可能的变体。您实际上是在声明 我可以使用此字段发送任何内容,因此编译器会确保您能够发送任何内容。

解决方案是使用泛型参数声明您要发送的特定类型。这可能需要拆分为多个参数或类,但它确实可以减少代码大小和编译时间。

【讨论】:

我理解它试图做什么,但是通过搜索这是一件令人困惑的事情。为什么 GWT 不能让我对我认为可序列化的内容进行更细粒度的控制?在什么情况下,当前的行为会有用?我无法想象盲目地拉入每种类型的情况是可以接受的。 我认为从 GWT 的角度正确地做到这一点是相当困难的。如果您对如何完成它有任何想法,我建议您在 GWT 跟踪器中提出问题:code.google.com/p/google-web-toolkit/issues/entry【参考方案4】:

您必须非常精确地返回结果以帮助 GWT。一个典型的解决方案是使用根类或标记接口,并声明 RPC 方法返回一个 ArrayList,然后 GWT 可以修剪可能的类型。

【讨论】:

以上是关于当我使用 ArrayList 时,如何防止 GWT 尝试包含每个可序列化的类的主要内容,如果未能解决你的问题,请参考以下文章

在 gwt 中,如何导出 ArrayList<String> 并提示保存对话框供用户下载?

检索 ArrayList 时出现 GWT-RPC 无法解释的 500 状态错误

GWT 序列化异常

如何在悬停时增加 gwt menuitem

序列化为 GWT AutoBean 时如何保留 LinkedHashMap 的顺序?

如何正确地将复杂属性 (ArrayList<POJO>) 设置为 GWT BaseTreeModel?序列化问题