54 關於BeanUtils.copyProperties複製不生效

前言 

呵呵 前端時間使用 BeanUtils.copyProperties 的時候碰到了一個這樣的問題 

我有兩個實體, 有同樣的屬性, 一個有給定的屬性的 getter, 另外一個有 給定的屬性的 setter, 但是 我使用 BeanUtils.copyProperties 的時候 把來源對象的這個屬性 複製不到 目標對象上面 

然後 當時也跟蹤了一下代碼, 然後 這裏整理一下 改代碼片段吧 

然後在調試的過程中 也發現了一些其他的問題, 呵呵 算是額外的瞭解吧 

 

一下代碼基於 : jdk1.8.0_211 + commons-beanutils 1.9.4

 

問題的排查

首先來一段測試用例, 裏面主要包含了三個類, 一個測試類, 兩個實體類

package com.hx.test03;


import org.apache.commons.beanutils.BeanUtils;

/**
 * Test24BeanUtilsCopy
 *
 * @author Jerry.X.He <[email protected]>
 * @version 1.0
 * @date 2020-02-25 16:55
 */
public class Test24BeanUtilsCopy {

  // Test24BeanUtilsCopy
  // 1. 取的 source 的 propertyDescriptor
  // 2. get, set 對應的類型不匹配
  public static void main(String[] args) throws Exception {

    Test24ImmutableEntity fromImmutable = new Test24ImmutableEntity("fromImmutable");
    Test24MutableEntity fromMutable = new Test24MutableEntity("fromMutable");
    Test24MutableEntity targetEntity = new Test24MutableEntity("targetEntity");

    // does't work
    BeanUtils.copyProperties(targetEntity, fromImmutable);
    System.out.println(targetEntity.getAttr());
    // does't work
    BeanUtils.copyProperties(targetEntity, fromMutable);
    System.out.println(targetEntity.getAttr());

  }
}

package com.hx.test03;

/**
 * ImmutablePayment
 *
 * @author Jerry.X.He <[email protected]>
 * @version 1.0
 * @date 2020-02-25 16:32
 */
public class Test24ImmutableEntity {

  // attr
  private final String attr;

  public Test24ImmutableEntity(String attr) {
    this.attr = attr;
  }

  public String getAttr() {
    return attr;
  }

}
package com.hx.test03;

import java.util.Optional;

/**
 * ImmutablePayment
 *
 * @author Jerry.X.He <[email protected]>
 * @version 1.0
 * @date 2020-02-25 16:32
 */
public class Test24MutableEntity {

  // attr
  private String attr;

  public Test24MutableEntity(String attr) {
    this.attr = attr;
  }

  public Optional<String> getAttr() {
    return Optional.of(attr);
  }

//  public String getAttr() {
//    return attr;
//  }

  public void setAttr(String attr) {
    this.attr = attr;
  }

}

 

以上測試代碼輸出結果爲 : 

 

從測試代碼中可以看到這裏有兩個 BeanUtils.copyProperties 的使用, 並且兩個都沒有拷貝成功, 我們一個一個的來看 

首先是第一個 BeanUtils.copyProperties, 來源對象 和 目標對象分別爲 ImmutableEntity 和 MutableEntity 

ImmutableEntity 上面有 getAttr, MutableEntity 上面有 setAttr, 但是爲什麼沒有拷貝成功呢 ? 

 

在下圖的地方打一個斷點 調試一下 

調試發現 源對象是可讀的, 但是 目標對象不可寫?, 爲什麼呢?, 我們的 MutableEntity 不是有 setAttr 麼 

 

在 processPropertyDescriptor 方法之後, 我們發現 attr 屬性, 居然不可寫了 ?

 

具體到 processPropertyDescriptor 方法, 他主要乾的事情是 

// 1. 尋找 getter(存在多個merge) 
// First pass. Find the latest getter method. Merge properties
// of previous getter methods.

// 2. 尋找 setter(存在多個merge) 
// Second pass. Find the latest setter method which
// has the same type as the getter method.

// 3. merge getter & setter 
// At this stage we should have either PDs or IPDs for the
// representative getters and setters. The order at which the
// property descriptors are determined represent the
// precedence of the property ordering.

