hutool的BeanUtil.copyProperties复制枚举类属性大坑

这篇具有很好参考价值的文章主要介绍了hutool的BeanUtil.copyProperties复制枚举类属性大坑。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

现象

        项目中需要使用到对象属性复制,于是使用hutool的BeanUtil.copyProperties方法。这个方法线上一直用着都没问题,然而最近修改代码后却突然报错:Can not convert XXX to XXX。结合代码得知,该报错为把Map中的字符串复制到Bean的枚举类属性,并为该属性设置对应对象时出现的。

报错截图如下:

hutool的BeanUtil.copyProperties复制枚举类属性大坑

 报错内容如下:

cn.hutool.core.convert.ConvertException: Can not convert ORDER_INVALID to class com.xxx
	at cn.hutool.core.convert.impl.EnumConverter.convertInternal(EnumConverter.java:53) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.convert.AbstractConverter.convert(AbstractConverter.java:58) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.convert.ConverterRegistry.convertSpecial(ConverterRegistry.java:357) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.convert.ConverterRegistry.convert(ConverterRegistry.java:271) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.convert.ConverterRegistry.convert(ConverterRegistry.java:297) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.convert.Convert.convertWithCheck(Convert.java:745) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.bean.copier.provider.MapValueProvider.value(MapValueProvider.java:65) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.bean.copier.provider.MapValueProvider.value(MapValueProvider.java:24) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.bean.copier.BeanCopier.lambda$valueProviderToBean$1(BeanCopier.java:259) ~[hutool-all-5.7.16.jar!/:na]
	at java.util.LinkedHashMap$LinkedValues.forEach(LinkedHashMap.java:608) ~[na:1.8.0_322]
	at cn.hutool.core.bean.BeanUtil.descForEach(BeanUtil.java:182) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.bean.copier.BeanCopier.valueProviderToBean(BeanCopier.java:232) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.bean.copier.BeanCopier.mapToBean(BeanCopier.java:133) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.bean.copier.BeanCopier.copy(BeanCopier.java:102) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.bean.BeanUtil.copyProperties(BeanUtil.java:742) ~[hutool-all-5.7.16.jar!/:na]
	at cn.hutool.core.bean.BeanUtil.copyProperties(BeanUtil.java:706) ~[hutool-all-5.7.16.jar!/:na]
	at 

排查

        之前一直用都没问题,突然间出现报错着实奇怪,首先想到的就是改动的代码导致的bug。

        通过Git查看新提交的代码,发现本次改动只是在枚举类中增加一个通过code获取枚举类对象的静态方法getEnumByCode,代码如下:

package com.xxx;

import lombok.Getter;


@Getter
public enum XxxTypeEnum {

    XX("1", "类型1"),
    ORDER_INVALID("8", "订单失效"),
    /*...*/
    /*此处省略一堆枚举类对象*/

    ;

    private final String code;
    private final String desc;

    private static final Map<String, XxxTypeEnum> ENUM_MAP = Arrays.stream(XxxTypeEnum.values()).collect(Collectors.toMap(XxxTypeEnum::getCode, Function.identity(), (v1, v2) -> v1));

    XxxTypeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getFullDesc() {
        return "[" + this.getCode() + "-" + this.getDesc() + "]";
    }

    // TODO: 2023-2-7 hutool的BeanUtil.copyProperties巨坑: 枚举类中有类似下面的方法会导致报错:Can not convert {字符串} to class {全类名}
    public static XxxTypeEnum getEnumByCode(String code) {
        return ENUM_MAP.get(code);
    }
}

        相关代码截图:XxxEventReq、XxxTypeEnum

