文档章节

Play 1.2.3 反序列化速度慢

奋斗到天明
 奋斗到天明
发布于 2015/08/27 18:09
字数 1200
阅读 33
收藏 0
点赞 0
评论 0

继续之前讲的SSH+EJB迁移到Play 1.2.3上,今天出了一个很奇怪的BUG。 

在进行表单提交的时候,提交数据中有一个List列表数据,如果List数据多的话,服务端处理会很慢,有时甚至超时!比如10条的时候,处理时间就多达50多秒~要命的问题,在调试中发现,业务逻辑方面没有问题,代码编写处没有问题。而问题出在Play框架对请求参数的绑定上。即反序列化List对象。 

无奈只好有去啃源码……在源码中打入日志输出点后,发现List的元素出现重复设定。比如,如果该次请求List有十个元素,正常对列表List,应该只会调用set 10次。但是日志显示,大约有500次set,每个元素重复40多次……而该次请求的List元素类中有字段40多个,加上其他一些请求参数,正好500左右个K/V对。

也就是说play框架对请求参数中K/V参数对中,是该List的子孙元素都进行了一次set。 

为了验证上面的思路,我们可以看看源码,为了缩小篇幅,只显示BUG

相关的代码: Play/data/binding/Binder.java#bindInternal()

public static Object bindInternal(String name, Class clazz, Type type, Annotation[] annotations, Map params, String suffix, String[] profiles) {
  try {

    Logger.trace("bindInternal: name [" + name + "] suffix [" + suffix + "]");

    String[] value = params.get(name + suffix);
    Logger.trace("bindInternal: value [" + value + "]");
    Logger.trace("bindInternal: profile [" + Utils.join(profiles, ",") + "]");

    if (annotations != null) {... }

    // Arrays types
    // The array condition is not so nice... We should find another way of doing this....
    if (clazz.isArray() && (clazz != byte[].class && clazz != byte[][].class && clazz != File[].class && clazz != Upload[].class)) {...}
    // Enums
    if (Enum.class.isAssignableFrom(clazz)) {...}
    // Map
    if (Map.class.isAssignableFrom(clazz)) {...}
    // Collections types
    if (Collection.class.isAssignableFrom(clazz)) {         
      ...

      if (value == null) {
        value = params.get(name + suffix + "[]");
        if (value == null && r instanceof List) {

          //@1 遍历所有的参数
          for (String param : params.keySet()) {
            Pattern p = Pattern.compile("^" + escape(name + suffix) + "\\[([0-9]+)\\](.*)$");
            Matcher m = p.matcher(param);
            if (m.matches()) {
              int key = Integer.parseInt(m.group(1));

              //@2 根据上面匹配到的index值,对list进行扩容
              while (((List) r).size() <= key) {
                ((List) r).add(null);
              }
              if (isComposite(name + suffix + "[" + key + "]", params)) {
                BeanWrapper beanWrapper = getBeanWrapper(componentClass);

                //@3 跳转到BeanWrapper类,对key值所在的index位置的元素进行绑定。
                Object oValue = beanWrapper.bind("", type, params, name + suffix + "[" + key + "]", annotations);
                //@4 将绑定得到的结果set到list中
                ((List) r).set(key, oValue);
              } else {
                Map tP = new HashMap();
                tP.put("value", params.get(name + suffix + "[" + key + "]"));
                Object oValue = bindInternal("value", componentClass, componentClass, annotations, tP, "", value);
                if (oValue != MISSING) {
                  ((List) r).set(key, oValue);
                }
              }
            }
          }
          return r.isEmpty() ? MISSING : r;
        }
      }
      ...
    }
    ...
  } catch (Exception e) {
    Validation.addError(name + suffix, "validation.invalid");
    return MISSING;
  }
}


Play/data/binding/BeanWrapper.java#bind()

public Object bind(String name, Type type, Map params, String prefix, Object instance, Annotation[] annotations) throws Exception {
  //@1 遍历要绑定的元素的类的每个字段
  for (Property prop : wrappers.values()) {
    Logger.trace("beanwrapper: prefix [" + prefix + "] prop.getName() [" + prop.getName() + "]");
    for (String key : params.keySet()) {
      Logger.trace("key: [" + key + "]");
    }

    String newPrefix = prefix + "." + prop.getName();
    if (name.equals("") && prefix.equals("") && newPrefix.startsWith(".")) {
      newPrefix = newPrefix.substring(1);
    }
    Logger.trace("beanwrapper: bind name [" + name + "] annotation [" + Utils.join(annotations, " ") + "]");

    Object value = Binder.bindInternal(name, prop.getType(), prop.getGenericType(), prop.getAnnotations(), params, newPrefix, prop.profiles);
    if (value != Binder.MISSING) {
      if (value != Binder.NO_BINDING) {
        prop.setValue(instance, value);
      }
    } else {
      Logger.trace("beanwrapper: bind annotation [" + Utils.join(prop.getAnnotations(), " ") + "]");

      //@2 调用bindInternal绑定元素的字段
      value = Binder.bindInternal(name, prop.getType(), prop.getGenericType(), annotations, params, newPrefix, prop.profiles);
      Logger.trace("beanwrapper: value [" + value + "]");

      if (value != Binder.MISSING && value != Binder.NO_BINDING) {
         //@3 set字段的值
        prop.setValue(instance, value);
      }
    }
  }
  return instance;
}


