天天看點

拷貝工具BeanUtilsBean擴充拷貝工具BeanUtilsBean擴充

拷貝工具BeanUtilsBean擴充

同僚在使用ES的時候有這麼樣的需求:

  1. 拷貝對象時,如果源對象的屬性為null,則不拷貝該屬性到目标對象了;
  2. 源對象的屬性不為null才會将屬性拷貝到目标對象;
  3. 如果源對象的屬性是自定義的bean,那麼bean内的屬性也按照1、2那樣拷貝。

  這時,使用BeanUtils的copyProperties就不滿足要求,需要對該方法進行擴充。

解決上面問題的封裝類CopyProperties

  話不多說,上源碼

java CopyProperties.java

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 拷貝對象屬性工具
 * 将源對象中不為空的屬性拷貝到目标對象中,當目标對象的屬性值為null時,會先為改屬性建立一個對象,再将源對象的屬性拷貝過去
 * 注:不支援通過内部類定義屬性,因為通過反射建立對象有問題
 * @author wjy
 */
public class CopyProperties extends BeanUtilsBean {

  private static CopyProperties  INSTANCE = null;

  private static byte[] lock = new byte[];

  private CopyProperties(){}

  private static void init() {
    synchronized (lock) {
      if (null == INSTANCE) {
        INSTANCE = new CopyProperties();
      }
    }
  }

  public static CopyProperties getInstance() {
    if (null == INSTANCE) {
      init();
    }
    return INSTANCE;
  }

  private Log log = LogFactory.getLog(CopyProperties.class);

  //定義需要排掉的資料類型
  private final List<String> exceptTypeList = Arrays.asList("Boolean", "Byte", "Character", "Short", "Integer", "Long", "Float", "Double", "String");

  /**
   * 如果源對象的值為null,不對目标對象對應屬性做操作
   * @param bean 目标對象
   * @param name 屬性名
   * @param value 屬性值
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  @Override
  public void copyProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException {
    if (value == null) {
      return;
    }
    super.copyProperty(bean, name, value);
  }

  /**
   * 拷貝屬性方法
   * @param dest 目标對象
   * @param orig 源對象
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  @Override
  public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {

    // Validate existence of the specified beans
    if (dest == null) {
      throw new IllegalArgumentException ("No destination bean specified");
    }
    if (orig == null) {
      throw new IllegalArgumentException("No origin bean specified");
    }
    if (log.isDebugEnabled()) {
      log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")");
    }

    // Copy the properties, converting as necessary
    if (orig instanceof DynaBean) {
      DynaProperty origDescriptors[] = ((DynaBean) orig).getDynaClass().getDynaProperties();
      for (int i = ; i < origDescriptors.length; i++) {
        String name = origDescriptors[i].getName();
        if (getPropertyUtils().isWriteable(dest, name)) {
          Object value = ((DynaBean) orig).get(name);
          copyProperty(dest, name, value);
        }
      }
    } else if (orig instanceof Map) {
      Iterator names = ((Map) orig).keySet().iterator();
      while (names.hasNext()) {
        String name = (String) names.next();
        if (getPropertyUtils().isWriteable(dest, name)) {
          Object value = ((Map) orig).get(name);
          copyProperty(dest, name, value);
        }
      }
    } else /* if (orig is a standard JavaBean) */ {
      //修改了目标對象為JavaBean類型時,拷貝屬性的方法
      PropertyDescriptor origDescriptors[] = getPropertyUtils().getPropertyDescriptors(orig);
      for (int i = ; i < origDescriptors.length; i++) {
        String name = origDescriptors[i].getName();//對象的屬性名稱
        String type = origDescriptors[i].getPropertyType().getSimpleName();//對象的屬性類型
        if ("class".equals(name)) {
          continue; // No point in trying to set an object's class
        }
        if (!exceptTypeList.contains(type)) {
          //過濾出沒有被排掉的對象,也就是要處理的自定義對象
          //System.out.println(type + " is not base data type");
          Object deepDest = null;
          try {
            deepDest = getPropertyUtils().getProperty(dest, name);
          }catch (NoSuchMethodException e){
            ; // Should not happen
          }
          //如果目标對象中的屬性是null,需要為該屬性new一個對象
          if(deepDest == null){
            Object obj = null;
            try {
              Class classType = dest.getClass().getField(name).getType();
              obj = classType.newInstance();
            }catch (NoSuchFieldException e){
              ; // create object fail
            }catch (InstantiationException e){
              ; // create object fail
            }
            //反射建立對象失敗,後面拷貝對象會報錯
            if(obj==null){
              continue;
            }
            //将new的對象賦給dest對象
            origDescriptors[i].getWriteMethod().invoke(dest,obj);
            deepDest = obj;
          }
          //擷取源中的深層對象
          Object deepOrig = origDescriptors[i].getReadMethod().invoke(orig);
          if(deepOrig == null){
            System.out.println("No deep origin bean specified");
            continue;
          }
          //遞歸的将源的深層對象賦給目标深層對象
          copyProperties(deepDest,deepOrig);
          continue;
        }
        if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name)) {
          try {
            Object value = getPropertyUtils().getSimpleProperty(orig, name);
            //将值賦給目标對象名為name的屬性
            copyProperty(dest, name, value);
          } catch (NoSuchMethodException e) {
            ; // Should not happen
          }
        }
      }
    }
  }
}
           

