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


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

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

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


10350
2018-02-05 20:33


起源



答案:


让我扩展David Nouls所说的话。 GWT编译器无法读懂您的想法,因此当您未能指定返回类型的内容时,GWT会假定它可以是任何内容,并且必须做额外的工作以确保可以在Javascript客户端进行。

你真的应该指定哪些类型可以返回。这样做只有好处 - 因为编译器会生成更多优化的代码,而不是生成代码来处理'465 genreated单元',因此您的下载速度会更快。

我建议创建一个名为“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编译器可以更轻松地优化代码。


5
2018-02-09 03:07



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


答案:


让我扩展David Nouls所说的话。 GWT编译器无法读懂您的想法,因此当您未能指定返回类型的内容时,GWT会假定它可以是任何内容,并且必须做额外的工作以确保可以在Javascript客户端进行。

你真的应该指定哪些类型可以返回。这样做只有好处 - 因为编译器会生成更多优化的代码,而不是生成代码来处理'465 genreated单元',因此您的下载速度会更快。

我建议创建一个名为“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编译器可以更轻松地优化代码。


5
2018-02-09 03:07



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


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

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

尽管如此,还是需要一个解决方案!所以这就是我提出的: 1)使用IsSerializable(或其某些子类)而不是在所有自定义传输对象上使用java.io.Serializable。

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

这个解决方案对我有用......它既可以让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 list = rpcObject.getValue();

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

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

-Jeff

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);
    }
}

4
2017-08-02 06:31



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


如果你添加一个 ArrayList 或类似的 Object 对于可序列化对象的字段,GWT编译器别无选择,只能在其编译中包含所有可能的变体。你基本上是宣告 我可以使用这个字段发送任何内容,因此编译器确保您能够发送任何内容。

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


3
2018-02-05 22:01



我明白它想要做什么,但从搜索周围这是一个广泛混淆的事情。为什么GWT不能让我对我认为的Serializable有更细粒度的控制?在什么情况下当前的行为会有用?我无法想象只是盲目地拉入每种类型都是可以接受的情况。 - Sean
从GWT的角度来看,我认为很难正确地做到这一点。如果您对如何完成它有所了解,我建议您在GWT跟踪器中提出一个问题: code.google.com/p/google-web-toolkit/issues/entry - Robert Munteanu


你必须非常精确地回报你所返回的东西。典型的解决方案是使用根类或标记接口并声明RPC方法返回ArrayList,然后GWT可以减少可能的类型。


1
2018-02-06 18:10