可以看到两个类互相有引用,有点递归调用的意思,其逻辑大概如注释所说: Bind类中遍历所有的K/V,正则匹配到列表的index值,然后交给BeanWrapper去绑定这个index处的列表元素。 

而BeanWrapper类则遍历传入的类的字段,调用Bind类中的方法(在Bind中又会扫描一次K/V),绑定结束后,将元素返回 Bind类中接受返回的结果,并set 入list。 待所有的K/V遍历结束之后。

该列表绑定处理即结束。 如果理解了这个过程之后,就好处理了,在每次set之前增加一个判断即可。因为第一次set一个列表元素时,已经扫描了K/V中该index相关的元素字段,重复设定没有意义。 

代码修改就比较容易,在Binder.java中:

...

  //@2 根据上面匹配到的index值,对list进行扩容
  while (((List) r).size() <= key) {
    ((List) r).add(null);
  }
      
  //add to jump List element repeat bind hechaoyang@2014/10/14  
  if(((List) r).get(key) != null){
    continue;
  }  

  if (isComposite(name + suffix + "[" + key + "]", params)) {
    BeanWrapper beanWrapper = getBeanWrapper(componentClass);

    //@3 跳转到BeanWrapper类,对key值所在的index位置的元素进行绑定。
    Object oValue = beanWrapper.bind("", type, params, name + suffix + "[" + key + "]", annotations);
    //@4 将绑定得到的结果set到list中
     ((List) r).set(key, oValue);
  } else { 
  ...


为了验证效果,我们新建一个小工程,给了一个类型简单的类:

package models;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Mity {
  @Id
  public int id;
  public String name1;
  ...
  public String name39;
}


这里只给出Model的代码,是为了下面的效果。如想自测,请自行补充其他代码。 同样的10条测试数据在没有修改之前: ak1 

修改之后:

  ak2 

显然不是一个数量级别的,这个问题我在1.2.7和1.3.0上测试已经不存在了。可能是已经优化过了。

© 著作权归作者所有

共有 人打赏支持
奋斗到天明
粉丝 18
博文 112
码字总数 82707
作品 0
昌平
程序员
Serializable和Externalizable

Serailizable,类通过实现此接口使类对象可以被序列化,如把某对象保存到本地磁盘上,然后再从磁盘还原成jvm里的对象,代码如下: public static void main(String[] args) throws Exceptio...

伊森papa ⋅ 2013/10/17 ⋅ 0

spring mvc采用fastjson后,List 泛型反序列化报错,如何解决?

@wenshao 你好,想跟你请教个问题: 在spring mvc中,我用fastjson替换默认是Jackson,有个方法的反序列化出现问题,方法是这么声明的: @ResponseBody public BaseResp batchImportSomeThi...

hzwei206 ⋅ 2015/08/10 ⋅ 3

Java序列化与JSON序列化大比拼

一、背景 有项目需要传输Map结构的数据,有人倾向用Java序列化来做,有人倾向用JSON的序列化来做。所以我们还是比比吧。 Java观点:Object2Object,使用时简单快速。 JSON观点:JSON格式与语...

NoahX ⋅ 2013/03/10 ⋅ 20

序列化框架比较:kryo & hessian & Protostuff & java

序列化框架性能对比(kryo、hessian、java、protostuff) 简介: 优点 缺点 Kryo 速度快,序列化后体积小 跨语言支持较复杂 Hessian 默认支持跨语言 较慢 Protostuff 速度快,基于protobuf ...

鉴客 ⋅ 2013/03/04 ⋅ 0

从Spring + Ejb + Struts2 迁移到Play 1.x 框架 之一

原来项目比较古老,前台是用delphi,后台有用Ejb做……这货已经很少有人见过了……,现在公司主要项目都转到play上,所以这个项目也重构。 第一阶段是将SSE 迁移到play,尽量不改动代码,只要...

刀狂剑痴 ⋅ 2015/08/27 ⋅ 0

记忆模糊的知识点5-22

序列化 在程序运行的过程中,所有的变量都是在内存中,比如,定义一个dict: d = dict(name='Bob', age=20, score=88) 可以随时修改变量,比如把改成,但是一旦程序结束,变量所占用的内存就...

mrzengqq ⋅ 2017/05/22 ⋅ 0

Dribbble 第三方 Android 客户端 - Mango APP

芒果 Mango 是一款为 Dribbble 开发的 Android App , 灵感来源于 Resplash、Plaid 和 Protein 。 特色 Kotlin 和 RxKotlin: 完全由 Kotlin 编写开发 MVP 架构: 完全的 MVP 架构. Material De...

TonnyL ⋅ 2017/12/04 ⋅ 0

Android Intent (2) Serializable vs Parcelable

Android 主要是通过Intent来实现组件之间的相互调用,同时还可以传递附加的数据。这些数据主要是存储在Bundle之中(当调用Intent.putExtras(Bundle)时,Android会复制Bundle中的数据,而不是...

风荷举 ⋅ 2013/11/09 ⋅ 2

网络安全&加密方式的笔记&json序列化

1.网络安全:(1)网络中传输数据都会被监控到(2)本地存储也是不安全的 因为会被越狱破解 2.任何加密解密的操作就是对二进制进行操作 //加密-指定base64编码的方式0 //解密-忽略未知的字...

KevinEmily ⋅ 2016/02/23 ⋅ 0

【iOS】Plist-XML-JSON数据解析

网络上传输数据通用的有XML,JSON等,iOS中也可以用Plist。 要进行数据传输,就要首先进行序列化: 1.序列化. 对象转换成二进制流.(这个一句话就行) 2.反序列化. 二进制流转换为对象等. (关键...

xn4545945 ⋅ 2014/07/13 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

BS与CS的联系与区别【简】

C/S是Client/Server的缩写。服务器通常采用高性能的PC、工作站或小型机,并采用大型数据库系统,如Oracle、Sybase、InFORMix或 SQL Server。客户端需要安装专用的客户端软件。 B/S是Brower/...

anlve ⋅ 55分钟前 ⋅ 0

发生了什么?Linus 又发怒了?

在一个 Linux 内核 4.18-rc1 的 Pull Request 中,开发者 Andy Shevchenko 表示其在对设备属性框架进行更新时,移除了 union 别名,这引发了 Linus 的暴怒。 这一次 Linus Torvalds 发怒的原...

问题终结者 ⋅ 今天 ⋅ 0

在树莓派上搭建一个maven仓库

在树莓派上搭建一个maven仓库 20180618 lambo init 项目说明 家里有台树莓派性能太慢。想搭建一个maven私服, 使用nexus或者 jfrog-artifactory 运行的够呛。怎么办呢,手写一个吧.所在这个...

林小宝 ⋅ 今天 ⋅ 0

Spring发展历程总结

转自与 https://www.cnblogs.com/RunForLove/p/4641672.html 目前很多公司的架构,从Struts2迁移到了SpringMVC。你有想过为什么不使用Servlet+JSP来构建Java web项目,而是采用SpringMVC呢?...

onedotdot ⋅ 今天 ⋅ 0

Python模块/包/库安装(6种方法)

Python模块/包/库安装(6种方法) 冰颖机器人 2016-11-29 21:33:26 一、方法1: 单文件模块 直接把文件拷贝到 $python_dir/Lib 二、方法2: 多文件模块,带setup.py 下载模块包(压缩文件zip...

cswangyx ⋅ 今天 ⋅ 0

零基础学习大数据人工智能,学习路线篇!系统规划大数据之路?

大数据处理技术怎么学习呢?首先我们要学习Python语言和Linux操作系统,这两个是学习大数据的基础,学习的顺序不分前后。 Python:Python 的排名从去年开始就借助人工智能持续上升,现在它已经...

董黎明 ⋅ 今天 ⋅ 0

openJdk和sun jdk的区别

使用过LINUX的人都应该知道,在大多数LINUX发行版本里,内置或者通过软件源安装JDK的话,都是安装的OpenJDK, 那么到底什么是OpenJDK,它与SUN JDK有什么关系和区别呢? 历史上的原因是,Ope...

jason_kiss ⋅ 今天 ⋅ 0

梳理

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。 它是JS的状态容器,是一种解决问题的方式,所以即可以用于 react 也可以用于 vue。 需要理解其思想及实现方式。 应用中所有的 stat...

分秒 ⋅ 今天 ⋅ 0

Java 后台判断是否为ajax请求

/** * 是否是Ajax请求 * @param request * @return */public static boolean isAjax(ServletRequest request){return "XMLHttpRequest".equalsIgnoreCase(((HttpServletReques......

JavaSon712 ⋅ 今天 ⋅ 0

Redis 单线程 为何却需要事务处理并发问题

Redis是单线程处理,也就是命令会顺序执行。那么为什么会存在并发问题呢? 个人理解是,虽然redis是单线程,但是可以同时有多个客户端访问,每个客户端会有 一个线程。客户端访问之间存在竞争...

码代码的小司机 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部