天天看點

基于反射進行對象屬性的拷貝

    在實際的工作中,有時可能存在2個java bean屬性之間的拷貝,而如果使用bean 之間setter方法進行設定,那麼将會存在大量的備援的代碼,是以可以考慮使用反射來進行屬性的拷貝操作。

大緻思路如下:

1、從class檔案中,擷取到所有的public類型的方法

2、擷取到所有的getter方法和setter方法,getter方法的擷取需要考慮到boolean類型這個比較特殊的類型的擷取。

3、進行屬性的拷貝的時候,需要考慮到源對象中的屬性值為null,是否應該拷貝到目标對象中

4、一個class檔案中的getter和setter方法一般都是不可變的,是以需要進行緩存起來,避免每次都進行擷取。

一、編寫BeanUtils工具類,實作拷貝

/**
 * bean 之間的屬性的複制.
 * 
 * @描述
 * @作者 huan
 * @時間 2017年11月4日 - 上午11:25:26
 */
public final class BeanUtils {

  private static final ConcurrentHashMap<Class<?>, List<Method>> CACHE_CLASS_GET_METHOD = new ConcurrentHashMap<>();
  private static final ConcurrentHashMap<Class<?>, List<Method>> CACHE_CLASS_SET_METHOD = new ConcurrentHashMap<>();

  private BeanUtils() {
  }

  /**
   * 将src java bean的屬性拷貝到 tar java bean的屬性中,預設不拷貝src對象中的空對象的值.
   *
   * @param src
   *            源對象
   * @param tar
   *            目标對象
   */
  public static void copyProperties(Object src, Object tar) {
    copyProperties(src, tar, true);
  }

  /**
   * 将src java bean的屬性拷貝到 tar java bean的屬性中
   *
   * @param src
   *            源對象
   * @param tar
   *            目标對象
   * @param skipEmpty
   *            如果src中屬性的值時null,是否跳過這個拷貝, true:跳過 false:不跳過
   */
  public static void copyProperties(Object src, Object tar, boolean skipEmpty) {
    List<Method> getterMethods = getGetMethod(src.getClass());
    List<Method> setterMethods = getSetMethod(tar.getClass());
    try {
      for (Method getMethod : getterMethods) {
        Object value = getMethod.invoke(src);
        if (skipEmpty && value == null) {
          continue;
        }
        Method method = setterMethods.stream().filter(m -> Objects.equals(m.getName(), getInvokedSetterMethodName(getMethod.getName()))).findFirst().orElse(null);
        if (null != method) {
          method.invoke(tar, value);
        }
      }
    } catch (IllegalAccessException | InvocationTargetException e) {
      throw new RuntimeException("bean複制屬性過程中産生異常", e);
    }
  }

  /**
   * 根據getter的方法名拿到setter的方法名
   *
   * @param getterMethodName
   *            getter的方法名
   * @return setter的方法名
   */
  public static String getInvokedSetterMethodName(String getterMethodName) {
    if (getterMethodName.startsWith("get")) {
      return "set" + getterMethodName.substring(3);
    } else {
      return "set" + getterMethodName.substring(2);
    }
  }

  /**
   * 擷取clazz中的所有public getter 方法
   *
   * @param clazz
   *            需要擷取getter方法的類
   * @return public getter方法
   */
  public static List<Method> getGetMethod(Class<?> clazz) {
    Class<?> lockClazz = clazz;
    if (!CACHE_CLASS_GET_METHOD.containsKey(clazz)) {
      synchronized (lockClazz) {
        if (!CACHE_CLASS_GET_METHOD.containsKey(clazz)) {
          CACHE_CLASS_GET_METHOD.put(clazz, Arrays.stream(getMethods(clazz)).filter(BeanUtils::isGetterMethod).filter(m -> !Objects.equals(m.getName(), "getClass")).collect(Collectors.toList()));
        }
      }
    }
    return CACHE_CLASS_GET_METHOD.get(clazz);
  }

  /**
   * 擷取clazz中的所有public setter 方法
   *
   * @param clazz
   *            需要擷取setter方法的類
   * @return public setter方法
   */
  public static List<Method> getSetMethod(Class<?> clazz) {
    Class<?> lockClazz = clazz;
    if (!CACHE_CLASS_SET_METHOD.containsKey(clazz)) {
      synchronized (lockClazz) {
        if (!CACHE_CLASS_SET_METHOD.containsKey(clazz)) {
          CACHE_CLASS_SET_METHOD.put(clazz, Arrays.stream(getMethods(clazz)).filter(BeanUtils::isSetterMethod).collect(Collectors.toList()));
        }
      }
    }
    return CACHE_CLASS_SET_METHOD.get(clazz);
  }

  /**
   * 擷取一個類中中的所有的方法
   *
   * @param clazz
   * @return
   */
  public static Method[] getMethods(Class<?> clazz) {
    return clazz.getMethods();
  }

  /**
   * 判斷一個方式是否是getter方法
   *
   * @param method
   *            需要判斷的方法
   * @return true:是getter方法 false:不是getter方法
   */
  public static boolean isGetterMethod(Method method) {
    if (method.getName().startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != Void.class) {
      return true;
    } else if (method.getName().startsWith("is") && method.getParameterCount() == 0 && (method.getReturnType().equals(boolean.class) || method.getReturnType().equals(Boolean.class))) {
      return true;
    }
    return false;
  }

  /**
   * 判斷是否是setter方法
   *
   * @param method
   *            需要判斷的方法
   * @return true是setter方法 false不是setter方法
   */
  public static boolean isSetterMethod(Method method) {
    boolean isSetter = false;
    if (method.getName().startsWith("set") && method.getParameterCount() == 1 && method.getReturnType() != Void.class) {
      isSetter = true;
    }
    return isSetter;
  }
}      

 二、測試

   1、編寫一個實體類 SysResource

/**
 * 資源實體類
 * 
 * @描述
 * @作者 huan
 * @時間 2017年11月4日 - 上午11:26:46
 */
@Data
public class SysResource {
  private String id;
  private String pid;
  private String name;
  private String url;
  private Integer type;
  private Date createTime;
}      

   2、編寫測試代碼

基于反射進行對象屬性的拷貝