diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java
index f4384f30c..cf883219e 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java
@@ -54,109 +54,6 @@ import java.util.stream.Collectors;
*/
public class BeanUtil {
- /**
- * 判断是否为可读的Bean对象,判定方法是:
- *
- *
- * 1、是否存在只有无参数的getXXX方法或者isXXX方法
- * 2、是否存在public类型的字段
- *
- *
- * @param clazz 待测试类
- * @return 是否为可读的Bean对象
- * @see #hasGetter(Class)
- * @see #hasPublicField(Class)
- */
- public static boolean isReadableBean(final Class> clazz) {
- if (null == clazz) {
- return false;
- }
- return hasGetter(clazz) || hasPublicField(clazz);
- }
-
- /**
- * 判断是否为可写Bean对象,判定方法是:
- *
- *
- * 1、是否存在只有一个参数的setXXX方法
- * 2、是否存在public类型的字段
- *
- *
- * @param clazz 待测试类
- * @return 是否为Bean对象
- * @see #hasSetter(Class)
- * @see #hasPublicField(Class)
- */
- public static boolean isWritableBean(final Class> clazz) {
- if (null == clazz) {
- return false;
- }
- return hasSetter(clazz) || hasPublicField(clazz);
- }
-
- /**
- * 判断是否有Setter方法
- * 判定方法是否存在只有一个参数的setXXX方法
- *
- * @param clazz 待测试类
- * @return 是否为Bean对象
- * @since 4.2.2
- */
- public static boolean hasSetter(final Class> clazz) {
- if (ClassUtil.isNormalClass(clazz)) {
- for (final Method method : clazz.getMethods()) {
- if (method.getParameterCount() == 1 && method.getName().startsWith("set")) {
- // 检测包含标准的setXXX方法即视为标准的JavaBean
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * 判断是否为Bean对象
- * 判定方法是否存在只有无参数的getXXX方法或者isXXX方法
- *
- * @param clazz 待测试类
- * @return 是否为Bean对象
- * @since 4.2.2
- */
- public static boolean hasGetter(final Class> clazz) {
- if (ClassUtil.isNormalClass(clazz)) {
- for (final Method method : clazz.getMethods()) {
- if (method.getParameterCount() == 0) {
- final String name = method.getName();
- if (name.startsWith("get") || name.startsWith("is")) {
- if (!"getClass".equals(name)) {
- return true;
- }
- }
- }
- }
- }
- return false;
- }
-
- /**
- * 指定类中是否有public类型字段(static字段除外)
- *
- * @param clazz 待测试类
- * @return 是否有public类型字段
- * @since 5.1.0
- */
- public static boolean hasPublicField(final Class> clazz) {
- if (ClassUtil.isNormalClass(clazz)) {
- for (final Field field : clazz.getFields()) {
- if (ModifierUtil.isPublic(field) && !ModifierUtil.isStatic(field)) {
- //非static的public字段
- return true;
- }
- }
- }
- return false;
- }
-
/**
* 创建动态Bean
*
@@ -200,8 +97,7 @@ public class BeanUtil {
getBeanDesc(clazz).getProps().forEach(action);
}
- // --------------------------------------------------------------------------------------------------------- PropertyDescriptor
-
+ // region ----- getPropertyDescriptor
/**
* 获得Bean字段描述数组
*
@@ -278,6 +174,7 @@ public class BeanUtil {
final Map map = getPropertyDescriptorMap(clazz, ignoreCase);
return (null == map) ? null : map.get(fieldName);
}
+ // endregion
/**
* 获得字段值,通过反射直接获得字段值,并不调用getXXX方法
@@ -380,101 +277,7 @@ public class BeanUtil {
BeanPath.of(expression).setValue(bean, value);
}
- // --------------------------------------------------------------------------------------------- mapToBean
-
- /**
- * Map转换为Bean对象
- *
- * @param Bean类型
- * @param map {@link Map}
- * @param beanClass Bean Class
- * @param isToCamelCase 是否将Map中的下划线风格key转换为驼峰风格
- * @param copyOptions 转Bean选项
- * @return Bean
- */
- public static T mapToBean(final Map, ?> map, final Class beanClass, final boolean isToCamelCase, final CopyOptions copyOptions) {
- return fillBeanWithMap(map, ConstructorUtil.newInstanceIfPossible(beanClass), isToCamelCase, copyOptions);
- }
-
- // --------------------------------------------------------------------------------------------- fillBeanWithMap
-
- /**
- * 使用Map填充Bean对象
- *
- * @param Bean类型
- * @param map Map
- * @param bean Bean
- * @param isIgnoreError 是否忽略注入错误
- * @return Bean
- */
- public static T fillBeanWithMap(final Map, ?> map, final T bean, final boolean isIgnoreError) {
- return fillBeanWithMap(map, bean, false, isIgnoreError);
- }
-
- /**
- * 使用Map填充Bean对象,可配置将下划线转换为驼峰
- *
- * @param Bean类型
- * @param map Map
- * @param bean Bean
- * @param isToCamelCase 是否将下划线模式转换为驼峰模式
- * @param isIgnoreError 是否忽略注入错误
- * @return Bean
- */
- public static T fillBeanWithMap(final Map, ?> map, final T bean, final boolean isToCamelCase, final boolean isIgnoreError) {
- return fillBeanWithMap(map, bean, isToCamelCase, CopyOptions.of().setIgnoreError(isIgnoreError));
- }
-
- /**
- * 使用Map填充Bean对象,忽略大小写
- *
- * @param Bean类型
- * @param map Map
- * @param bean Bean
- * @param isIgnoreError 是否忽略注入错误
- * @return Bean
- */
- public static T fillBeanWithMapIgnoreCase(final Map, ?> map, final T bean, final boolean isIgnoreError) {
- return fillBeanWithMap(map, bean, CopyOptions.of().setIgnoreCase(true).setIgnoreError(isIgnoreError));
- }
-
- /**
- * 使用Map填充Bean对象
- *
- * @param Bean类型
- * @param map Map
- * @param bean Bean
- * @param copyOptions 属性复制选项 {@link CopyOptions}
- * @return Bean
- */
- public static T fillBeanWithMap(final Map, ?> map, final T bean, final CopyOptions copyOptions) {
- return fillBeanWithMap(map, bean, false, copyOptions);
- }
-
- /**
- * 使用Map填充Bean对象
- *
- * @param Bean类型
- * @param map Map
- * @param bean Bean
- * @param isToCamelCase 是否将Map中的下划线风格key转换为驼峰风格
- * @param copyOptions 属性复制选项 {@link CopyOptions}
- * @return Bean
- * @since 3.3.1
- */
- public static T fillBeanWithMap(Map, ?> map, final T bean, final boolean isToCamelCase, final CopyOptions copyOptions) {
- if (MapUtil.isEmpty(map)) {
- return bean;
- }
- if (isToCamelCase) {
- map = MapUtil.toCamelCaseMap(map);
- }
- copyProperties(map, bean, copyOptions);
- return bean;
- }
-
- // --------------------------------------------------------------------------------------------- fillBean
-
+ // region ----- toBean
/**
* 对象或Map转Bean
*
@@ -520,7 +323,9 @@ public class BeanUtil {
copyProperties(source, target, options);
return target;
}
+ // endregion
+ // region ----- fillBean
/**
* 填充Bean的核心方法
*
@@ -537,8 +342,25 @@ public class BeanUtil {
return BeanCopier.of(valueProvider, bean, copyOptions).copy();
}
- // --------------------------------------------------------------------------------------------- beanToMap
+ /**
+ * 使用Map填充Bean对象
+ *
+ * @param Bean类型
+ * @param map Map
+ * @param bean Bean
+ * @param copyOptions 属性复制选项 {@link CopyOptions}
+ * @return Bean
+ */
+ public static T fillBeanWithMap(final Map, ?> map, final T bean, final CopyOptions copyOptions) {
+ if (MapUtil.isEmpty(map)) {
+ return bean;
+ }
+ return copyProperties(map, bean, copyOptions);
+ }
+ // endregion
+
+ // region ----- beanToMap
/**
* 将bean的部分属性转换成map
* 可选拷贝哪些属性值,默认是不忽略值为{@code null}的值的。
@@ -657,8 +479,9 @@ public class BeanUtil {
return BeanCopier.of(bean, targetMap, copyOptions).copy();
}
+ // endregion
- // --------------------------------------------------------------------------------------------- copyProperties
+ // region ----- copyProperties
/**
* 按照Bean对象属性创建对应的Class对象,并忽略某些属性
@@ -727,6 +550,20 @@ public class BeanUtil {
return BeanCopier.of(source, target, ObjUtil.defaultIfNull(copyOptions, CopyOptions::of)).copy();
}
+ /**
+ * 复制集合中的Bean属性
+ * 此方法遍历集合中每个Bean,复制其属性后加入一个新的{@link List}中。
+ *
+ * @param collection 原Bean集合
+ * @param targetType 目标Bean类型
+ * @param Bean类型
+ * @return 复制后的List
+ * @since 5.6.6
+ */
+ public static List copyToList(final Collection> collection, final Class targetType) {
+ return copyToList(collection, targetType, CopyOptions.of());
+ }
+
/**
* 复制集合中的Bean属性
* 此方法遍历集合中每个Bean,复制其属性后加入一个新的{@link List}中。
@@ -758,20 +595,7 @@ public class BeanUtil {
return target;
}).collect(Collectors.toList());
}
-
- /**
- * 复制集合中的Bean属性
- * 此方法遍历集合中每个Bean,复制其属性后加入一个新的{@link List}中。
- *
- * @param collection 原Bean集合
- * @param targetType 目标Bean类型
- * @param Bean类型
- * @return 复制后的List
- * @since 5.6.6
- */
- public static List copyToList(final Collection> collection, final Class targetType) {
- return copyToList(collection, targetType, CopyOptions.of());
- }
+ // endregion
/**
* 给定的Bean的类名是否匹配指定类名字符串
@@ -881,6 +705,110 @@ public class BeanUtil {
);
}
+ /**
+ * 判断是否为可读的Bean对象,判定方法是:
+ *
+ *
+ * 1、是否存在只有无参数的getXXX方法或者isXXX方法
+ * 2、是否存在public类型的字段
+ *
+ *
+ * @param clazz 待测试类
+ * @return 是否为可读的Bean对象
+ * @see #hasGetter(Class)
+ * @see #hasPublicField(Class)
+ */
+ public static boolean isReadableBean(final Class> clazz) {
+ if (null == clazz) {
+ return false;
+ }
+ return hasGetter(clazz) || hasPublicField(clazz);
+ }
+
+ /**
+ * 判断是否为可写Bean对象,判定方法是:
+ *
+ *
+ * 1、是否存在只有一个参数的setXXX方法
+ * 2、是否存在public类型的字段
+ *
+ *
+ * @param clazz 待测试类
+ * @return 是否为Bean对象
+ * @see #hasSetter(Class)
+ * @see #hasPublicField(Class)
+ */
+ public static boolean isWritableBean(final Class> clazz) {
+ if (null == clazz) {
+ return false;
+ }
+ return hasSetter(clazz) || hasPublicField(clazz);
+ }
+
+ // region ----- hasXXX
+ /**
+ * 判断是否有Setter方法
+ * 判定方法是否存在只有一个参数的setXXX方法
+ *
+ * @param clazz 待测试类
+ * @return 是否为Bean对象
+ * @since 4.2.2
+ */
+ public static boolean hasSetter(final Class> clazz) {
+ if (ClassUtil.isNormalClass(clazz)) {
+ for (final Method method : clazz.getMethods()) {
+ if (method.getParameterCount() == 1 && method.getName().startsWith("set")) {
+ // 检测包含标准的setXXX方法即视为标准的JavaBean
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 判断是否为Bean对象
+ * 判定方法是否存在只有无参数的getXXX方法或者isXXX方法
+ *
+ * @param clazz 待测试类
+ * @return 是否为Bean对象
+ * @since 4.2.2
+ */
+ public static boolean hasGetter(final Class> clazz) {
+ if (ClassUtil.isNormalClass(clazz)) {
+ for (final Method method : clazz.getMethods()) {
+ if (method.getParameterCount() == 0) {
+ final String name = method.getName();
+ if (name.startsWith("get") || name.startsWith("is")) {
+ if (!"getClass".equals(name)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 指定类中是否有public类型字段(static字段除外)
+ *
+ * @param clazz 待测试类
+ * @return 是否有public类型字段
+ * @since 5.1.0
+ */
+ public static boolean hasPublicField(final Class> clazz) {
+ if (ClassUtil.isNormalClass(clazz)) {
+ for (final Field field : clazz.getFields()) {
+ if (ModifierUtil.isPublic(field) && !ModifierUtil.isStatic(field)) {
+ //非static的public字段
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* 判断Bean是否包含值为{@code null}的属性
* 对象本身为{@code null}也返回true
@@ -912,6 +840,7 @@ public class BeanUtil {
&& StrUtil.isEmptyIfStr(FieldUtil.getFieldValue(bean, field))
);
}
+ // endregion
/**
* 检查Bean
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/BeanToBeanCopier.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/BeanToBeanCopier.java
index c0800bbf6..04e286202 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/BeanToBeanCopier.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/BeanToBeanCopier.java
@@ -86,7 +86,7 @@ public class BeanToBeanCopier extends AbsCopier {
// 检查目标字段可写性
// 目标字段检查放在键值对编辑之后,因为键可能被编辑修改
- final PropDesc tDesc = targetPropDescMap.get(sFieldName);
+ final PropDesc tDesc = this.copyOptions.findPropDesc(targetPropDescMap, sFieldName);
if (null == tDesc || !tDesc.isWritable(this.copyOptions.transientSupport)) {
// 字段不可写,跳过之
return;
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/CopyOptions.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/CopyOptions.java
index 0d2df9fe4..ad3008b22 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/CopyOptions.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/CopyOptions.java
@@ -12,12 +12,14 @@
package org.dromara.hutool.core.bean.copier;
+import org.dromara.hutool.core.bean.PropDesc;
import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.convert.Converter;
import org.dromara.hutool.core.func.LambdaUtil;
import org.dromara.hutool.core.func.SerFunction;
import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.core.array.ArrayUtil;
+import org.dromara.hutool.core.text.StrUtil;
import java.io.Serializable;
import java.lang.reflect.Field;
@@ -76,11 +78,16 @@ public class CopyOptions implements Serializable {
*/
protected boolean override = true;
+ /**
+ * 是否自动转换为驼峰方式
+ */
+ protected boolean autoTransCamelCase = true;
+
/**
* 自定义类型转换器,默认使用全局万能转换器转换
*/
protected Converter converter = (type, value) ->
- Convert.convertWithCheck(type, value, null, ignoreError);
+ Convert.convertWithCheck(type, value, null, ignoreError);
//region create
@@ -178,7 +185,7 @@ public class CopyOptions implements Serializable {
*/
public CopyOptions setIgnoreProperties(final String... ignoreProperties) {
return setPropertiesFilter((field, o) -> {
- if(ignoreCase){
+ if (ignoreCase) {
// issue#I80FP4
return !ArrayUtil.containsIgnoreCase(ignoreProperties, field.getName());
}
@@ -284,7 +291,7 @@ public class CopyOptions implements Serializable {
protected MutableEntry editField(final String fieldName, final Object fieldValue) {
final MutableEntry entry = new MutableEntry<>(fieldName, fieldValue);
return (null != this.fieldEditor) ?
- this.fieldEditor.apply(entry) : entry;
+ this.fieldEditor.apply(entry) : entry;
}
/**
@@ -311,6 +318,19 @@ public class CopyOptions implements Serializable {
return this;
}
+ /**
+ * 设置是否自动转换为驼峰方式
+ * 一般用于map转bean和bean转bean出现非驼峰格式时,在尝试转换失败的情况下,是否二次检查转为驼峰匹配
+ *
+ * @param autoTransCamelCase 是否自动转换为驼峰方式
+ * @return this
+ * @since 6.0.0
+ */
+ public CopyOptions setAutoTransCamelCase(final boolean autoTransCamelCase) {
+ this.autoTransCamelCase = autoTransCamelCase;
+ return this;
+ }
+
/**
* 设置自定义类型转换器,默认使用全局万能转换器转换。
*
@@ -334,7 +354,7 @@ public class CopyOptions implements Serializable {
*/
protected Object convertField(final Type targetType, final Object fieldValue) {
return (null != this.converter) ?
- this.converter.convert(targetType, fieldValue) : fieldValue;
+ this.converter.convert(targetType, fieldValue) : fieldValue;
}
/**
@@ -348,4 +368,25 @@ public class CopyOptions implements Serializable {
protected boolean testPropertyFilter(final Field field, final Object value) {
return null == this.propertiesFilter || this.propertiesFilter.test(field, value);
}
+
+ /**
+ * 查找Map对应Bean的名称
+ * 尝试原名称、转驼峰名称、isXxx去掉is的名称
+ *
+ * @param targetPropDescMap 目标bean的属性描述Map
+ * @param sKeyStr 键或字段名
+ * @return {@link PropDesc}
+ */
+ protected PropDesc findPropDesc(final Map targetPropDescMap, final String sKeyStr) {
+ PropDesc propDesc = targetPropDescMap.get(sKeyStr);
+ // 转驼峰尝试查找
+ if (null == propDesc && this.autoTransCamelCase) {
+ final String camelCaseKey = StrUtil.toCamelCase(sKeyStr);
+ if (!StrUtil.equals(sKeyStr, camelCaseKey)) {
+ // 只有转换为驼峰后与原key不同才重复查询,相同说明本身就是驼峰,不需要二次查询
+ propDesc = targetPropDescMap.get(camelCaseKey);
+ }
+ }
+ return propDesc;
+ }
}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/MapToBeanCopier.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/MapToBeanCopier.java
index e6e9c2f49..9ffba6a5a 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/MapToBeanCopier.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/copier/MapToBeanCopier.java
@@ -88,7 +88,7 @@ public class MapToBeanCopier extends AbsCopier