From 0c3a1ea29871949ff0829cbb9740fd93cde286ca Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 9 Sep 2020 14:23:21 +0800 Subject: [PATCH] fix bean --- .../java/cn/hutool/core/bean/BeanDesc.java | 308 -------------- .../java/cn/hutool/core/bean/BeanUtil.java | 44 +- .../java/cn/hutool/core/bean/DynaBean.java | 4 +- .../java/cn/hutool/core/bean/PropDesc.java | 382 ++++++++++++++++++ .../hutool/core/bean/copier/BeanCopier.java | 116 ++---- .../hutool/core/bean/copier/CopyOptions.java | 59 ++- .../copier/provider/BeanValueProvider.java | 6 +- .../core/comparator/PropertyComparator.java | 10 +- .../cn/hutool/core/bean/BeanDescTest.java | 2 - .../cn/hutool/db/handler/HandleHelper.java | 2 +- .../main/java/cn/hutool/json/JSONObject.java | 45 +-- 11 files changed, 513 insertions(+), 465 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/bean/PropDesc.java diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java index 98d6a8051..bdc032736 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java @@ -1,22 +1,15 @@ package cn.hutool.core.bean; -import cn.hutool.core.annotation.AnnotationUtil; -import cn.hutool.core.annotation.PropIgnore; -import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ModifierUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.TypeUtil; -import java.beans.Transient; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.lang.reflect.Type; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -333,305 +326,4 @@ public class BeanDesc implements Serializable { return methodName.equals("set" + fieldName); } // ------------------------------------------------------------------------------------------------------ Private method end - - /** - * 属性描述,包括了字段、getter、setter和相应的方法执行 - * - * @author looly - */ - public static class PropDesc { - - /** - * 字段 - */ - private final Field field; - /** - * Getter方法 - */ - private Method getter; - /** - * Setter方法 - */ - private Method setter; - - /** - * 构造
- * Getter和Setter方法设置为默认可访问 - * - * @param field 字段 - * @param getter get方法 - * @param setter set方法 - */ - public PropDesc(Field field, Method getter, Method setter) { - this.field = field; - this.getter = ClassUtil.setAccessible(getter); - this.setter = ClassUtil.setAccessible(setter); - } - - /** - * 获取字段名,如果存在Alias注解,读取注解的值作为名称 - * - * @return 字段名 - */ - public String getFieldName() { - return ReflectUtil.getFieldName(this.field); - } - - /** - * 获取字段名称 - * - * @return 字段名 - * @since 5.1.6 - */ - public String getRawFieldName() { - return null == this.field ? null : this.field.getName(); - } - - /** - * 获取字段 - * - * @return 字段 - */ - public Field getField() { - return this.field; - } - - /** - * 获得字段类型
- * 先获取字段的类型,如果字段不存在,则获取Getter方法的返回类型,否则获取Setter的第一个参数类型 - * - * @return 字段类型 - */ - public Type getFieldType() { - if (null != this.field) { - return TypeUtil.getType(this.field); - } - return findPropType(getter, setter); - } - - /** - * 获得字段类型
- * 先获取字段的类型,如果字段不存在,则获取Getter方法的返回类型,否则获取Setter的第一个参数类型 - * - * @return 字段类型 - */ - public Class getFieldClass() { - if (null != this.field) { - return TypeUtil.getClass(this.field); - } - return findPropClass(getter, setter); - } - - /** - * 获取Getter方法,可能为{@code null} - * - * @return Getter方法 - */ - public Method getGetter() { - return this.getter; - } - - /** - * 获取Setter方法,可能为{@code null} - * - * @return {@link Method}Setter 方法对象 - */ - public Method getSetter() { - return this.setter; - } - - /** - * 获取属性值
- * 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值 - * - * @param bean Bean对象 - * @return 字段值 - * @since 4.0.5 - */ - public Object getValue(Object bean) { - if (null != this.getter) { - return ReflectUtil.invoke(bean, this.getter); - } else if (ModifierUtil.isPublic(this.field)) { - return ReflectUtil.getFieldValue(bean, this.field); - } - return null; - } - - /** - * 获取属性值,自动转换属性值类型
- * 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值 - * - * @param bean Bean对象 - * @param valueType 返回属性值类型,null表示不转换 - * @param ignoreError 是否忽略错误,包括转换错误和注入错误 - * @return this - * @since 5.4.2 - */ - public Object getValueWithConvert(Object bean, Type valueType, boolean ignoreError) { - Object result = null; - try { - result = getValue(bean); - } catch (Exception e) { - if (false == ignoreError) { - throw new BeanException(e, "Get value of [{}] error!", getFieldName()); - } - } - - if (null != result && null != valueType) { - // 尝试将结果转换为目标类型,如果转换失败,返回原类型。 - final Object convertValue = Convert.convertWithCheck(valueType, result, null, ignoreError); - if (null != convertValue) { - result = convertValue; - } - } - return result; - } - - /** - * 设置Bean的字段值
- * 首先调用字段对应的Setter方法,如果Setter方法不存在,则判断字段如果为public,则直接赋值字段值 - * - * @param bean Bean对象 - * @param value 值,必须与字段值类型匹配 - * @return this - * @since 4.0.5 - */ - public PropDesc setValue(Object bean, Object value) { - if (null != this.setter) { - ReflectUtil.invoke(bean, this.setter, value); - } else if (ModifierUtil.isPublic(this.field)) { - ReflectUtil.setFieldValue(bean, this.field, value); - } - return this; - } - - /** - * 设置属性值,可以自动转换字段类型为目标类型 - * - * @param bean Bean对象 - * @param value 属性值,可以为任意类型 - * @param ignoreNull 是否忽略{@code null}值,true表示忽略 - * @param ignoreError 是否忽略错误,包括转换错误和注入错误 - * @return this - * @since 5.4.2 - */ - public PropDesc setValueWithConvert(Object bean, Object value, boolean ignoreNull, boolean ignoreError) { - if (ignoreNull && null == value) { - return this; - } - - // 当类型不匹配的时候,执行默认转换 - if (null != value) { - final Class propClass = getFieldClass(); - if (false == propClass.isInstance(value)) { - value = Convert.convertWithCheck(propClass, value, null, ignoreError); - } - } - - // 属性赋值 - if (null != value || false == ignoreNull) { - try { - this.setValue(bean, value); - } catch (Exception e) { - if (false == ignoreError) { - throw new BeanException(e, "Set value of [{}] error!", getFieldName()); - } - // 忽略注入失败 - } - } - - return this; - } - - /** - * 字段和Getter方法是否为Transient关键字修饰的 - * - * @return 是否为Transient关键字修饰的 - * @since 5.3.11 - */ - public boolean isTransient() { - boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT); - - // 检查Getter方法 - if (false == isTransient && null != this.getter) { - isTransient = ModifierUtil.hasModifier(this.getter, ModifierUtil.ModifierType.TRANSIENT); - - // 检查注解 - if (false == isTransient) { - isTransient = AnnotationUtil.hasAnnotation(this.getter, Transient.class); - } - } - - return isTransient; - } - - /** - * 检查字段是否被忽略读,通过{@link PropIgnore} 注解完成,规则为: - *
-		 *     1. 在字段上有{@link PropIgnore} 注解
-		 *     2. 在getXXX方法上有{@link PropIgnore} 注解
-		 * 
- * - * @return 是否忽略读 - * @since 5.4.2 - */ - public boolean isIgnoreGet() { - return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class) - || AnnotationUtil.hasAnnotation(this.getter, PropIgnore.class); - } - - /** - * 检查字段是否被忽略写,通过{@link PropIgnore} 注解完成,规则为: - *
-		 *     1. 在字段上有{@link PropIgnore} 注解
-		 *     2. 在setXXX方法上有{@link PropIgnore} 注解
-		 * 
- * - * @return 是否忽略写 - * @since 5.4.2 - */ - public boolean isIgnoreSet() { - return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class) - || AnnotationUtil.hasAnnotation(this.setter, PropIgnore.class); - } - - //------------------------------------------------------------------------------------ Private method start - - /** - * 通过Getter和Setter方法中找到属性类型 - * - * @param getter Getter方法 - * @param setter Setter方法 - * @return {@link Type} - */ - private Type findPropType(Method getter, Method setter) { - Type type = null; - if (null != getter) { - type = TypeUtil.getReturnType(getter); - } - if (null == type && null != setter) { - type = TypeUtil.getParamType(setter, 0); - } - return type; - } - - /** - * 通过Getter和Setter方法中找到属性类型 - * - * @param getter Getter方法 - * @param setter Setter方法 - * @return {@link Type} - */ - private Class findPropClass(Method getter, Method setter) { - Class type = null; - if (null != getter) { - type = TypeUtil.getReturnClass(getter); - } - if (null == type && null != setter) { - type = TypeUtil.getFirstParamClass(setter); - } - return type; - } - //------------------------------------------------------------------------------------ Private method end - } } \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java index f7e68e8bc..1eefdef40 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java @@ -1,6 +1,5 @@ package cn.hutool.core.bean; -import cn.hutool.core.bean.BeanDesc.PropDesc; import cn.hutool.core.bean.copier.BeanCopier; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.ValueProvider; @@ -29,6 +28,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; /** * Bean工具类 @@ -169,6 +169,17 @@ public class BeanUtil { return BeanDescCache.INSTANCE.getBeanDesc(clazz, ()-> new BeanDesc(clazz)); } + /** + * 遍历Bean的属性 + * + * @param clazz Bean类 + * @param action 每个元素的处理类 + * @since 5.4.2 + */ + public static void descForEach(Class clazz, Consumer action){ + getBeanDesc(clazz).getProps().forEach(action); + } + // --------------------------------------------------------------------------------------------------------- PropertyDescriptor /** @@ -616,32 +627,11 @@ public class BeanUtil { return null; } - final Collection props = BeanUtil.getBeanDesc(bean.getClass()).getProps(); - - String key; - Method getter; - Object value; - for (PropDesc prop : props) { - key = prop.getFieldName(); - // 过滤class属性 - // 得到property对应的getter方法 - getter = prop.getGetter(); - if (null != getter) { - // 只读取有getter方法的属性 - try { - value = getter.invoke(bean); - } catch (Exception ignore) { - continue; - } - if (false == ignoreNullValue || (null != value && false == value.equals(bean))) { - key = keyEditor.edit(key); - if (null != key) { - targetMap.put(key, value); - } - } - } - } - return targetMap; + return BeanCopier.create(bean, targetMap, + CopyOptions.create() + .setIgnoreNullValue(ignoreNullValue) + .setFieldNameEditor(keyEditor) + ).copy(); } // --------------------------------------------------------------------------------------------- copyProperties diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java b/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java index e9a3a6621..bf8280288 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java @@ -82,7 +82,7 @@ public class DynaBean extends CloneSupport implements Serializable { if (Map.class.isAssignableFrom(beanClass)) { return (T) ((Map) bean).get(fieldName); } else { - final BeanDesc.PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName); + final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName); if(null == prop){ throw new BeanException("No public field or get method for {}", fieldName); } @@ -129,7 +129,7 @@ public class DynaBean extends CloneSupport implements Serializable { if (Map.class.isAssignableFrom(beanClass)) { ((Map) bean).put(fieldName, value); } else { - final BeanDesc.PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName); + final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName); if(null == prop){ throw new BeanException("No public field or set method for {}", fieldName); } diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/PropDesc.java b/hutool-core/src/main/java/cn/hutool/core/bean/PropDesc.java new file mode 100644 index 000000000..0f6534e96 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/bean/PropDesc.java @@ -0,0 +1,382 @@ +package cn.hutool.core.bean; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.annotation.PropIgnore; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ModifierUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.TypeUtil; + +import java.beans.Transient; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +/** + * 属性描述,包括了字段、getter、setter和相应的方法执行 + * + * @author looly + */ +public class PropDesc { + + /** + * 字段 + */ + final Field field; + /** + * Getter方法 + */ + protected Method getter; + /** + * Setter方法 + */ + protected Method setter; + + /** + * 构造
+ * Getter和Setter方法设置为默认可访问 + * + * @param field 字段 + * @param getter get方法 + * @param setter set方法 + */ + public PropDesc(Field field, Method getter, Method setter) { + this.field = field; + this.getter = ClassUtil.setAccessible(getter); + this.setter = ClassUtil.setAccessible(setter); + } + + /** + * 获取字段名,如果存在Alias注解,读取注解的值作为名称 + * + * @return 字段名 + */ + public String getFieldName() { + return ReflectUtil.getFieldName(this.field); + } + + /** + * 获取字段名称 + * + * @return 字段名 + * @since 5.1.6 + */ + public String getRawFieldName() { + return null == this.field ? null : this.field.getName(); + } + + /** + * 获取字段 + * + * @return 字段 + */ + public Field getField() { + return this.field; + } + + /** + * 获得字段类型
+ * 先获取字段的类型,如果字段不存在,则获取Getter方法的返回类型,否则获取Setter的第一个参数类型 + * + * @return 字段类型 + */ + public Type getFieldType() { + if (null != this.field) { + return TypeUtil.getType(this.field); + } + return findPropType(getter, setter); + } + + /** + * 获得字段类型
+ * 先获取字段的类型,如果字段不存在,则获取Getter方法的返回类型,否则获取Setter的第一个参数类型 + * + * @return 字段类型 + */ + public Class getFieldClass() { + if (null != this.field) { + return TypeUtil.getClass(this.field); + } + return findPropClass(getter, setter); + } + + /** + * 获取Getter方法,可能为{@code null} + * + * @return Getter方法 + */ + public Method getGetter() { + return this.getter; + } + + /** + * 获取Setter方法,可能为{@code null} + * + * @return {@link Method}Setter 方法对象 + */ + public Method getSetter() { + return this.setter; + } + + /** + * 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值) + * @param checkTransient 是否检查Transient关键字或注解 + * @return 是否可读 + * @since 5.4.2 + */ + public boolean isReadable(boolean checkTransient){ + // 检查是否有getter方法或是否为public修饰 + if(null == this.getter && false == ModifierUtil.isPublic(this.field)){ + return false; + } + + // 检查transient关键字和@Transient注解 + if(checkTransient && isTransientForGet()){ + return false; + } + + // 检查@PropIgnore注解 + return false == isIgnoreGet(); + } + + /** + * 获取属性值
+ * 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值
+ * 此方法不检查任何注解,使用前需调用 {@link #isReadable(boolean)} 检查是否可读 + * + * @param bean Bean对象 + * @return 字段值 + * @since 4.0.5 + */ + public Object getValue(Object bean) { + if (null != this.getter) { + return ReflectUtil.invoke(bean, this.getter); + } else if (ModifierUtil.isPublic(this.field)) { + return ReflectUtil.getFieldValue(bean, this.field); + } + + return null; + } + + /** + * 获取属性值,自动转换属性值类型
+ * 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值 + * + * @param bean Bean对象 + * @param targetType 返回属性值需要转换的类型,null表示不转换 + * @param ignoreError 是否忽略错误,包括转换错误和注入错误 + * @return this + * @since 5.4.2 + */ + public Object getValue(Object bean, Type targetType, boolean ignoreError) { + Object result = null; + try { + result = getValue(bean); + } catch (Exception e) { + if (false == ignoreError) { + throw new BeanException(e, "Get value of [{}] error!", getFieldName()); + } + } + + if (null != result && null != targetType) { + // 尝试将结果转换为目标类型,如果转换失败,返回原类型。 + final Object convertValue = Convert.convertWithCheck(targetType, result, null, ignoreError); + if (null != convertValue) { + result = convertValue; + } + } + return result; + } + + /** + * 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值) + * @param checkTransient 是否检查Transient关键字或注解 + * @return 是否可读 + * @since 5.4.2 + */ + public boolean isWritable(boolean checkTransient){ + // 检查是否有getter方法或是否为public修饰 + if(null == this.setter && false == ModifierUtil.isPublic(this.field)){ + return false; + } + + // 检查transient关键字和@Transient注解 + if(checkTransient && isTransientForSet()){ + return false; + } + + // 检查@PropIgnore注解 + return false == isIgnoreSet(); + } + + /** + * 设置Bean的字段值
+ * 首先调用字段对应的Setter方法,如果Setter方法不存在,则判断字段如果为public,则直接赋值字段值
+ * 此方法不检查任何注解,使用前需调用 {@link #isWritable(boolean)} 检查是否可写 + * + * @param bean Bean对象 + * @param value 值,必须与字段值类型匹配 + * @return this + * @since 4.0.5 + */ + public PropDesc setValue(Object bean, Object value) { + if (null != this.setter) { + ReflectUtil.invoke(bean, this.setter, value); + } else if (ModifierUtil.isPublic(this.field)) { + ReflectUtil.setFieldValue(bean, this.field, value); + } + return this; + } + + /** + * 设置属性值,可以自动转换字段类型为目标类型 + * + * @param bean Bean对象 + * @param value 属性值,可以为任意类型 + * @param ignoreNull 是否忽略{@code null}值,true表示忽略 + * @param ignoreError 是否忽略错误,包括转换错误和注入错误 + * @return this + * @since 5.4.2 + */ + public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) { + if (ignoreNull && null == value) { + return this; + } + + // 当类型不匹配的时候,执行默认转换 + if (null != value) { + final Class propClass = getFieldClass(); + if (false == propClass.isInstance(value)) { + value = Convert.convertWithCheck(propClass, value, null, ignoreError); + } + } + + // 属性赋值 + if (null != value || false == ignoreNull) { + try { + this.setValue(bean, value); + } catch (Exception e) { + if (false == ignoreError) { + throw new BeanException(e, "Set value of [{}] error!", getFieldName()); + } + // 忽略注入失败 + } + } + + return this; + } + + //------------------------------------------------------------------------------------ Private method start + + /** + * 通过Getter和Setter方法中找到属性类型 + * + * @param getter Getter方法 + * @param setter Setter方法 + * @return {@link Type} + */ + private Type findPropType(Method getter, Method setter) { + Type type = null; + if (null != getter) { + type = TypeUtil.getReturnType(getter); + } + if (null == type && null != setter) { + type = TypeUtil.getParamType(setter, 0); + } + return type; + } + + /** + * 通过Getter和Setter方法中找到属性类型 + * + * @param getter Getter方法 + * @param setter Setter方法 + * @return {@link Type} + */ + private Class findPropClass(Method getter, Method setter) { + Class type = null; + if (null != getter) { + type = TypeUtil.getReturnClass(getter); + } + if (null == type && null != setter) { + type = TypeUtil.getFirstParamClass(setter); + } + return type; + } + + /** + * 检查字段是否被忽略写,通过{@link PropIgnore} 注解完成,规则为: + *
+	 *     1. 在字段上有{@link PropIgnore} 注解
+	 *     2. 在setXXX方法上有{@link PropIgnore} 注解
+	 * 
+ * + * @return 是否忽略写 + * @since 5.4.2 + */ + private boolean isIgnoreSet() { + return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class) + || AnnotationUtil.hasAnnotation(this.setter, PropIgnore.class); + } + + /** + * 检查字段是否被忽略读,通过{@link PropIgnore} 注解完成,规则为: + *
+	 *     1. 在字段上有{@link PropIgnore} 注解
+	 *     2. 在getXXX方法上有{@link PropIgnore} 注解
+	 * 
+ * + * @return 是否忽略读 + * @since 5.4.2 + */ + private boolean isIgnoreGet() { + return AnnotationUtil.hasAnnotation(this.field, PropIgnore.class) + || AnnotationUtil.hasAnnotation(this.getter, PropIgnore.class); + } + + /** + * 字段和Getter方法是否为Transient关键字修饰的 + * + * @return 是否为Transient关键字修饰的 + * @since 5.3.11 + */ + private boolean isTransientForGet() { + boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT); + + // 检查Getter方法 + if (false == isTransient && null != this.getter) { + isTransient = ModifierUtil.hasModifier(this.getter, ModifierUtil.ModifierType.TRANSIENT); + + // 检查注解 + if (false == isTransient) { + isTransient = AnnotationUtil.hasAnnotation(this.getter, Transient.class); + } + } + + return isTransient; + } + + /** + * 字段和Getter方法是否为Transient关键字修饰的 + * + * @return 是否为Transient关键字修饰的 + * @since 5.3.11 + */ + private boolean isTransientForSet() { + boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT); + + // 检查Getter方法 + if (false == isTransient && null != this.setter) { + isTransient = ModifierUtil.hasModifier(this.setter, ModifierUtil.ModifierType.TRANSIENT); + + // 检查注解 + if (false == isTransient) { + isTransient = AnnotationUtil.hasAnnotation(this.setter, Transient.class); + } + } + + return isTransient; + } + //------------------------------------------------------------------------------------ Private method end +} diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java index 4cb9792e6..2203d7730 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java @@ -1,6 +1,5 @@ package cn.hutool.core.bean.copier; -import cn.hutool.core.bean.BeanDesc.PropDesc; import cn.hutool.core.bean.BeanException; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.DynaBean; @@ -9,17 +8,11 @@ import cn.hutool.core.bean.copier.provider.DynaBeanValueProvider; import cn.hutool.core.bean.copier.provider.MapValueProvider; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.copier.Copier; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ModifierUtil; -import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.TypeUtil; import java.io.Serializable; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.lang.reflect.Type; -import java.util.Collection; import java.util.HashSet; import java.util.Map; @@ -164,43 +157,45 @@ public class BeanCopier implements Copier, Serializable { */ @SuppressWarnings({ "rawtypes", "unchecked" }) private void beanToMap(Object bean, Map targetMap) { - final Collection props = BeanUtil.getBeanDesc(bean.getClass()).getProps(); final HashSet ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null; final CopyOptions copyOptions = this.copyOptions; - String key; - Object value; - for (PropDesc prop : props) { - // 忽略注解的字段 - if(prop.isIgnoreGet()){ - continue; + BeanUtil.descForEach(bean.getClass(), (prop)->{ + if(false == prop.isReadable(copyOptions.isTransientSupport())){ + // 忽略的属性跳过之 + return; } - key = prop.getFieldName(); + String key = prop.getFieldName(); if (CollUtil.contains(ignoreSet, key)) { // 目标属性值被忽略或值提供者无此key时跳过 - continue; + return; } + + Object value; try { value = prop.getValue(bean); } catch (Exception e) { if (copyOptions.ignoreError) { - continue;// 忽略反射失败 + return;// 忽略反射失败 } else { throw new BeanException(e, "Get value of [{}] error!", prop.getFieldName()); } } - if (null == value && copyOptions.ignoreNullValue) { - continue;// 当允许跳过空时,跳过 + if ((null == value && copyOptions.ignoreNullValue) || bean == value) { + // 当允许跳过空时,跳过 + //值不能为bean本身,防止循环引用,此类也跳过 + return; } - if (bean.equals(value)) { - continue;// 值不能为bean本身,防止循环引用 - } - targetMap.put(mappingKey(copyOptions.fieldMapping, key), value); - } + + // 对key做映射 + key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key, false)); + targetMap.put(key, value); + }); } /** - * 值提供器转Bean + * 值提供器转Bean
+ * 此方法通过遍历目标Bean的字段,从ValueProvider查找对应值 * * @param valueProvider 值提供器 * @param bean Bean @@ -220,73 +215,38 @@ public class BeanCopier implements Copier, Serializable { actualEditable = copyOptions.editable; } final HashSet ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null; - final Map fieldReverseMapping = copyOptions.getReversedMapping(); - final Collection props = BeanUtil.getBeanDesc(actualEditable).getProps(); - Field field; - String fieldName; - Object value; - Method setterMethod; - Class propClass; - for (PropDesc prop : props) { - // 获取值 - field = prop.getField(); - fieldName = prop.getFieldName(); + BeanUtil.descForEach(actualEditable, (prop)->{ + if(false == prop.isWritable(this.copyOptions.isTransientSupport())){ + // 字段不可写,跳过之 + return; + } + + // 检查属性名 + String fieldName = prop.getFieldName(); if (CollUtil.contains(ignoreSet, fieldName)) { // 目标属性值被忽略或值提供者无此key时跳过 - continue; + return; } - // 在支持情况下,忽略transient修饰(包括修饰和注解) - if(copyOptions.isTransientSupport() && prop.isTransient()){ - continue; - } - - // @Ignore修饰的字段和setXXX方法忽略之 - if(prop.isIgnoreSet()){ - continue; - } - - final String providerKey = mappingKey(fieldReverseMapping, fieldName); + final String providerKey = copyOptions.getMappedFieldName(fieldName, true); if (false == valueProvider.containsKey(providerKey)) { // 无对应值可提供 - continue; - } - setterMethod = prop.getSetter(); - if (null == setterMethod && false == ModifierUtil.isPublic(field)) { - // Setter方法不存在或者字段为非public跳过 - //5.1.0新增支持public字段注入支持 - continue; + return; } // 获取目标字段真实类型 final Type fieldType = TypeUtil.getActualType(this.destType ,prop.getFieldType()); - value = valueProvider.value(providerKey, fieldType); - if (null == value && copyOptions.ignoreNullValue) { - continue;// 当允许跳过空时,跳过 - } - if (bean == value) { + // 获取属性值 + Object value = valueProvider.value(providerKey, fieldType); + if ((null == value && copyOptions.ignoreNullValue) || bean == value) { + // 当允许跳过空时,跳过 // 值不能为bean本身,防止循环引用 - continue; + return; } - prop.setValueWithConvert(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError); - } - } - - /** - * 获取指定字段名对应的映射值 - * - * @param mapping 反向映射Map - * @param fieldName 字段名 - * @return 映射值,无对应值返回字段名 - * @since 4.1.10 - */ - private static String mappingKey(Map mapping, String fieldName) { - if (MapUtil.isEmpty(mapping)) { - return fieldName; - } - return ObjectUtil.defaultIfNull(mapping.get(fieldName), fieldName); + prop.setValue(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError); + }); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java index b588db654..812559f65 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java @@ -1,6 +1,8 @@ package cn.hutool.core.bean.copier; +import cn.hutool.core.lang.Editor; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; import java.io.Serializable; import java.util.Map; @@ -41,6 +43,14 @@ public class CopyOptions implements Serializable { * 拷贝属性的字段映射,用于不同的属性之前拷贝做对应表用 */ protected Map fieldMapping; + /** + * 反向映射表,自动生成用于反向查找 + */ + private Map reversedFieldMapping; + /** + * 字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等 + */ + protected Editor fieldNameEditor; /** * 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。 */ @@ -182,6 +192,19 @@ public class CopyOptions implements Serializable { return this; } + /** + * 设置字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等
+ * 此转换器只针对源端的字段做转换,请确认转换后与目标端字段一致 + * + * @param fieldNameEditor 字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等 + * @return CopyOptions + * @since 5.4.2 + */ + public CopyOptions setFieldNameEditor(Editor fieldNameEditor) { + this.fieldNameEditor = fieldNameEditor; + return this; + } + /** * 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。 * @@ -204,13 +227,45 @@ public class CopyOptions implements Serializable { return this; } + /** + * 获得映射后的字段名
+ * 当非反向,则根据源字段名获取目标字段名,反之根据目标字段名获取源字段名。 + * + * @param fieldName 字段名 + * @param reversed 是否反向映射 + * @return 映射后的字段名 + */ + protected String getMappedFieldName(String fieldName, boolean reversed){ + Map mapping = reversed ? getReversedMapping() : this.fieldMapping; + if(MapUtil.isEmpty(mapping)){ + return fieldName; + } + return ObjectUtil.defaultIfNull(mapping.get(fieldName), fieldName); + } + + /** + * 转换字段名为编辑后的字段名 + * @param fieldName 字段名 + * @return 编辑后的字段名 + * @since 5.4.2 + */ + protected String editFieldName(String fieldName){ + return (null != this.fieldNameEditor) ? this.fieldNameEditor.edit(fieldName) : fieldName; + } + /** * 获取反转之后的映射 * * @return 反转映射 * @since 4.1.10 */ - protected Map getReversedMapping() { - return (null != this.fieldMapping) ? MapUtil.reverse(this.fieldMapping) : null; + private Map getReversedMapping() { + if(null == this.fieldMapping){ + return null; + } + if(null == this.reversedFieldMapping){ + reversedFieldMapping = MapUtil.reverse(this.fieldMapping); + } + return reversedFieldMapping; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java index bdbae6880..2ca6ce471 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java @@ -1,7 +1,7 @@ package cn.hutool.core.bean.copier.provider; -import cn.hutool.core.bean.BeanDesc.PropDesc; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.PropDesc; import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.util.StrUtil; @@ -38,7 +38,7 @@ public class BeanValueProvider implements ValueProvider { Object result = null; if (null != sourcePd) { - result = sourcePd.getValueWithConvert(this.source, valueType, this.ignoreError); + result = sourcePd.getValue(this.source, valueType, this.ignoreError); } return result; } @@ -48,7 +48,7 @@ public class BeanValueProvider implements ValueProvider { final PropDesc sourcePd = getPropDesc(key, null); // 字段描述不存在或忽略读的情况下,表示不存在 - return null != sourcePd && false == sourcePd.isIgnoreGet(); + return null != sourcePd && false == sourcePd.isReadable(false); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/PropertyComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/PropertyComparator.java index 73ce5d25c..40cc501cf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/PropertyComparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/PropertyComparator.java @@ -1,11 +1,11 @@ package cn.hutool.core.comparator; -import java.io.Serializable; -import java.util.Comparator; - import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.ObjectUtil; +import java.io.Serializable; +import java.util.Comparator; + /** * Bean属性排序器
* 支持读取Bean多层次下的属性 @@ -53,8 +53,8 @@ public class PropertyComparator implements Comparator, Serializable { Comparable v1; Comparable v2; try { - v1 = (Comparable) BeanUtil.getProperty(o1, property); - v2 = (Comparable) BeanUtil.getProperty(o2, property); + v1 = BeanUtil.getProperty(o1, property); + v2 = BeanUtil.getProperty(o2, property); } catch (Exception e) { throw new ComparatorException(e); } diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanDescTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanDescTest.java index 5139c758b..353b9b91a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanDescTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanDescTest.java @@ -3,8 +3,6 @@ package cn.hutool.core.bean; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.bean.BeanDesc.PropDesc; - /** * {@link BeanDesc} 单元测试类 * diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java b/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java index e9762bebc..5fb86e344 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java @@ -1,7 +1,7 @@ package cn.hutool.db.handler; -import cn.hutool.core.bean.BeanDesc.PropDesc; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.PropDesc; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index 011cbef69..0f0117c8a 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -1,8 +1,9 @@ package cn.hutool.json; -import cn.hutool.core.bean.BeanDesc.PropDesc; import cn.hutool.core.bean.BeanPath; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.BeanCopier; +import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.map.CaseInsensitiveLinkedMap; @@ -19,7 +20,6 @@ import cn.hutool.json.serialize.JSONSerializer; import java.io.IOException; import java.io.Writer; -import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; @@ -620,41 +620,12 @@ public class JSONObject implements JSON, JSONGetter, Map * @param bean Bean对象 */ private void populateMap(Object bean) { - final Collection props = BeanUtil.getBeanDesc(bean.getClass()).getProps(); - - Method getter; - Object value; - for (PropDesc prop : props) { - if(this.config.isTransientSupport() && prop.isTransient()){ - // 忽略Transient字段和方法 - continue; - } - - // 得到property对应的getter方法 - getter = prop.getGetter(); - if (null == getter) { - // 无Getter跳过 - continue; - } - - // 只读取有getter方法的属性 - try { - value = getter.invoke(bean); - } catch (Exception ignore) { - // 忽略读取失败的属性 - continue; - } - - if (ObjectUtil.isNull(value) && this.config.isIgnoreNullValue()) { - // 值为null且用户定义跳过则跳过 - continue; - } - - if (value != bean) { - // 防止循环引用 - this.put(prop.getFieldName(), value); - } - } + BeanCopier.create(bean, this, + CopyOptions.create() + .setIgnoreCase(config.isIgnoreCase()) + .setIgnoreError(true) + .setIgnoreNullValue(config.isIgnoreNullValue()) + ).copy(); } /**