文档章节

Play 1.2.3 反序列化速度慢

奋斗到天明
 奋斗到天明
发布于 2015/08/27 18:09
字数 1200
阅读 36
收藏 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
0
spring mvc采用fastjson后,List 泛型反序列化报错,如何解决?

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

hzwei206
2015/08/10
2.1K
3
Java序列化与JSON序列化大比拼

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

NoahX
2013/03/10
0
20
序列化框架比较:kryo & hessian & Protostuff & java

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

鉴客
2013/03/04
10.6K
0
从Spring + Ejb + Struts2 迁移到Play 1.x 框架 之一

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

刀狂剑痴
2015/08/27
129
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

kernel version does not match DSO version

错误信息: kernel version 384.11 does not match DSO version 384.130.0 原因是: cuda driver版本太低,不匹配DSO 简单有效的修复方法,升级nvidia driver, 步骤如下: 1. google seach ...

刘小米
今天
0
0
maven坐标和依赖

一、maven坐标详解 <groupId>com.fgt.club</groupId><artifactId>club-common-service-facade</artifactId><version>3.0.0</version><packaging>jar</packaging> maven的坐标元素说......

老韭菜
今天
1
0
springmvc-servlet.xml配置表功能解释

问:<?xml version="1.0" encoding="UTF-8" ?> 答: xml version="1.0"表示是此xml文件的版本是1.0 encoding="UTF-8"表示此文件的编码方式是UTF-8 问:<!DOCTYPE beans PUBLIC "-//SPRING//......

隐士族隐逸
今天
1
0
基于TP5的微信的公众号获取登录用户信息

之前讲过微信的公众号自动登录的菜单配置,这次记录一下在TP5项目中获取自动登录的用户信息并存到数据库的操作 基本的流程为:微信设置自动登录的菜单—>访问的URL指定的函数里获取用户信息—...

月夜中徘徊
今天
0
0
youTrack

package jetbrains.teamsys.license.runtime; 计算lis package jetbrains.ring.license.reader; 验证lis 安装后先不要生成lis,要把相关文件进行替换 ring-license-checker-1.0.41.jar char......

max佩恩
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部