BeanUtil

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"

    通常写法:

  1. .....
  2. user.setEnterpriseName(userForm.getEnterpriseName());
  3. user.setLinkMan(userForm.getLinkMan());
  4. user.setPhone(userForm.getPhone());
  5. ......

此时,可以使用

  1. BeanUtil.copyProperties(user,userForm,"enterpriseName","linkMan","phone");

注册自定义 Converter:如果有 java.util.Date类型的需要copy,那么需要先使用ConvertUtils.register(Converter, Class)方法:

  1. 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中的以值(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 示例:
  1. User user = new User();
  2. user.setId(5L);
  3. Map<String, Object> properties = new HashMap<>();
  4. properties.put("id", 8L);
  5. BeanUtil.populate(user, properties);
  6. LOGGER.info(JsonUtil.format(user));

返回:

  1. {
  2. "id": 8,
  3. }

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信息:

  1. {
  2. "memcached.alivecheck": "false",
  3. "memcached.expiretime": "180",
  4. "memcached.initconnection": "10",
  5. "memcached.maintSleep": "30",
  6. "memcached.maxconnection": "250",
  7. "memcached.minconnection": "5",
  8. "memcached.nagle": "false",
  9. "memcached.poolname": "sidsock2",
  10. "memcached.serverlist": "172.20.31.23:11211,172.20.31.22:11211",
  11. "memcached.serverweight": "2",
  12. "memcached.socketto": "3000"
  13. }

有以下aliasBean信息:

  1. public class DangaMemCachedConfig{
  2. //** The serverlist.
  3. @Alias(name = "memcached.serverlist",sampleValue = "172.20.31.23:11211,172.20.31.22:11211")
  4. private String[] serverList;
  5. //@Alias(name = "memcached.poolname",sampleValue = "sidsock2")
  6. private String poolName;
  7. //** The expire time 单位分钟.
  8. @Alias(name = "memcached.expiretime",sampleValue = "180")
  9. private Integer expireTime;
  10. //** 权重.
  11. @Alias(name = "memcached.serverweight",sampleValue = "2,1")
  12. private Integer[] weight;
  13. //** The init connection.
  14. @Alias(name = "memcached.initconnection",sampleValue = "10")
  15. private Integer initConnection;
  16. //** The min connection.
  17. @Alias(name = "memcached.minconnection",sampleValue = "5")
  18. private Integer minConnection;
  19. //** The max connection.
  20. @Alias(name = "memcached.maxconnection",sampleValue = "250")
  21. private Integer maxConnection;
  22. //** 设置主线程睡眠时间,每30秒苏醒一次,维持连接池大小.
  23. @Alias(name = "memcached.maintSleep",sampleValue = "30")
  24. private Integer maintSleep;
  25. //** 关闭套接字缓存.
  26. @Alias(name = "memcached.nagle",sampleValue = "false")
  27. private Boolean nagle;
  28. //** 连接建立后的超时时间.
  29. @Alias(name = "memcached.socketto",sampleValue = "3000")
  30. private Integer socketTo;
  31. //** The alive check.
  32. @Alias(name = "memcached.alivecheck",sampleValue = "false")
  33. private Boolean aliveCheck;
  34. //setter getter 略
  35. }

此时你可以如此调用代码:

  1. Map<String, String> readPropertiesToMap = ResourceBundleUtil.readPropertiesToMap("memcached");
  2. DangaMemCachedConfig dangaMemCachedConfig = new DangaMemCachedConfig();
  3. BeanUtil.populateAliasBean(dangaMemCachedConfig, readPropertiesToMap);
  4. LOGGER.debug(JsonUtil.format(dangaMemCachedConfig));

返回:

  1. {
  2. "maxConnection": 250,
  3. "expireTime": 180,
  4. "serverList": [
  5. "172.20.31.23",
  6. "11211",
  7. "172.20.31.22",
  8. "11211"
  9. ],
  10. "weight": [2],
  11. "nagle": false,
  12. "initConnection": 10,
  13. "aliveCheck": false,
  14. "poolName": "sidsock2",
  15. "maintSleep": 30,
  16. "socketTo": 3000,
  17. "minConnection": 5
  18. }

此时你会发现,上面的 serverList 期望值是 ["172.20.31.23:11211","172.20.31.22:11211"],但是和你的期望值不符合,因为, ArrayConverter 默认允许的字符 allowedChars 只有 '.', '-',其他都会被做成分隔符

你需要如此这般:

  1. Map<String, String> readPropertiesToMap = ResourceBundleUtil.readPropertiesToMap("memcached");
  2. DangaMemCachedConfig dangaMemCachedConfig = new DangaMemCachedConfig();
  3. ArrayConverter arrayConverter = new ArrayConverter(String[].class, new StringConverter(), 2);
  4. char[] allowedChars = { ':' };
  5. arrayConverter.setAllowedChars(allowedChars);
  6. BeanUtil.register(arrayConverter, String[].class);
  7. BeanUtil.populateAliasBean(dangaMemCachedConfig, readPropertiesToMap);
  8. LOGGER.debug(JsonUtil.format(dangaMemCachedConfig));

返回:

  1. {
  2. "maxConnection": 250,
  3. "expireTime": 180,
  4. "serverList": [
  5. "172.20.31.23:11211",
  6. "172.20.31.22:11211"
  7. ],
  8. "weight": [2],
  9. "nagle": false,
  10. "initConnection": 10,
  11. "aliveCheck": false,
  12. "poolName": "sidsock2",
  13. "maintSleep": 30,
  14. "socketTo": 3000,
  15. "minConnection": 5
  16. }

4.newDynaBean(Map<String, ?>)

使用 valueMap 来构造一个 DynaBean.

说明:

  • 一般情况下,你可能不需要使用这个方法
  • 很适合那种属性值数量不确定,并且又不想在页面使用map来渲染的地方,比如制作多维度的图表
  • 程序内部,默认使用的是 org.apache.commons.beanutils.LazyDynaClass
  • 不需要先创建一个期望的数据结构DynaClass,就能向LazyDynaBean中填充我们任意想填充的数据。
  • LazyDynaBean内部会根据我们填充进的数据(即使是一个map中的一个key-value pair),创建metadata的。示例:
  1. DynaBean newDynaBean = BeanUtil.newDynaBean(toMap(//
  2. Pair.of("address", (Object) new HashMap()),
  3. Pair.of("firstName", (Object) "Fred"),
  4. Pair.of("lastName", (Object) "Flintstone")));
  5. LOGGER.debug(JsonUtil.format(newDynaBean));

返回:

  1. {
  2. "address": {},
  3. "firstName": "Fred",
  4. "lastName": "Flintstone"
  5. }