hutool的BeanUtil.copyProperties复制枚举类属性大坑

 hutool的BeanUtil.copyProperties复制枚举类属性大坑

        经同事提醒,进一步排查问题,在idea下载hutool的源码,查看cn.hutool.core.convert.impl.EnumConverter.convertInternal方法源码如下:

	@Override
	protected Object convertInternal(Object value) {
		Enum enumValue = tryConvertEnum(value, this.enumClass);
		if (null == enumValue && false == value instanceof String) {
			// 最后尝试先将value转String,再valueOf转换
			enumValue = Enum.valueOf(this.enumClass, convertToStr(value));
		}

		if (null != enumValue) {
			return enumValue;
		}

        // 报错在此抛出
		throw new ConvertException("Can not convert {} to {}", value, this.enumClass);
	}

        继续查看tryConvertEnum方法代码,注意到方法描述中转换规则的【找到类似转换的静态方法调用实现转换且优先使用】内容,该内容指出【用户自定义的类似转换的静态方法】的优先级高于【Enum的valueOf方法】。因此,当枚举类中无【用户自定义的类似转换的静态方法】时,BeanUtil会使用【Enum的valueOf方法】根据字符串获取到枚举类对象并赋值给Bean中的该枚举类属性。当枚举类中增加自定义的getEnumByCode方法后,BeanUtil会根据该方法来转换对象,然而枚举类XxxTypeEnum中的code并没有包含ORDER_INVALID,所以getEnumByCode方法返回为null,进而导致convertInternal方法抛出异常。

        综上,bug是枚举类中XxxTypeEnum增加类似转换的静态方法getEnumByCode导致的。当然hutool的上述转换规则确实是个坑,在使用时要特别注意。

	/**
	 * 尝试转换,转换规则为:
	 * <ul>
	 *     <li>如果实现{@link EnumItem}接口,则调用fromInt或fromStr转换</li>
	 *     <li>找到类似转换的静态方法调用实现转换且优先使用</li>
	 *     <li>约定枚举类应该提供 valueOf(String) 和 valueOf(Integer)用于转换</li>
	 *     <li>oriInt /name 转换托底</li>
	 * </ul>
	 *
	 * @param value     被转换的值
	 * @param enumClass enum类
	 * @return 对应的枚举值
	 */
	protected static Enum tryConvertEnum(Object value, Class enumClass) {
		if (value == null) {
			return null;
		}

		// EnumItem实现转换
		if (EnumItem.class.isAssignableFrom(enumClass)) {
			final EnumItem first = (EnumItem) EnumUtil.getEnumAt(enumClass, 0);
			if (null != first) {
				if (value instanceof Integer) {
					return (Enum) first.fromInt((Integer) value);
				} else if (value instanceof String) {
					return (Enum) first.fromStr(value.toString());
				}
			}
		}

		// 用户自定义方法
		// 查找枚举中所有返回值为目标枚举对象的方法,如果发现方法参数匹配,就执行之
		try {
			final Map<Class<?>, Method> methodMap = getMethodMap(enumClass);
			if (MapUtil.isNotEmpty(methodMap)) {
				final Class<?> valueClass = value.getClass();
				for (Map.Entry<Class<?>, Method> entry : methodMap.entrySet()) {
					if (ClassUtil.isAssignable(entry.getKey(), valueClass)) {
						return ReflectUtil.invokeStatic(entry.getValue(), value);
					}
				}
			}
		} catch (Exception ignore) {
			//ignore
		}

		//oriInt 应该滞后使用 以 GB/T 2261.1-2003 性别编码为例,对应整数并非连续数字会导致数字转枚举时失败
		//0 - 未知的性别
		//1 - 男性
		//2 - 女性
		//5 - 女性改(变)为男性
		//6 - 男性改(变)为女性
		//9 - 未说明的性别
		Enum enumResult = null;
		if (value instanceof Integer) {
			enumResult = EnumUtil.getEnumAt(enumClass, (Integer) value);
		} else if (value instanceof String) {
			try {
				enumResult = Enum.valueOf(enumClass, (String) value);
			} catch (IllegalArgumentException e) {
				//ignore
			}
		}

		return enumResult;
	}


	/**
	 * 获取用于转换为enum的所有static方法
	 *
	 * @param enumClass 枚举类
	 * @return 转换方法map,key为方法参数类型,value为方法
	 */
	private static Map<Class<?>, Method> getMethodMap(Class<?> enumClass) {
		return VALUE_OF_METHOD_CACHE.get(enumClass, () -> Arrays.stream(enumClass.getMethods())
				.filter(ModifierUtil::isStatic)
				.filter(m -> m.getReturnType() == enumClass)
				.filter(m -> m.getParameterCount() == 1)
				.filter(m -> false == "valueOf".equals(m.getName()))
				.collect(Collectors.toMap(m -> m.getParameterTypes()[0], m -> m, (k1, k2) -> k1)));
	}

解决

        封装枚举类操作工具类EnumHandleUtil,将枚举类中XxxTypeEnum的getEnumByCode方法抽取到工具类中。

ps

        本文章中对应软件框架版本:

         Java:1.8

         spring boot:1.5.12.RELEASE

         hutool:5.7.16文章来源地址https://www.toymoban.com/news/detail-454483.html

