Play 1.2.3 反序列化速度慢
Play 1.2.3 反序列化速度慢
刀狂剑痴 发表于2年前
Play 1.2.3 反序列化速度慢
  • 发表于 2年前
  • 阅读 25
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

继续之前讲的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上测试已经不存在了。可能是已经优化过了。

标签: play framework
共有 人打赏支持
粉丝 18
博文 111
码字总数 82582
×
刀狂剑痴
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: