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模板网!

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

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

相关文章

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包