以上註釋來自於 Introspector.java, 1, 2, 3 的註釋來自於我 

我們這裏重點關注 step2, 需要找到 類型匹配 getter 類型的 setter 方法, 但是我們這裏的情況是 getter 返回值是 Optional, setter 返回值是 String, 因此類型不匹配 所以我們上面看到的結果是 有 getter, 沒得 setter 

實際的上下文信息如下圖 

 

以上便是 第一個 BeanUtils.copyProperties 不生效的原因了 

第二個 BeanUtils.copyProperties, 原因也是同上, 不過直觀的理解來說, attr 是有 getter 並且有 setter 的, 但是 由於規範的約定, 因此 propertyDescriptor 裏面有 getter, 沒得 setter 

 

 

問題的擴展

package com.hx.test03;


import org.apache.commons.beanutils.BeanUtils;

/**
 * BeanUtilsCopy
 *
 * @author Jerry.X.He <[email protected]>
 * @version 1.0
 * @date 2020-02-24 12:49
 */
public class Test23BeanUtilsCopy {

  // Test23BeanUtilsCopy
  // 1. 取的 source 的 propertyDescriptor
  // 2. get, set 對應的類型不匹配
  public static void main(String[] args) throws Exception {

    ImmutableEntity fromImmutable = new ImmutableEntity("fromImmutable");
    MutableEntity fromMutable = new MutableEntity("fromMutable");
    MutableEntity targetEntity = new MutableEntity("targetEntity");

    // does't work
    BeanUtils.copyProperties(targetEntity, fromImmutable);
    System.out.println(targetEntity.getAttr());
    // does't work
    BeanUtils.copyProperties(targetEntity, fromMutable);
    System.out.println(targetEntity.getAttr());

  }
}

/**
 * ImmutablePayment
 *
 * @author Jerry.X.He <[email protected]>
 * @version 1.0
 * @date 2020-02-24 12:50
 */
class ImmutableEntity {
  // attr
  private final String attr;

  public ImmutableEntity(String attr) {
    this.attr = attr;
  }

  public String getAttr() {
    return attr;
  }
}

/**
 * MutablePayment
 *
 * @author Jerry.X.He <[email protected]>
 * @version 1.0
 * @date 2020-02-24 12:54
 */
class MutableEntity {
  // attr
  private String attr;

  public MutableEntity(String attr) {
    this.attr = attr;
  }

//  public Optional<String> getAttr() {
//    return Optional.of(attr);
//  }
  public String getAttr() {
    return attr;
  }

  public void setAttr(String attr) {
    this.attr = attr;
  }
}

我們吧如上代碼 整理到同一個文件中(這其實才是第一個 demo, 上文中的是第二個 demo), 並且調整了 MutableEntity.getter 使其和 setter 的類型能夠匹配 

但是我們一跑, 發現結果還是有些出人意料 

 

BeanUtilsBean 如下地方打一個斷點 

我們發現這裏有一個奇怪的現象, 源對象不可讀, 目標對象不可寫??, 這是怎麼回事 ? 

 

以 ImmutableEntity. getAttr 爲例, 我們在 MethodUtils.getAccessableMethod 裏面如下地方打一個斷點 

我們發現 尋找目標的方法主要有圖中 三個地方 

第一個是當前類, 另外一個是當前類實現的接口, 另外一個是 當前類的基類(上圖還有未截取完的一部分, 限定 method 必須爲 public, 否則不允許訪問)  

1. 在當前類查詢 : 首先需要限定當前類是 public(我們這裏不滿足) public 允許訪問 

2. 當前類實現的接口查詢 : 獲取接口以及父接口中 匹配方法名字, 參數列表 的方法

3. 當前類的基類查詢 : 獲取基類以及更上的基類中, 並且是 public 的基類, 匹配方法名字, 參數列表 的方法

 

因此, 我們這裏的 第二個例子的 兩個 BeanUtils.copyProperties 也沒有生效 

呵呵 不知道這個限定類爲 public 的限定是否是 bug 呢?, 還是說 相關規範就是這麼約定的呢 ?

 

 

完 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章