JAVA中使用spring的浅克隆
场景 : 最近,应项目要求要拆分表(原先表字段过多),拆成多个表(A表分成B表跟C表,B表保留基本信息,C表保留详细信息),这个时候我们希望后台第一个修订版不作出过大改动,所以我们最实际的方法就先把A表干掉,使用D实体类代替(A的备份),但是D的数据需要从B+C拷贝过来。 或者说我们的场景就是比如 M实体类跟数据库对应,N是M的包装类,但是我们很多时候是直接把M的数据拷贝到N类中,所以我们可以使用N继承M,或者N拥有M的很多属性,然后我们使用浅克隆拷贝即可。
注意 : 我们使用的浅克隆是spring当中的BeanUtils的copyProperties方法,org.springframework.beans.BeanUtils.copyProperties(Object source,Object target)。
方法说明 : 我们可以传递三个参数,第一个param是源obj,第二个是目标obj,第三个是需要忽略的属性数组(即是我们不想拷贝的属性)。
实际测试 : 1. 测试下Date类型跟Set类型能够复制; 2. 测试下NULL值能否复制;
@Test public void testCopyProperties() { // DXB2171127AF6100001 String sql = "select * from in_applyinfo where status_lookup_code='140' and applyid='IST1180208AL2014102' allow filtering"; ApplyInfoMigration applyInfoMigration = applyInfoDao.selectOne(sql,ApplyInfoMigration.class ); // A实体 ApplyInfo appInfo = new ApplyInfo(); // B表 ApplyInfoDetail applyInfoDetail = new ApplyInfoDetail(); // C表 System.out.println("-------------------> 属性开始copy-------》"); // BeanUtils.copyProperties BeanUtils.copyProperties(applyInfoMigration, appInfo); // 经过测试 发现是可以拷贝非空属性的 BeanUtils.copyProperties(applyInfoMigration, applyInfoDetail); // 经过测试 相同的属性再次 拷贝过来,是会覆盖的 System.out.println(appInfo.getV3_finger_time()); Set<Map<String, String>> tempSet = appInfo.getJobinfo(); tempSet.forEach(e -> { Map<String,String> tempMap = e; for (String key : tempMap.keySet()){ System.out.println(key + "其值为" + tempMap.get(key)); } }); System.out.println("---> detail信息"); System.out.println(applyInfoDetail.getHistorytravelinfo_issuedate()); tempSet = applyInfoDetail.getEducationinfo(); tempSet.forEach(e -> { Map<String,String> tempMap = e; for (String key : tempMap.keySet()){ System.out.println(key + "其值为" + tempMap.get(key)); } }); }
代码分析 : 首先准备1个能查询的数据实体,我们这里是ApplyInfoMigration,这是B表跟C表的合体pojo类,B表是基表只保存基本信息,C表示detail详细信息表,B表跟C表就一个UUID关联。这里我们测试了date类型跟set类型都可以拷贝成功,而且非空属性是不能拷贝的。
经过上面测试,大部分情况下是可以适用的。下面我们再分析下源代码 :
BeanUtils.copyProperties 源码分析 : private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties) throws BeansException { Assert.notNull(source, "Source must not be null"); // 断言判断 源类跟目标类是否为空 ,为空抛出异常,使用单元测试应该很熟悉这个东西 Assert.notNull(target, "Target must not be null"); Class<?> actualEditable = target.getClass(); // 获取目标类class // instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例 (这个对象是否是这个特定类或者是它的子类的一个实例。) // 如果 object 是 class 的一个实例,则 instanceof 运算符返回 true。如果 object 不是指定类的一个实例,或者 object 是 null,则返回 false。 if (editable != null) { if (!editable.isInstance(target)) { throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]"); } actualEditable = editable; } // 看起来感觉没做什么,但是感觉如果 editable 是target的父类的话,这就把target 目标类转换成了父类? PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); // 获取到目标类的属性 PS:以后知道这个API了 List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null); // 需要忽略的属性(就是不想copy 赋值的属性) for (PropertyDescriptor targetPd : targetPds) { Method writeMethod = targetPd.getWriteMethod(); // 从属性上获取到set方法 if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { // 从忽略的属性集合中过滤 PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); // 获取到目标类的具体属性(传参了) if (sourcePd != null) { Method readMethod = sourcePd.getReadMethod(); // 从源类上获取到 get方法 if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { // isAssignable(Class<?> cls, Class<?> toClass) 调用ClassUtils.class的isAssignable看是否能转型 // System.out.println(ClassUtils.isAssignable(Date.class, Object.class)); //= true // System.out.println(ClassUtils.isAssignable(Object.class, Date.class)); //=false try { if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { // 获取到 源类的定义类的方法的语言修饰符 readMethod.setAccessible(true); // 置为true:取消java语言访问检查,否则这里会抛异常的 /* 例如要序列化的时候,我们又必须有能力去处理这些字段, 这时候,我们就需要调用AccessibleObject上的setAccessible()方法来允许这种访问, 而由于反射类中的Field,Method和Constructor继承自AccessibleObject, 因此,通过在这些类上调用setAccessible()方法,我们可以实现对这些字段的操作。 但有的时候这将会成为一个安全隐患,为此,我们可以启用java.security.manager来判断程序是否具有调用setAccessible()的权限。 */
// 这里的readMethod.setAccessible(true);必须要设置为true才行,我也写过反射调用方法进行set跟get但是必须先取消java语言访问检查才行,否则不能反射set值
} Object value = readMethod.invoke(source); // 从源类获取到值 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } writeMethod.invoke(target, value); // 把值插入到 目标类当中 } catch (Throwable ex) { throw new FatalBeanException( "Could not copy property '" + targetPd.getName() + "' from source to target", ex); } } } } } }
既然,分析完源码,那我们能做些什么呢 ? 分析完源码,我们这里有个业务场景: 我们不想拷贝Null值,就是说B拷贝给A的一个J属性不为空,但是C继续拷贝给A,其中C有J属性并且值为NULL,其实我们在上面的代码中
writeMethod.invoke(target, value); // 把值插入到 目标类当中 ,这这里对value进行NULL判断即可。
if(NULL!= value ){
writeMethod.invoke(target, value);
}
这里基本就介绍完了,但是这个修改的源码怎么发布呢? 这就是另外的知识点,以后再做介绍,我们也可以把源码拷贝出去,使用自己的类来完成这个公共方法也行。

浙公网安备 33010602011771号