Man.java

/**
 * 男人:姓名、年齡、性别、家庭
 * @author wjy
 */
public class Man {

  public String name;

  public Integer age;

  public Boolean sex;

  public Family family;

  public String getName() {
    return name;
  }

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

  public Integer getAge() {
  return age;
  }

  public void setAge(Integer age) {
  this.age = age;
  }

  public Boolean getSex() {
  return sex;
  }

  public void setSex(Boolean sex) {
  this.sex = sex;
  }

  public Family getFamily() {
  return family;
  }

  public void setFamily(Family family) {
  this.family = family;
  }

  @Override
  public String toString() {
    return "Man{" +
        "name='" + name + '\'' +
        ", age=" + age +
        ", sex=" + sex +
        ", family=" + family +
        '}';
  }
}
           

Family.java

/**
 * 家庭:老婆、孩子
 * @author wjy
 */

public class Family {

  public Family(){}

  public String wife;

  public String child;

  public String getWife() {
    return wife;
  }

  public void setWife(String wife) {
  this.wife = wife;
  }

  public String getChild() {
  return child;
  }

  public void setChild(String child) {
  this.child = child;
  }

  @Override
  public String toString() {
    return "Family{" +
        "wife='" + wife + '\'' +
        ", child='" + child + '\'' +
        '}';
  }
}
           

CopyTest.java

/**
 * 測試類
 * @author wjy
 */
public class CopyTest {

  public static void main(String[] args){

    Man man = new Man();
    man.setName("Rick");
    man.setSex(true);
    Family fam = new Family();
    fam.setWife("Ann");
    man.setFamily(fam);//下文會注釋掉的代碼段1.
    Man man1 = new Man();
    man1.setName("Frank");
    man1.setAge();
    Family fam1 = new Family();
    fam1.setWife("Carry");
    fam1.setChild("Wade");
    man1.setFamily(fam1);//下文會注釋掉的代碼段2.
    System.out.println("origin man:"+man);
    System.out.println("dest man1:"+man1);

    CopyProperties cp = CopyProperties.getInstance();
    try{
      cp.copyProperties(man1,man);
    }catch (Exception e){
      e.printStackTrace();
    }
    System.out.println("copy man to man1:"+man1);
  }
}
           

運作結果

  1. 當代碼1跟代碼2都不注釋時,會将man中非null的屬性指派到man1對應的屬性。
    拷貝工具BeanUtilsBean擴充拷貝工具BeanUtilsBean擴充
  2. 當隻有代碼1注釋時,會将man中非null的屬性指派到man1對應的屬性。

    由于family屬性是null,也是以不會對man1中family的屬性進行修改。

    拷貝工具BeanUtilsBean擴充拷貝工具BeanUtilsBean擴充
  3. 當隻有代碼2注釋時,會将man中非null屬性指派到man1對應的屬性。

    由于man1的family屬性是null,是以會先為man1建立一個family對象,再将man的family的非空屬性指派給man1的family的對應屬性。

    拷貝工具BeanUtilsBean擴充拷貝工具BeanUtilsBean擴充
  4. 當代碼1跟代碼2都注釋掉時,會将man中非null的屬性複制到man1對應的屬性。

    但是man、man1的family屬性都是null,是以man1的family隻會建立一個family對象,但是不會被man的family屬性覆寫掉。

    拷貝工具BeanUtilsBean擴充拷貝工具BeanUtilsBean擴充

如果有寫的不好的地方歡迎拍磚

源碼位址:https://github.com/weijiayou/JavaUtil_CopyProperties