關於Spring中的BeanUtils的使用的細節和由此導致的巨坑

關於Spring中的BeanUtils的使用的細節和由此導致的巨坑

前言

本文研究關於spring帶的BeanUtils的坑。

BeanUtils.copyProperties 的使用注意點

結論:

  1. 名字要相同
  2. 類型要相同(primitive type和對應包裝類同)
  3. Source必須有getter,且必須public
  4. Target必須有setter,且必須public
  5. Source中有static則不行(static和static final)
  6. Target中final的不會被賦值(final和static final)
  7. Source或Target中,若有父類,父類中的字段是可以複製的
  8. Source中內嵌的非普通類要單獨進行set(避免大坑

使用注意點

假設有個Source類,和一個Target類

Source轉換到Target,一般除了基礎類型的字段(8種原始類型+對應的包裝類+String),如果包含有其他類型的,使用要十分謹慎!!!

  • Source裏的字段名和Target裏的字段名,必須完全一樣,這個能夠轉換的第一個條件
  • Source裏的字段類型和Target裏的要嚴格相同,這是第二個條件
  • Source裏需要被拷貝的字段需要有getter,Target的字段,需要有setter,第三個條件
  • Source裏需要被拷貝的字段的getter,和Target裏的setter,都只能是public的(protected/private/不寫即default通通不行),第四個條件
  • Source裏的字段,不能是static的(含static final),第五條件
  • Target裏的字段,不能是final的(含static final),第六條件

注意:

  1. BeanUtils是淺拷貝,不是深拷貝,只是把引用弄過去

例如:Source中有A類的字段field,Target中也有A類的字段field,則Source中的field和Target中的field將會是相同的對象(哈希碼相同)

如果Source中有非普通字段,謹慎用BeanUtils,最好將內嵌的轉換後再set到外層,不然很多坑!!!

​ 2. Source裏的父類的字段,也能被拷貝

舉例

注意,無特別表示都表示Source有getter,Target有getter,且都是public的

Source Target 是否可以拷貝
字段名:age 字段名:age2
字段類型:int 字段類型:int
字段類型:int 字段類型:Integer
字段類型:Integer 字段類型:int
字段類型:int 字段類型:Long
字段類型:int 字段類型:long
字段類型:Long 字段類型:int
字段類型:boolean 字段類型:boolean
字段類型:boolean 字段類型:Boolean
字段類型:Boolean 字段類型:boolean
字段類型:Dog(繼承Animal) 字段類型:Animal
字段類型:List<A> 字段類型:List<B> 是。雖然有泛型,但能賦值過去,但存在嚴重隱患,見附錄
字段類型:Boolean 字段類型:Boolean
Source繼承Super1,Super1裏有name字段 Target繼承Super2,Super2裏也有name字段
Source繼承Super1,Super1裏有name字段 Target裏有name字段(不繼承)
Source有name字段(不繼承) Target繼承Super2,Super2裏有name字段
字段修飾:static,如static Integer age 任意修飾符:如static/static final/普通類型/final 否。(不拋異常,但讀取不了Source的static字段)
字段修飾:普通字段 任意修飾符:如static/普通類型(除final/static final) 是(但不能set到Target是final的)
字段修飾:final 任意修飾符:如static/普通類型(除final/static final) 是(但不能set到Target是final的)
  1. Source中的serialVersionUID(就是Serializable要求的那個字段),會出現什麼情況? 會覆蓋Target中的嗎? (好像不會吧,這個final的)(不會轉過去,因爲是static的

附錄

下面演示了一個巨大的坑

public class Test_A_Fucking_Problem {
    public static void main(String[] args) {
        Source source = new Source();
        Pet p1 = new Pet();
        p1.setName("dog");

        Pet p2 = new Pet();
        p2.setName("cat");

        List<Pet> petList = new ArrayList<>();
        petList.add(p1);
        petList.add(p2);
        source.setPetList(petList);

        Target target = new Target();
        BeanUtils.copyProperties(source, target);
        System.out.println("target:" + target + ",target.petList.hashCode:"
                + (target.getPetList() == null ? "未被賦值": target.getPetList().hashCode()) + ",petList.hashCode:" + petList.hashCode());

        // ★★需要非常注意的點,Source是List<A> 但是Target是List<B>,BeanUtils是可以把值賦值到target的,因爲都是List類型,它不會管泛型不同的
        // 但是,賦值過去,實際上是把對象的引用引過去,也就是 `List<B> petList` 字段其實是被賦值了 `List<A> petList` 的實例,在下面運行的過程就出現類轉換異常
        // get(0) 還不會出現異常,但是get(0)後再getName,則JVM需要弄清楚這個get(0)得到是什麼類型,當發現類型跟聲明的泛型類型不一樣的時候,就拋出了運行時異常(ClassCast...)
        System.out.println(target.getPetList().get(0));
        System.out.println(target.getPetList().get(0).getName());

    }
}
public class Source {
    private List<Pet> petList;

    public List<Pet> getPetList() {
        return petList;
    }

    public void setPetList(List<Pet> petList) {
        this.petList = petList;
    }
}

public class Target {
    private List<Pet2> petList;

    public List<Pet2> getPetList() {
        return petList;
    }

    public void setPetList(List<Pet2> petList) {
        this.petList = petList;
    }
}

public class Pet {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class Pet2 {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章