到了这里,关于hutool的BeanUtil.copyProperties复制枚举类属性大坑的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 父类对象的属性直接赋值给子类对象(使用copyProperties中的方法copyProperties)

    BeanUtils.copyProperties() 是 Apache Commons BeanUtils 包中提供的一个方法,用于将一个 JavaBean 对象的属性值赋值到另一个 JavaBean 对象中。该方法可以简化 JavaBean 之间的属性复制过程,避免手动编写大量的赋值代码。 以下是 BeanUtils.copyProperties() 方法的基本用法: 在上面的例子中,首

    2024年02月15日
    浏览(43)
  • java中使用BeanUtils.copyProperties方法对象复制同名字段类型不同赋值为空问题解析

    对象之间的复制,dto对象中字段类型为String,model中字段类型为BigDecimal。使用BeanUtils.copyProperties方法进行对象批量复制。 提示:这里描述项目中遇到的问题: 使用Spring 的 BeanUtils.copyProperties 方法发现dto中的String类型字段无法转换为model中的BigDecimal类型同名字段。 使用Spring

    2024年02月13日
    浏览(39)
  • Java枚举中定义属性

    刚接触枚举时的例子太简单,就一个Season枚举类,里面四个常量值,后来开发中看到枚举中定义属性就很看不惯。这里梳理下Java枚举中定义属性,以及枚举在开发中的实际应用举例。 Java 枚举是 一个特殊的类 ,一般表示一组常量,比如一年的 4 个季节,一年的 12 个月份 J

    2024年02月07日
    浏览(30)
  • 《Effective Java》第三条 用私有构造器或者枚举类型强化Singleton属性

    Singleton其实就是单例,即一个类在一个应用程序中只被实例化一次,只有一个对象。 本节讨论三种实现方式 私有构造+共有成员 私有构造+静态工厂方法+私有成员 枚举实现 1、共有成员 构造方法私有,有一个该类类型的公有不可变的实例域。 1.1 code 当类加载的时候就会创建

    2024年02月16日
    浏览(39)
  • Effective Java笔记(3)用私有构造器或者枚举类型强化 Singleton 属性

            Singleton 是指仅仅被实例化一次的类 。Singleton 通常被用来代表一个无状态的对象,如函数,或者那些本质上唯一的系统组件 。 使类成为 Singleton会使它的害户端测试变得十分困难 ,因为不可能给 Singleton 替换模拟实现,除非实现一个充当其类型的接口 。      

    2024年02月15日
    浏览(52)
  • UE5——网络——属性复制

    当属性被注册进行复制后,您将无法再取消注册(涉及到生存期这一话题)。之所以会这样,是因为我们要预制尽可能多的信息,以便针对同一组属性将某一工作分担给多个连接。这样可以节省大量的计算时间。 COND_InitialOnly :此属性将仅尝试在初始话的时候发送 COND_OwnerO

    2024年02月06日
    浏览(39)
  • Effective第三版 中英 | 第2章 创建和销毁对象 | 用私有构造器或者枚举类型强化 Singleton 属性

    大家好,这里是 Rocky 编程日记 ,喜欢后端架构及中间件源码,目前正在阅读 effective-java 书籍。同时也把自己学习该书时的笔记,代码分享出来,供大家学习交流,如若笔记中有不对的地方,那一定是当时我的理解还不够,希望你能及时提出。如果对于该笔记存在很多疑惑,

    2024年02月07日
    浏览(37)
  • java中实现对象属性复制的工具类

    Apache Commons BeanUtils 提供了 BeanUtils 类,可以方便地进行属性的复制。您可以使用 BeanUtils.copyProperties() 方法将一个对象的属性值复制到另一个对象中。例如: 请确保已经引入 Apache Commons BeanUtils 的相关依赖。 pom可以如下 例如: 请确保已经引入 Spring Framework 的相关依赖。(偷懒

    2024年02月16日
    浏览(33)
  • 建库、建表、修改表、复制表、字符类型、数值类型、枚举类型、日期时间类型、检索目录、数据导入命令、数据导入步骤、数据导出命令、非空、默认值、唯一索引

    1.1 问题 建库练习 建表练习 修改表练习 1.2 方案 在MySQL50主机完成练习。 1.3 步骤 实现此案例需要按照如下步骤进行。 步骤一:建库练习 库名命名规则: 仅可以使用数字、字母、下划线、不能纯数字 区分字母大小写, 具有唯一性 不可使用MySQL命令或特殊字符 命令操作如下

    2024年01月17日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包