BeanUtil  ## 1.copyProperties(Object, Object, String...) 将 fromObj 中的全部或者一组属性 `includePropertyNames` 的值,复制到 toObj 对象中. **注意:** - 这种copy都是 `浅拷贝`,复制后的2个Bean的同一个属性可能拥有同一个对象的ref,在使用时要小心,特别是对于属性为自定义类的情况 . - 此方法调用了 `BeanUtils.copyProperties(Object, Object)`,会自动进行`Object--->String--->Object`类型转换 - 如果指定了`includePropertyNames`,会调用 `getProperty(Object, String)`,在自动进行`Object--->String` 类型转换过程中,如果发现值是数组,只会取第一个元素重新构造数组转到 toObj中,规则参见 ConvertUtil.toString(Object) - 如果需要copy的两个对象属性之间的类型一样的话,那么调用这个方法会有性能消耗,此时强烈建议调用 `PropertyUtil.copyProperties(Object, Object, String)` - 不支持toObj是map类型,从`BeanUtilsBean.copyProperties(Object, Object)`源码可以看出, fromObj可以是map **使用示例:** 例如两个pojo: user和userForm 都含有字段"enterpriseName","linkMan","phone" 通常写法: ```JAVA ..... user.setEnterpriseName(userForm.getEnterpriseName()); user.setLinkMan(userForm.getLinkMan()); user.setPhone(userForm.getPhone()); ...... ``` 此时,可以使用 ```JAVA BeanUtil.copyProperties(user,userForm,"enterpriseName","linkMan","phone"); ``` **注册自定义 Converter:** 如果有 `java.util.Date`类型的需要copy,那么需要先使用`ConvertUtils.register(Converter, Class)`方法: ```JAVA ConvertUtils.register(new DateLocaleConverter(Locale.US, DatePattern.TO_STRING_STYLE),Date.class); ``` **BeanUtils.copyProperties(Object, Object)与 PropertyUtils.copyProperties(Object, Object)区别** - `BeanUtils.copyProperties(Object, Object)` 提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而 `PropertyUtils.copyProperties(Object, Object)`不支持这个功能,但是速度会更快一些. - commons-beanutils v1.9.0以前的版本 BeanUtils不允许对象的属性值为 null,PropertyUtils可以拷贝属性值 null的对象. - (注:commons-beanutils v1.9.0+修复了这个情况,`BeanUtilsBean.copyProperties()` no longer throws a `ConversionException` for null properties of certain data types),具体信息,可以参阅commons-beanutils的 RELEASE-NOTES.txt **相比较直接调用 BeanUtils.copyProperties(Object, Object)的优点:** - 将 checkedException 异常转成了 `BeanOperationException` RuntimeException,因为通常copy的时候出现了`checkedException`,也是普普通通记录下log,没有更好的处理方式 - 支持 `includePropertyNames` 参数,允许针对性copy 个别属性 - 更多,更容易理解的的javadoc ## 2.cloneBean(T) 调用`BeanUtils.cloneBean(Object)`. **注意:** - 这个方法通过默认构造函数建立一个bean的新实例,然后拷贝每一个属性到这个新的bean中,即使这个bean没有实现 Cloneable接口 . - 是为那些本身没有实现clone方法的类准备的 - 在源码上看是调用了 `getPropertyUtils().copyProperties(newBean, bean)`;最后实际上还是复制的引用,无法实现深clone - 但还是可以帮助我们减少工作量的,假如类的属性不是基础类型的话(即自定义类),可以先clone出那个自定义类,在把他付给新的类,覆盖原来类的引用 - 如果需要深度clone,可以使用 `SerializationUtils.clone` ,但是它的性能要慢很多倍 - 由于内部实现是通过 `Class.newInstance()`来构造新的对象,所以需要被clone的对象必须存在默认无参构造函数,否则会出现异常 `InstantiationException` - 目前无法clone list,总是返回empty list,参见 `BeanUtils.cloneBean` with List is broken ## 3.populate ### 3.1 populate(T, Map<String, ?>) 把`properties/map`里面的值 populate (填充)到bean中. **说明:** - 将Map<Key,value>中的以值(String或String[])转换到目标bean对应的属性中,Key是目标bean的属性名. - apache的javadoc中,明确指明这个方法是为解析http请求参数特别定义和使用的,在正常使用中不推荐使用.推荐使用 `copyProperties(Object, Object, String)`方法 - 底层方法原理 `BeanUtilsBean.populate(Object, Map)`,循环map,调用 `BeanUtilsBean.setProperty(Object, String, Object)`方法 ,一一对应设置到 bean对象 - 如果properties key中有bean中不存在的属性,那么该条数据自动忽略 - 如果properties key中有null,那么该条数据自动忽略,see `BeanUtilsBean.populate(Object, Map)` line 817 - bean可以是Map类型,不会转换之后的key和value都会是Object类型,而不是声明的类型,see `BeanUtilsBean.setProperty(Object, String, Object)` line 928 **示例:** ```JAVA User user = new User(); user.setId(5L); Map<String, Object> properties = new HashMap<>(); properties.put("id", 8L); BeanUtil.populate(user, properties); LOGGER.info(JsonUtil.format(user)); ``` **返回: ** ```JSON { "id": 8, } ``` ### 3.2 populateAliasBean(T, Map<String, ?>) 将 alias 和value 的map populate (填充)到 `aliasBean`. **背景:** BeanUtil 有标准的populate功能:populate(Object, Map) ,但是要求 map的key 和 bean的属性名称必须是一一对应 有很多情况,比如 map 的key是 `"memcached.alivecheck"` 这样的字符串(常见于properties 的配置文件),或者是大写的 `"ALIVECHECK"` 的字符串(常见于第三方接口 xml属性名称) 而我们的bean里面的属性名称是标准的 java bean 规范的名字,比如 `"aliveCheck"`,这时就没有办法直接使用 `populate(Object, Map)` 方法了 你可以使用 `populateAliasBean(Object, Map)`方法~~ **示例:** 有以下`aliasAndValueMap`信息: ```JSON { "memcached.alivecheck": "false", "memcached.expiretime": "180", "memcached.initconnection": "10", "memcached.maintSleep": "30", "memcached.maxconnection": "250", "memcached.minconnection": "5", "memcached.nagle": "false", "memcached.poolname": "sidsock2", "memcached.serverlist": "172.20.31.23:11211,172.20.31.22:11211", "memcached.serverweight": "2", "memcached.socketto": "3000" } ``` 有以下aliasBean信息: ```JAVA public class DangaMemCachedConfig{ //** The serverlist. @Alias(name = "memcached.serverlist",sampleValue = "172.20.31.23:11211,172.20.31.22:11211") private String[] serverList; //@Alias(name = "memcached.poolname",sampleValue = "sidsock2") private String poolName; //** The expire time 单位分钟. @Alias(name = "memcached.expiretime",sampleValue = "180") private Integer expireTime; //** 权重. @Alias(name = "memcached.serverweight",sampleValue = "2,1") private Integer[] weight; //** The init connection. @Alias(name = "memcached.initconnection",sampleValue = "10") private Integer initConnection; //** The min connection. @Alias(name = "memcached.minconnection",sampleValue = "5") private Integer minConnection; //** The max connection. @Alias(name = "memcached.maxconnection",sampleValue = "250") private Integer maxConnection; //** 设置主线程睡眠时间,每30秒苏醒一次,维持连接池大小. @Alias(name = "memcached.maintSleep",sampleValue = "30") private Integer maintSleep; //** 关闭套接字缓存. @Alias(name = "memcached.nagle",sampleValue = "false") private Boolean nagle; //** 连接建立后的超时时间. @Alias(name = "memcached.socketto",sampleValue = "3000") private Integer socketTo; //** The alive check. @Alias(name = "memcached.alivecheck",sampleValue = "false") private Boolean aliveCheck; //setter getter 略 } ``` 此时你可以如此调用代码: ```JAVA Map<String, String> readPropertiesToMap = ResourceBundleUtil.readPropertiesToMap("memcached"); DangaMemCachedConfig dangaMemCachedConfig = new DangaMemCachedConfig(); BeanUtil.populateAliasBean(dangaMemCachedConfig, readPropertiesToMap); LOGGER.debug(JsonUtil.format(dangaMemCachedConfig)); ``` **返回: ** ```JSON { "maxConnection": 250, "expireTime": 180, "serverList": [ "172.20.31.23", "11211", "172.20.31.22", "11211" ], "weight": [2], "nagle": false, "initConnection": 10, "aliveCheck": false, "poolName": "sidsock2", "maintSleep": 30, "socketTo": 3000, "minConnection": 5 } ``` 此时你会发现,上面的 serverList 期望值是 `["172.20.31.23:11211","172.20.31.22:11211"]`,但是和你的期望值不符合, 因为, `ArrayConverter` 默认允许的字符 `allowedChars` 只有 '.', '-',其他都会被做成分隔符 你需要如此这般: ```JAVA Map<String, String> readPropertiesToMap = ResourceBundleUtil.readPropertiesToMap("memcached"); DangaMemCachedConfig dangaMemCachedConfig = new DangaMemCachedConfig(); ArrayConverter arrayConverter = new ArrayConverter(String[].class, new StringConverter(), 2); char[] allowedChars = { ':' }; arrayConverter.setAllowedChars(allowedChars); BeanUtil.register(arrayConverter, String[].class); BeanUtil.populateAliasBean(dangaMemCachedConfig, readPropertiesToMap); LOGGER.debug(JsonUtil.format(dangaMemCachedConfig)); ``` **返回: ** ```JAVA { "maxConnection": 250, "expireTime": 180, "serverList": [ "172.20.31.23:11211", "172.20.31.22:11211" ], "weight": [2], "nagle": false, "initConnection": 10, "aliveCheck": false, "poolName": "sidsock2", "maintSleep": 30, "socketTo": 3000, "minConnection": 5 } ``` ## 4.newDynaBean(Map<String, ?>) 使用 `valueMap` 来构造一个 `DynaBean`. **说明:** - 一般情况下,你可能不需要使用这个方法 - 很适合那种属性值数量不确定,并且又不想在页面使用map来渲染的地方,比如制作多维度的图表 - 程序内部,默认使用的是 `org.apache.commons.beanutils.LazyDynaClass` - 不需要先创建一个期望的数据结构DynaClass,就能向LazyDynaBean中填充我们任意想填充的数据。 - `LazyDynaBean`内部会根据我们填充进的数据(即使是一个map中的一个key-value pair),创建metadata的。 **示例:** ```JAVA DynaBean newDynaBean = BeanUtil.newDynaBean(toMap(// Pair.of("address", (Object) new HashMap()), Pair.of("firstName", (Object) "Fred"), Pair.of("lastName", (Object) "Flintstone"))); LOGGER.debug(JsonUtil.format(newDynaBean)); ``` **返回: ** ``` { "address": {}, "firstName": "Fred", "lastName": "Flintstone" } ```