From 3d1f14bac18e15fbf14e763243dc20000b0db7b3 Mon Sep 17 00:00:00 2001 From: yangchuanlong Date: Fri, 8 Jan 2021 15:19:06 +0800 Subject: [PATCH 1/2] =?UTF-8?q?beanUtil=20=E5=A2=9E=E5=8A=A0=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=9B=86=E5=90=88=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/core/bean/BeanUtil.java | 1573 +++++++++-------- .../cn/hutool/core/bean/BeanUtilTest.java | 37 +- 2 files changed, 824 insertions(+), 786 deletions(-) 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 33fafbd4d0..39d6676a36 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 @@ -9,25 +9,13 @@ import cn.hutool.core.lang.Editor; import cn.hutool.core.lang.Filter; import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ArrayUtil; -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 java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.beans.PropertyEditor; -import java.beans.PropertyEditorManager; +import cn.hutool.core.util.*; + +import java.beans.*; +import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Consumer; /** @@ -42,766 +30,793 @@ import java.util.function.Consumer; */ public class BeanUtil { - /** - * 判断是否为可读的Bean对象,判定方法是: - * - *
-	 *     1、是否存在只有无参数的getXXX方法或者isXXX方法
-	 *     2、是否存在public类型的字段
-	 * 
- * - * @param clazz 待测试类 - * @return 是否为可读的Bean对象 - * @see #hasGetter(Class) - * @see #hasPublicField(Class) - */ - public static boolean isReadableBean(Class clazz) { - return hasGetter(clazz) || hasPublicField(clazz); - } - - /** - * 判断是否为Bean对象,判定方法是: - * - *
-	 *     1、是否存在只有一个参数的setXXX方法
-	 *     2、是否存在public类型的字段
-	 * 
- * - * @param clazz 待测试类 - * @return 是否为Bean对象 - * @see #hasSetter(Class) - * @see #hasPublicField(Class) - */ - public static boolean isBean(Class clazz) { - return hasSetter(clazz) || hasPublicField(clazz); - } - - /** - * 判断是否有Setter方法
- * 判定方法是是否存在只有一个参数的setXXX方法 - * - * @param clazz 待测试类 - * @return 是否为Bean对象 - * @since 4.2.2 - */ - public static boolean hasSetter(Class clazz) { - if (ClassUtil.isNormalClass(clazz)) { - final Method[] methods = clazz.getMethods(); - for (Method method : methods) { - if (method.getParameterTypes().length == 1 && method.getName().startsWith("set")) { - // 检测包含标准的setXXX方法即视为标准的JavaBean - return true; - } - } - } - return false; - } - - /** - * 判断是否为Bean对象
- * 判定方法是是否存在只有一个参数的setXXX方法 - * - * @param clazz 待测试类 - * @return 是否为Bean对象 - * @since 4.2.2 - */ - public static boolean hasGetter(Class clazz) { - if (ClassUtil.isNormalClass(clazz)) { - for (Method method : clazz.getMethods()) { - if (method.getParameterTypes().length == 0) { - if (method.getName().startsWith("get") || method.getName().startsWith("is")) { - return true; - } - } - } - } - return false; - } - - /** - * 指定类中是否有public类型字段(static字段除外) - * - * @param clazz 待测试类 - * @return 是否有public类型字段 - * @since 5.1.0 - */ - public static boolean hasPublicField(Class clazz) { - if (ClassUtil.isNormalClass(clazz)) { - for (Field field : clazz.getFields()) { - if (ModifierUtil.isPublic(field) && false == ModifierUtil.isStatic(field)) { - //非static的public字段 - return true; - } - } - } - return false; - } - - /** - * 创建动态Bean - * - * @param bean 普通Bean或Map - * @return {@link DynaBean} - * @since 3.0.7 - */ - public static DynaBean createDynaBean(Object bean) { - return new DynaBean(bean); - } - - /** - * 查找类型转换器 {@link PropertyEditor} - * - * @param type 需要转换的目标类型 - * @return {@link PropertyEditor} - */ - public static PropertyEditor findEditor(Class type) { - return PropertyEditorManager.findEditor(type); - } - - /** - * 获取{@link BeanDesc} Bean描述信息 - * - * @param clazz Bean类 - * @return {@link BeanDesc} - * @since 3.1.2 - */ - public static BeanDesc getBeanDesc(Class clazz) { - 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 - - /** - * 获得Bean字段描述数组 - * - * @param clazz Bean类 - * @return 字段描述数组 - * @throws BeanException 获取属性异常 - */ - public static PropertyDescriptor[] getPropertyDescriptors(Class clazz) throws BeanException { - BeanInfo beanInfo; - try { - beanInfo = Introspector.getBeanInfo(clazz); - } catch (IntrospectionException e) { - throw new BeanException(e); - } - return ArrayUtil.filter(beanInfo.getPropertyDescriptors(), (Filter) t -> { - // 过滤掉getClass方法 - return false == "class".equals(t.getName()); - }); - } - - /** - * 获得字段名和字段描述Map,获得的结果会缓存在 {@link BeanInfoCache}中 - * - * @param clazz Bean类 - * @param ignoreCase 是否忽略大小写 - * @return 字段名和字段描述Map - * @throws BeanException 获取属性异常 - */ - public static Map getPropertyDescriptorMap(Class clazz, boolean ignoreCase) throws BeanException { - return BeanInfoCache.INSTANCE.getPropertyDescriptorMap(clazz, ignoreCase, () -> internalGetPropertyDescriptorMap(clazz, ignoreCase)); - } - - /** - * 获得字段名和字段描述Map。内部使用,直接获取Bean类的PropertyDescriptor - * - * @param clazz Bean类 - * @param ignoreCase 是否忽略大小写 - * @return 字段名和字段描述Map - * @throws BeanException 获取属性异常 - */ - private static Map internalGetPropertyDescriptorMap(Class clazz, boolean ignoreCase) throws BeanException { - final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz); - final Map map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1) - : new HashMap<>((int) (propertyDescriptors.length), 1); - - for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { - map.put(propertyDescriptor.getName(), propertyDescriptor); - } - return map; - } - - /** - * 获得Bean类属性描述,大小写敏感 - * - * @param clazz Bean类 - * @param fieldName 字段名 - * @return PropertyDescriptor - * @throws BeanException 获取属性异常 - */ - public static PropertyDescriptor getPropertyDescriptor(Class clazz, final String fieldName) throws BeanException { - return getPropertyDescriptor(clazz, fieldName, false); - } - - /** - * 获得Bean类属性描述 - * - * @param clazz Bean类 - * @param fieldName 字段名 - * @param ignoreCase 是否忽略大小写 - * @return PropertyDescriptor - * @throws BeanException 获取属性异常 - */ - public static PropertyDescriptor getPropertyDescriptor(Class clazz, final String fieldName, boolean ignoreCase) throws BeanException { - final Map map = getPropertyDescriptorMap(clazz, ignoreCase); - return (null == map) ? null : map.get(fieldName); - } - - /** - * 获得字段值,通过反射直接获得字段值,并不调用getXXX方法
- * 对象同样支持Map类型,fieldNameOrIndex即为key - * - * @param bean Bean对象 - * @param fieldNameOrIndex 字段名或序号,序号支持负数 - * @return 字段值 - */ - public static Object getFieldValue(Object bean, String fieldNameOrIndex) { - if (null == bean || null == fieldNameOrIndex) { - return null; - } - - if (bean instanceof Map) { - return ((Map) bean).get(fieldNameOrIndex); - } else if (bean instanceof Collection) { - return CollUtil.get((Collection) bean, Integer.parseInt(fieldNameOrIndex)); - } else if (ArrayUtil.isArray(bean)) { - return ArrayUtil.get(bean, Integer.parseInt(fieldNameOrIndex)); - } else {// 普通Bean对象 - return ReflectUtil.getFieldValue(bean, fieldNameOrIndex); - } - } - - /** - * 设置字段值,,通过反射设置字段值,并不调用setXXX方法
- * 对象同样支持Map类型,fieldNameOrIndex即为key - * - * @param bean Bean - * @param fieldNameOrIndex 字段名或序号,序号支持负数 - * @param value 值 - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - public static void setFieldValue(Object bean, String fieldNameOrIndex, Object value) { - if (bean instanceof Map) { - ((Map) bean).put(fieldNameOrIndex, value); - } else if (bean instanceof List) { - CollUtil.setOrAppend((List) bean, Convert.toInt(fieldNameOrIndex), value); - } else if (ArrayUtil.isArray(bean)) { - ArrayUtil.setOrAppend(bean, Convert.toInt(fieldNameOrIndex), value); - } else { - // 普通Bean对象 - ReflectUtil.setFieldValue(bean, fieldNameOrIndex, value); - } - } - - /** - * 解析Bean中的属性值 - * - * @param 属性值类型 - * @param bean Bean对象,支持Map、List、Collection、Array - * @param expression 表达式,例如:person.friend[5].name - * @return Bean属性值 - * @see BeanPath#get(Object) - * @since 3.0.7 - */ - @SuppressWarnings("unchecked") - public static T getProperty(Object bean, String expression) { - return (T) BeanPath.create(expression).get(bean); - } - - /** - * 解析Bean中的属性值 - * - * @param bean Bean对象,支持Map、List、Collection、Array - * @param expression 表达式,例如:person.friend[5].name - * @param value 属性值 - * @see BeanPath#get(Object) - * @since 4.0.6 - */ - public static void setProperty(Object bean, String expression, Object value) { - BeanPath.create(expression).set(bean, value); - } - - // --------------------------------------------------------------------------------------------- mapToBean - - /** - * Map转换为Bean对象 - * - * @param Bean类型 - * @param map {@link Map} - * @param beanClass Bean Class - * @param isIgnoreError 是否忽略注入错误 - * @return Bean - * @deprecated 请使用 {@link #toBean(Object, Class)} 或 {@link #toBeanIgnoreError(Object, Class)} - */ - @Deprecated - public static T mapToBean(Map map, Class beanClass, boolean isIgnoreError) { - return fillBeanWithMap(map, ReflectUtil.newInstanceIfPossible(beanClass), isIgnoreError); - } - - /** - * Map转换为Bean对象
- * 忽略大小写 - * - * @param Bean类型 - * @param map Map - * @param beanClass Bean Class - * @param isIgnoreError 是否忽略注入错误 - * @return Bean - * @deprecated 请使用 {@link #toBeanIgnoreCase(Object, Class, boolean)} - */ - @Deprecated - public static T mapToBeanIgnoreCase(Map map, Class beanClass, boolean isIgnoreError) { - return fillBeanWithMapIgnoreCase(map, ReflectUtil.newInstanceIfPossible(beanClass), isIgnoreError); - } - - /** - * Map转换为Bean对象 - * - * @param Bean类型 - * @param map {@link Map} - * @param beanClass Bean Class - * @param copyOptions 转Bean选项 - * @return Bean - * @deprecated 请使用 {@link #toBean(Object, Class, CopyOptions)} - */ - @Deprecated - public static T mapToBean(Map map, Class beanClass, CopyOptions copyOptions) { - return fillBeanWithMap(map, ReflectUtil.newInstanceIfPossible(beanClass), copyOptions); - } - - /** - * 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(Map map, Class beanClass, boolean isToCamelCase, CopyOptions copyOptions) { - return fillBeanWithMap(map, ReflectUtil.newInstanceIfPossible(beanClass), isToCamelCase, copyOptions); - } - - // --------------------------------------------------------------------------------------------- fillBeanWithMap - - /** - * 使用Map填充Bean对象 - * - * @param Bean类型 - * @param map Map - * @param bean Bean - * @param isIgnoreError 是否忽略注入错误 - * @return Bean - */ - public static T fillBeanWithMap(Map map, T bean, 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(Map map, T bean, boolean isToCamelCase, boolean isIgnoreError) { - return fillBeanWithMap(map, bean, isToCamelCase, CopyOptions.create().setIgnoreError(isIgnoreError)); - } - - /** - * 使用Map填充Bean对象,忽略大小写 - * - * @param Bean类型 - * @param map Map - * @param bean Bean - * @param isIgnoreError 是否忽略注入错误 - * @return Bean - */ - public static T fillBeanWithMapIgnoreCase(Map map, T bean, boolean isIgnoreError) { - return fillBeanWithMap(map, bean, CopyOptions.create().setIgnoreCase(true).setIgnoreError(isIgnoreError)); - } - - /** - * 使用Map填充Bean对象 - * - * @param Bean类型 - * @param map Map - * @param bean Bean - * @param copyOptions 属性复制选项 {@link CopyOptions} - * @return Bean - */ - public static T fillBeanWithMap(Map map, T bean, 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, T bean, boolean isToCamelCase, CopyOptions copyOptions) { - if (MapUtil.isEmpty(map)) { - return bean; - } - if (isToCamelCase) { - map = MapUtil.toCamelCaseMap(map); - } - copyProperties(map, bean, copyOptions); - return bean; - } - - // --------------------------------------------------------------------------------------------- fillBean - - /** - * 对象或Map转Bean - * - * @param 转换的Bean类型 - * @param source Bean对象或Map - * @param clazz 目标的Bean类型 - * @return Bean对象 - * @since 4.1.20 - */ - public static T toBean(Object source, Class clazz) { - return toBean(source, clazz, null); - } - - /** - * 对象或Map转Bean,忽略字段转换时发生的异常 - * - * @param 转换的Bean类型 - * @param source Bean对象或Map - * @param clazz 目标的Bean类型 - * @return Bean对象 - * @since 5.4.0 - */ - public static T toBeanIgnoreError(Object source, Class clazz) { - return toBean(source, clazz, CopyOptions.create().setIgnoreError(true)); - } - - /** - * 对象或Map转Bean,忽略字段转换时发生的异常 - * - * @param 转换的Bean类型 - * @param source Bean对象或Map - * @param clazz 目标的Bean类型 - * @param ignoreError 是否忽略注入错误 - * @return Bean对象 - * @since 5.4.0 - */ - public static T toBeanIgnoreCase(Object source, Class clazz, boolean ignoreError) { - return toBean(source, clazz, - CopyOptions.create() - .setIgnoreCase(true) - .setIgnoreError(ignoreError)); - } - - /** - * 对象或Map转Bean - * - * @param 转换的Bean类型 - * @param source Bean对象或Map - * @param clazz 目标的Bean类型 - * @param options 属性拷贝选项 - * @return Bean对象 - * @since 5.2.4 - */ - public static T toBean(Object source, Class clazz, CopyOptions options) { - final T target = ReflectUtil.newInstanceIfPossible(clazz); - copyProperties(source, target, options); - return target; - } - - /** - * ServletRequest 参数转Bean - * - * @param Bean类型 - * @param beanClass Bean Class - * @param valueProvider 值提供者 - * @param copyOptions 拷贝选项,见 {@link CopyOptions} - * @return Bean - */ - public static T toBean(Class beanClass, ValueProvider valueProvider, CopyOptions copyOptions) { - return fillBean(ReflectUtil.newInstanceIfPossible(beanClass), valueProvider, copyOptions); - } - - /** - * 填充Bean的核心方法 - * - * @param Bean类型 - * @param bean Bean - * @param valueProvider 值提供者 - * @param copyOptions 拷贝选项,见 {@link CopyOptions} - * @return Bean - */ - public static T fillBean(T bean, ValueProvider valueProvider, CopyOptions copyOptions) { - if (null == valueProvider) { - return bean; - } - - return BeanCopier.create(valueProvider, bean, copyOptions).copy(); - } - - // --------------------------------------------------------------------------------------------- beanToMap - - /** - * 对象转Map,不进行驼峰转下划线,不忽略值为空的字段 - * - * @param bean bean对象 - * @return Map - */ - public static Map beanToMap(Object bean) { - return beanToMap(bean, false, false); - } - - /** - * 对象转Map - * - * @param bean bean对象 - * @param isToUnderlineCase 是否转换为下划线模式 - * @param ignoreNullValue 是否忽略值为空的字段 - * @return Map - */ - public static Map beanToMap(Object bean, boolean isToUnderlineCase, boolean ignoreNullValue) { - return beanToMap(bean, new LinkedHashMap<>(), isToUnderlineCase, ignoreNullValue); - } - - /** - * 对象转Map - * - * @param bean bean对象 - * @param targetMap 目标的Map - * @param isToUnderlineCase 是否转换为下划线模式 - * @param ignoreNullValue 是否忽略值为空的字段 - * @return Map - * @since 3.2.3 - */ - public static Map beanToMap(Object bean, Map targetMap, final boolean isToUnderlineCase, boolean ignoreNullValue) { - if (bean == null) { - return null; - } - - return beanToMap(bean, targetMap, ignoreNullValue, key -> isToUnderlineCase ? StrUtil.toUnderlineCase(key) : key); - } - - /** - * 对象转Map
- * 通过实现{@link Editor} 可以自定义字段值,如果这个Editor返回null则忽略这个字段,以便实现: - * - *
-	 * 1. 字段筛选,可以去除不需要的字段
-	 * 2. 字段变换,例如实现驼峰转下划线
-	 * 3. 自定义字段前缀或后缀等等
-	 * 
- * - * @param bean bean对象 - * @param targetMap 目标的Map - * @param ignoreNullValue 是否忽略值为空的字段 - * @param keyEditor 属性字段(Map的key)编辑器,用于筛选、编辑key,如果这个Editor返回null则忽略这个字段 - * @return Map - * @since 4.0.5 - */ - public static Map beanToMap(Object bean, Map targetMap, boolean ignoreNullValue, Editor keyEditor) { - if (bean == null) { - return null; - } - - return BeanCopier.create(bean, targetMap, - CopyOptions.create() - .setIgnoreNullValue(ignoreNullValue) - .setFieldNameEditor(keyEditor) - ).copy(); - } - - // --------------------------------------------------------------------------------------------- copyProperties - - /** - * 按照Bean对象属性创建对应的Class对象,并忽略某些属性 - * - * @param 对象类型 - * @param source 源Bean对象 - * @param tClass 目标Class - * @param ignoreProperties 不拷贝的的属性列表 - * @return 目标对象 - */ - public static T copyProperties(Object source, Class tClass, String... ignoreProperties) { - T target = ReflectUtil.newInstanceIfPossible(tClass); - copyProperties(source, target, CopyOptions.create().setIgnoreProperties(ignoreProperties)); - return target; - } - - /** - * 复制Bean对象属性
- * 限制类用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类 - * - * @param source 源Bean对象 - * @param target 目标Bean对象 - * @param ignoreProperties 不拷贝的的属性列表 - */ - public static void copyProperties(Object source, Object target, String... ignoreProperties) { - copyProperties(source, target, CopyOptions.create().setIgnoreProperties(ignoreProperties)); - } - - /** - * 复制Bean对象属性
- * - * @param source 源Bean对象 - * @param target 目标Bean对象 - * @param ignoreCase 是否忽略大小写 - */ - public static void copyProperties(Object source, Object target, boolean ignoreCase) { - BeanCopier.create(source, target, CopyOptions.create().setIgnoreCase(ignoreCase)).copy(); - } - - /** - * 复制Bean对象属性
- * 限制类用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类 - * - * @param source 源Bean对象 - * @param target 目标Bean对象 - * @param copyOptions 拷贝选项,见 {@link CopyOptions} - */ - public static void copyProperties(Object source, Object target, CopyOptions copyOptions) { - if (null == copyOptions) { - copyOptions = new CopyOptions(); - } - BeanCopier.create(source, target, copyOptions).copy(); - } - - /** - * 给定的Bean的类名是否匹配指定类名字符串
- * 如果isSimple为{@code false},则只匹配类名而忽略包名,例如:cn.hutool.TestEntity只匹配TestEntity
- * 如果isSimple为{@code true},则匹配包括包名的全类名,例如:cn.hutool.TestEntity匹配cn.hutool.TestEntity - * - * @param bean Bean - * @param beanClassName Bean的类名 - * @param isSimple 是否只匹配类名而忽略包名,true表示忽略包名 - * @return 是否匹配 - * @since 4.0.6 - */ - public static boolean isMatchName(Object bean, String beanClassName, boolean isSimple) { - return ClassUtil.getClassName(bean, isSimple).equals(isSimple ? StrUtil.upperFirst(beanClassName) : beanClassName); - } - - /** - * 把Bean里面的String属性做trim操作。此方法直接对传入的Bean做修改。 - *

- * 通常bean直接用来绑定页面的input,用户的输入可能首尾存在空格,通常保存数据库前需要把首尾空格去掉 - * - * @param Bean类型 - * @param bean Bean对象 - * @param ignoreFields 不需要trim的Field名称列表(不区分大小写) - * @return 处理后的Bean对象 - */ - public static T trimStrFields(T bean, String... ignoreFields) { - if (bean == null) { - return null; - } - - final Field[] fields = ReflectUtil.getFields(bean.getClass()); - for (Field field : fields) { - if (ModifierUtil.isStatic(field)) { - continue; - } - if (ignoreFields != null && ArrayUtil.containsIgnoreCase(ignoreFields, field.getName())) { - // 不处理忽略的Fields - continue; - } - if (String.class.equals(field.getType())) { - // 只有String的Field才处理 - final String val = (String) ReflectUtil.getFieldValue(bean, field); - if (null != val) { - final String trimVal = StrUtil.trim(val); - if (false == val.equals(trimVal)) { - // Field Value不为null,且首尾有空格才处理 - ReflectUtil.setFieldValue(bean, field, trimVal); - } - } - } - } - - return bean; - } - - /** - * 判断Bean是否为非空对象,非空对象表示本身不为{@code null}或者含有非{@code null}属性的对象 - * - * @param bean Bean对象 - * @param ignoreFiledNames 忽略检查的字段名 - * @return 是否为空,{@code true} - 空 / {@code false} - 非空 - * @since 5.0.7 - */ - public static boolean isNotEmpty(Object bean, String... ignoreFiledNames) { - return false == isEmpty(bean, ignoreFiledNames); - } - - /** - * 判断Bean是否为空对象,空对象表示本身为{@code null}或者所有属性都为{@code null}
- * 此方法不判断static属性 - * - * @param bean Bean对象 - * @param ignoreFiledNames 忽略检查的字段名 - * @return 是否为空,{@code true} - 空 / {@code false} - 非空 - * @since 4.1.10 - */ - public static boolean isEmpty(Object bean, String... ignoreFiledNames) { - if (null != bean) { - for (Field field : ReflectUtil.getFields(bean.getClass())) { - if (ModifierUtil.isStatic(field)) { - continue; - } - if ((false == ArrayUtil.contains(ignoreFiledNames, field.getName())) - && null != ReflectUtil.getFieldValue(bean, field)) { - return false; - } - } - } - return true; - } - - /** - * 判断Bean是否包含值为{@code null}的属性
- * 对象本身为{@code null}也返回true - * - * @param bean Bean对象 - * @param ignoreFiledNames 忽略检查的字段名 - * @return 是否包含值为null的属性,{@code true} - 包含 / {@code false} - 不包含 - * @since 4.1.10 - */ - public static boolean hasNullField(Object bean, String... ignoreFiledNames) { - if (null == bean) { - return true; - } - for (Field field : ReflectUtil.getFields(bean.getClass())) { - if (ModifierUtil.isStatic(field)) { - continue; - } - if ((false == ArrayUtil.contains(ignoreFiledNames, field.getName())) - && null == ReflectUtil.getFieldValue(bean, field)) { - return true; - } - } - return false; - } + /** + * 判断是否为可读的Bean对象,判定方法是: + * + *

+     *     1、是否存在只有无参数的getXXX方法或者isXXX方法
+     *     2、是否存在public类型的字段
+     * 
+ * + * @param clazz 待测试类 + * @return 是否为可读的Bean对象 + * @see #hasGetter(Class) + * @see #hasPublicField(Class) + */ + public static boolean isReadableBean(Class clazz) { + return hasGetter(clazz) || hasPublicField(clazz); + } + + /** + * 判断是否为Bean对象,判定方法是: + * + *
+     *     1、是否存在只有一个参数的setXXX方法
+     *     2、是否存在public类型的字段
+     * 
+ * + * @param clazz 待测试类 + * @return 是否为Bean对象 + * @see #hasSetter(Class) + * @see #hasPublicField(Class) + */ + public static boolean isBean(Class clazz) { + return hasSetter(clazz) || hasPublicField(clazz); + } + + /** + * 判断是否有Setter方法
+ * 判定方法是是否存在只有一个参数的setXXX方法 + * + * @param clazz 待测试类 + * @return 是否为Bean对象 + * @since 4.2.2 + */ + public static boolean hasSetter(Class clazz) { + if (ClassUtil.isNormalClass(clazz)) { + final Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (method.getParameterTypes().length == 1 && method.getName().startsWith("set")) { + // 检测包含标准的setXXX方法即视为标准的JavaBean + return true; + } + } + } + return false; + } + + /** + * 判断是否为Bean对象
+ * 判定方法是是否存在只有一个参数的setXXX方法 + * + * @param clazz 待测试类 + * @return 是否为Bean对象 + * @since 4.2.2 + */ + public static boolean hasGetter(Class clazz) { + if (ClassUtil.isNormalClass(clazz)) { + for (Method method : clazz.getMethods()) { + if (method.getParameterTypes().length == 0) { + if (method.getName().startsWith("get") || method.getName().startsWith("is")) { + return true; + } + } + } + } + return false; + } + + /** + * 指定类中是否有public类型字段(static字段除外) + * + * @param clazz 待测试类 + * @return 是否有public类型字段 + * @since 5.1.0 + */ + public static boolean hasPublicField(Class clazz) { + if (ClassUtil.isNormalClass(clazz)) { + for (Field field : clazz.getFields()) { + if (ModifierUtil.isPublic(field) && false == ModifierUtil.isStatic(field)) { + //非static的public字段 + return true; + } + } + } + return false; + } + + /** + * 创建动态Bean + * + * @param bean 普通Bean或Map + * @return {@link DynaBean} + * @since 3.0.7 + */ + public static DynaBean createDynaBean(Object bean) { + return new DynaBean(bean); + } + + /** + * 查找类型转换器 {@link PropertyEditor} + * + * @param type 需要转换的目标类型 + * @return {@link PropertyEditor} + */ + public static PropertyEditor findEditor(Class type) { + return PropertyEditorManager.findEditor(type); + } + + /** + * 获取{@link BeanDesc} Bean描述信息 + * + * @param clazz Bean类 + * @return {@link BeanDesc} + * @since 3.1.2 + */ + public static BeanDesc getBeanDesc(Class clazz) { + 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 + + /** + * 获得Bean字段描述数组 + * + * @param clazz Bean类 + * @return 字段描述数组 + * @throws BeanException 获取属性异常 + */ + public static PropertyDescriptor[] getPropertyDescriptors(Class clazz) throws BeanException { + BeanInfo beanInfo; + try { + beanInfo = Introspector.getBeanInfo(clazz); + } catch (IntrospectionException e) { + throw new BeanException(e); + } + return ArrayUtil.filter(beanInfo.getPropertyDescriptors(), (Filter) t -> { + // 过滤掉getClass方法 + return false == "class".equals(t.getName()); + }); + } + + /** + * 获得字段名和字段描述Map,获得的结果会缓存在 {@link BeanInfoCache}中 + * + * @param clazz Bean类 + * @param ignoreCase 是否忽略大小写 + * @return 字段名和字段描述Map + * @throws BeanException 获取属性异常 + */ + public static Map getPropertyDescriptorMap(Class clazz, boolean ignoreCase) throws BeanException { + return BeanInfoCache.INSTANCE.getPropertyDescriptorMap(clazz, ignoreCase, () -> internalGetPropertyDescriptorMap(clazz, ignoreCase)); + } + + /** + * 获得字段名和字段描述Map。内部使用,直接获取Bean类的PropertyDescriptor + * + * @param clazz Bean类 + * @param ignoreCase 是否忽略大小写 + * @return 字段名和字段描述Map + * @throws BeanException 获取属性异常 + */ + private static Map internalGetPropertyDescriptorMap(Class clazz, boolean ignoreCase) throws BeanException { + final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz); + final Map map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1) + : new HashMap<>((int) (propertyDescriptors.length), 1); + + for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { + map.put(propertyDescriptor.getName(), propertyDescriptor); + } + return map; + } + + /** + * 获得Bean类属性描述,大小写敏感 + * + * @param clazz Bean类 + * @param fieldName 字段名 + * @return PropertyDescriptor + * @throws BeanException 获取属性异常 + */ + public static PropertyDescriptor getPropertyDescriptor(Class clazz, final String fieldName) throws BeanException { + return getPropertyDescriptor(clazz, fieldName, false); + } + + /** + * 获得Bean类属性描述 + * + * @param clazz Bean类 + * @param fieldName 字段名 + * @param ignoreCase 是否忽略大小写 + * @return PropertyDescriptor + * @throws BeanException 获取属性异常 + */ + public static PropertyDescriptor getPropertyDescriptor(Class clazz, final String fieldName, boolean ignoreCase) throws BeanException { + final Map map = getPropertyDescriptorMap(clazz, ignoreCase); + return (null == map) ? null : map.get(fieldName); + } + + /** + * 获得字段值,通过反射直接获得字段值,并不调用getXXX方法
+ * 对象同样支持Map类型,fieldNameOrIndex即为key + * + * @param bean Bean对象 + * @param fieldNameOrIndex 字段名或序号,序号支持负数 + * @return 字段值 + */ + public static Object getFieldValue(Object bean, String fieldNameOrIndex) { + if (null == bean || null == fieldNameOrIndex) { + return null; + } + + if (bean instanceof Map) { + return ((Map) bean).get(fieldNameOrIndex); + } + + // 是否是索引 + boolean isIndex = fieldNameOrIndex.matches("\\d"); + + if (isIndex) { + if (bean instanceof Collection) { + return CollUtil.get((Collection) bean, Integer.parseInt(fieldNameOrIndex)); + } + if (ArrayUtil.isArray(bean)) { + return ArrayUtil.get(bean, Integer.parseInt(fieldNameOrIndex)); + } + } else { + // 不是索引 则将集合每个对象的属性拿出来封装成新的集合 + if (bean instanceof Collection) { + Collection collection = (Collection) bean; + List result = new ArrayList<>(collection.size()); + for (Object o : collection) { + result.add(getFieldValue(o, fieldNameOrIndex)); + } + return result; + } + if (ArrayUtil.isArray(bean)) { + int length = Array.getLength(bean); + Object[] result = new Object[length]; + for (int i = 0; i < length; i++) { + result[i] = getFieldValue(ArrayUtil.get(bean, i), fieldNameOrIndex); + } + return result; + } + } + + // 普通Bean对象 + return ReflectUtil.getFieldValue(bean, fieldNameOrIndex); + } + + /** + * 设置字段值,,通过反射设置字段值,并不调用setXXX方法
+ * 对象同样支持Map类型,fieldNameOrIndex即为key + * + * @param bean Bean + * @param fieldNameOrIndex 字段名或序号,序号支持负数 + * @param value 值 + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void setFieldValue(Object bean, String fieldNameOrIndex, Object value) { + if (bean instanceof Map) { + ((Map) bean).put(fieldNameOrIndex, value); + } else if (bean instanceof List) { + CollUtil.setOrAppend((List) bean, Convert.toInt(fieldNameOrIndex), value); + } else if (ArrayUtil.isArray(bean)) { + ArrayUtil.setOrAppend(bean, Convert.toInt(fieldNameOrIndex), value); + } else { + // 普通Bean对象 + ReflectUtil.setFieldValue(bean, fieldNameOrIndex, value); + } + } + + /** + * 解析Bean中的属性值 + * + * @param 属性值类型 + * @param bean Bean对象,支持Map、List、Collection、Array + * @param expression 表达式,例如:person.friend[5].name + * @return Bean属性值 + * @see BeanPath#get(Object) + * @since 3.0.7 + */ + @SuppressWarnings("unchecked") + public static T getProperty(Object bean, String expression) { + return (T) BeanPath.create(expression).get(bean); + } + + /** + * 解析Bean中的属性值 + * + * @param bean Bean对象,支持Map、List、Collection、Array + * @param expression 表达式,例如:person.friend[5].name + * @param value 属性值 + * @see BeanPath#get(Object) + * @since 4.0.6 + */ + public static void setProperty(Object bean, String expression, Object value) { + BeanPath.create(expression).set(bean, value); + } + + // --------------------------------------------------------------------------------------------- mapToBean + + /** + * Map转换为Bean对象 + * + * @param Bean类型 + * @param map {@link Map} + * @param beanClass Bean Class + * @param isIgnoreError 是否忽略注入错误 + * @return Bean + * @deprecated 请使用 {@link #toBean(Object, Class)} 或 {@link #toBeanIgnoreError(Object, Class)} + */ + @Deprecated + public static T mapToBean(Map map, Class beanClass, boolean isIgnoreError) { + return fillBeanWithMap(map, ReflectUtil.newInstanceIfPossible(beanClass), isIgnoreError); + } + + /** + * Map转换为Bean对象
+ * 忽略大小写 + * + * @param Bean类型 + * @param map Map + * @param beanClass Bean Class + * @param isIgnoreError 是否忽略注入错误 + * @return Bean + * @deprecated 请使用 {@link #toBeanIgnoreCase(Object, Class, boolean)} + */ + @Deprecated + public static T mapToBeanIgnoreCase(Map map, Class beanClass, boolean isIgnoreError) { + return fillBeanWithMapIgnoreCase(map, ReflectUtil.newInstanceIfPossible(beanClass), isIgnoreError); + } + + /** + * Map转换为Bean对象 + * + * @param Bean类型 + * @param map {@link Map} + * @param beanClass Bean Class + * @param copyOptions 转Bean选项 + * @return Bean + * @deprecated 请使用 {@link #toBean(Object, Class, CopyOptions)} + */ + @Deprecated + public static T mapToBean(Map map, Class beanClass, CopyOptions copyOptions) { + return fillBeanWithMap(map, ReflectUtil.newInstanceIfPossible(beanClass), copyOptions); + } + + /** + * 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(Map map, Class beanClass, boolean isToCamelCase, CopyOptions copyOptions) { + return fillBeanWithMap(map, ReflectUtil.newInstanceIfPossible(beanClass), isToCamelCase, copyOptions); + } + + // --------------------------------------------------------------------------------------------- fillBeanWithMap + + /** + * 使用Map填充Bean对象 + * + * @param Bean类型 + * @param map Map + * @param bean Bean + * @param isIgnoreError 是否忽略注入错误 + * @return Bean + */ + public static T fillBeanWithMap(Map map, T bean, 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(Map map, T bean, boolean isToCamelCase, boolean isIgnoreError) { + return fillBeanWithMap(map, bean, isToCamelCase, CopyOptions.create().setIgnoreError(isIgnoreError)); + } + + /** + * 使用Map填充Bean对象,忽略大小写 + * + * @param Bean类型 + * @param map Map + * @param bean Bean + * @param isIgnoreError 是否忽略注入错误 + * @return Bean + */ + public static T fillBeanWithMapIgnoreCase(Map map, T bean, boolean isIgnoreError) { + return fillBeanWithMap(map, bean, CopyOptions.create().setIgnoreCase(true).setIgnoreError(isIgnoreError)); + } + + /** + * 使用Map填充Bean对象 + * + * @param Bean类型 + * @param map Map + * @param bean Bean + * @param copyOptions 属性复制选项 {@link CopyOptions} + * @return Bean + */ + public static T fillBeanWithMap(Map map, T bean, 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, T bean, boolean isToCamelCase, CopyOptions copyOptions) { + if (MapUtil.isEmpty(map)) { + return bean; + } + if (isToCamelCase) { + map = MapUtil.toCamelCaseMap(map); + } + copyProperties(map, bean, copyOptions); + return bean; + } + + // --------------------------------------------------------------------------------------------- fillBean + + /** + * 对象或Map转Bean + * + * @param 转换的Bean类型 + * @param source Bean对象或Map + * @param clazz 目标的Bean类型 + * @return Bean对象 + * @since 4.1.20 + */ + public static T toBean(Object source, Class clazz) { + return toBean(source, clazz, null); + } + + /** + * 对象或Map转Bean,忽略字段转换时发生的异常 + * + * @param 转换的Bean类型 + * @param source Bean对象或Map + * @param clazz 目标的Bean类型 + * @return Bean对象 + * @since 5.4.0 + */ + public static T toBeanIgnoreError(Object source, Class clazz) { + return toBean(source, clazz, CopyOptions.create().setIgnoreError(true)); + } + + /** + * 对象或Map转Bean,忽略字段转换时发生的异常 + * + * @param 转换的Bean类型 + * @param source Bean对象或Map + * @param clazz 目标的Bean类型 + * @param ignoreError 是否忽略注入错误 + * @return Bean对象 + * @since 5.4.0 + */ + public static T toBeanIgnoreCase(Object source, Class clazz, boolean ignoreError) { + return toBean(source, clazz, + CopyOptions.create() + .setIgnoreCase(true) + .setIgnoreError(ignoreError)); + } + + /** + * 对象或Map转Bean + * + * @param 转换的Bean类型 + * @param source Bean对象或Map + * @param clazz 目标的Bean类型 + * @param options 属性拷贝选项 + * @return Bean对象 + * @since 5.2.4 + */ + public static T toBean(Object source, Class clazz, CopyOptions options) { + final T target = ReflectUtil.newInstanceIfPossible(clazz); + copyProperties(source, target, options); + return target; + } + + /** + * ServletRequest 参数转Bean + * + * @param Bean类型 + * @param beanClass Bean Class + * @param valueProvider 值提供者 + * @param copyOptions 拷贝选项,见 {@link CopyOptions} + * @return Bean + */ + public static T toBean(Class beanClass, ValueProvider valueProvider, CopyOptions copyOptions) { + return fillBean(ReflectUtil.newInstanceIfPossible(beanClass), valueProvider, copyOptions); + } + + /** + * 填充Bean的核心方法 + * + * @param Bean类型 + * @param bean Bean + * @param valueProvider 值提供者 + * @param copyOptions 拷贝选项,见 {@link CopyOptions} + * @return Bean + */ + public static T fillBean(T bean, ValueProvider valueProvider, CopyOptions copyOptions) { + if (null == valueProvider) { + return bean; + } + + return BeanCopier.create(valueProvider, bean, copyOptions).copy(); + } + + // --------------------------------------------------------------------------------------------- beanToMap + + /** + * 对象转Map,不进行驼峰转下划线,不忽略值为空的字段 + * + * @param bean bean对象 + * @return Map + */ + public static Map beanToMap(Object bean) { + return beanToMap(bean, false, false); + } + + /** + * 对象转Map + * + * @param bean bean对象 + * @param isToUnderlineCase 是否转换为下划线模式 + * @param ignoreNullValue 是否忽略值为空的字段 + * @return Map + */ + public static Map beanToMap(Object bean, boolean isToUnderlineCase, boolean ignoreNullValue) { + return beanToMap(bean, new LinkedHashMap<>(), isToUnderlineCase, ignoreNullValue); + } + + /** + * 对象转Map + * + * @param bean bean对象 + * @param targetMap 目标的Map + * @param isToUnderlineCase 是否转换为下划线模式 + * @param ignoreNullValue 是否忽略值为空的字段 + * @return Map + * @since 3.2.3 + */ + public static Map beanToMap(Object bean, Map targetMap, final boolean isToUnderlineCase, boolean ignoreNullValue) { + if (bean == null) { + return null; + } + + return beanToMap(bean, targetMap, ignoreNullValue, key -> isToUnderlineCase ? StrUtil.toUnderlineCase(key) : key); + } + + /** + * 对象转Map
+ * 通过实现{@link Editor} 可以自定义字段值,如果这个Editor返回null则忽略这个字段,以便实现: + * + *
+     * 1. 字段筛选,可以去除不需要的字段
+     * 2. 字段变换,例如实现驼峰转下划线
+     * 3. 自定义字段前缀或后缀等等
+     * 
+ * + * @param bean bean对象 + * @param targetMap 目标的Map + * @param ignoreNullValue 是否忽略值为空的字段 + * @param keyEditor 属性字段(Map的key)编辑器,用于筛选、编辑key,如果这个Editor返回null则忽略这个字段 + * @return Map + * @since 4.0.5 + */ + public static Map beanToMap(Object bean, Map targetMap, boolean ignoreNullValue, Editor keyEditor) { + if (bean == null) { + return null; + } + + return BeanCopier.create(bean, targetMap, + CopyOptions.create() + .setIgnoreNullValue(ignoreNullValue) + .setFieldNameEditor(keyEditor) + ).copy(); + } + + // --------------------------------------------------------------------------------------------- copyProperties + + /** + * 按照Bean对象属性创建对应的Class对象,并忽略某些属性 + * + * @param 对象类型 + * @param source 源Bean对象 + * @param tClass 目标Class + * @param ignoreProperties 不拷贝的的属性列表 + * @return 目标对象 + */ + public static T copyProperties(Object source, Class tClass, String... ignoreProperties) { + T target = ReflectUtil.newInstanceIfPossible(tClass); + copyProperties(source, target, CopyOptions.create().setIgnoreProperties(ignoreProperties)); + return target; + } + + /** + * 复制Bean对象属性
+ * 限制类用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类 + * + * @param source 源Bean对象 + * @param target 目标Bean对象 + * @param ignoreProperties 不拷贝的的属性列表 + */ + public static void copyProperties(Object source, Object target, String... ignoreProperties) { + copyProperties(source, target, CopyOptions.create().setIgnoreProperties(ignoreProperties)); + } + + /** + * 复制Bean对象属性
+ * + * @param source 源Bean对象 + * @param target 目标Bean对象 + * @param ignoreCase 是否忽略大小写 + */ + public static void copyProperties(Object source, Object target, boolean ignoreCase) { + BeanCopier.create(source, target, CopyOptions.create().setIgnoreCase(ignoreCase)).copy(); + } + + /** + * 复制Bean对象属性
+ * 限制类用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类 + * + * @param source 源Bean对象 + * @param target 目标Bean对象 + * @param copyOptions 拷贝选项,见 {@link CopyOptions} + */ + public static void copyProperties(Object source, Object target, CopyOptions copyOptions) { + if (null == copyOptions) { + copyOptions = new CopyOptions(); + } + BeanCopier.create(source, target, copyOptions).copy(); + } + + /** + * 给定的Bean的类名是否匹配指定类名字符串
+ * 如果isSimple为{@code false},则只匹配类名而忽略包名,例如:cn.hutool.TestEntity只匹配TestEntity
+ * 如果isSimple为{@code true},则匹配包括包名的全类名,例如:cn.hutool.TestEntity匹配cn.hutool.TestEntity + * + * @param bean Bean + * @param beanClassName Bean的类名 + * @param isSimple 是否只匹配类名而忽略包名,true表示忽略包名 + * @return 是否匹配 + * @since 4.0.6 + */ + public static boolean isMatchName(Object bean, String beanClassName, boolean isSimple) { + return ClassUtil.getClassName(bean, isSimple).equals(isSimple ? StrUtil.upperFirst(beanClassName) : beanClassName); + } + + /** + * 把Bean里面的String属性做trim操作。此方法直接对传入的Bean做修改。 + *

+ * 通常bean直接用来绑定页面的input,用户的输入可能首尾存在空格,通常保存数据库前需要把首尾空格去掉 + * + * @param Bean类型 + * @param bean Bean对象 + * @param ignoreFields 不需要trim的Field名称列表(不区分大小写) + * @return 处理后的Bean对象 + */ + public static T trimStrFields(T bean, String... ignoreFields) { + if (bean == null) { + return null; + } + + final Field[] fields = ReflectUtil.getFields(bean.getClass()); + for (Field field : fields) { + if (ModifierUtil.isStatic(field)) { + continue; + } + if (ignoreFields != null && ArrayUtil.containsIgnoreCase(ignoreFields, field.getName())) { + // 不处理忽略的Fields + continue; + } + if (String.class.equals(field.getType())) { + // 只有String的Field才处理 + final String val = (String) ReflectUtil.getFieldValue(bean, field); + if (null != val) { + final String trimVal = StrUtil.trim(val); + if (false == val.equals(trimVal)) { + // Field Value不为null,且首尾有空格才处理 + ReflectUtil.setFieldValue(bean, field, trimVal); + } + } + } + } + + return bean; + } + + /** + * 判断Bean是否为非空对象,非空对象表示本身不为{@code null}或者含有非{@code null}属性的对象 + * + * @param bean Bean对象 + * @param ignoreFiledNames 忽略检查的字段名 + * @return 是否为空,{@code true} - 空 / {@code false} - 非空 + * @since 5.0.7 + */ + public static boolean isNotEmpty(Object bean, String... ignoreFiledNames) { + return false == isEmpty(bean, ignoreFiledNames); + } + + /** + * 判断Bean是否为空对象,空对象表示本身为{@code null}或者所有属性都为{@code null}
+ * 此方法不判断static属性 + * + * @param bean Bean对象 + * @param ignoreFiledNames 忽略检查的字段名 + * @return 是否为空,{@code true} - 空 / {@code false} - 非空 + * @since 4.1.10 + */ + public static boolean isEmpty(Object bean, String... ignoreFiledNames) { + if (null != bean) { + for (Field field : ReflectUtil.getFields(bean.getClass())) { + if (ModifierUtil.isStatic(field)) { + continue; + } + if ((false == ArrayUtil.contains(ignoreFiledNames, field.getName())) + && null != ReflectUtil.getFieldValue(bean, field)) { + return false; + } + } + } + return true; + } + + /** + * 判断Bean是否包含值为{@code null}的属性
+ * 对象本身为{@code null}也返回true + * + * @param bean Bean对象 + * @param ignoreFiledNames 忽略检查的字段名 + * @return 是否包含值为null的属性,{@code true} - 包含 / {@code false} - 不包含 + * @since 4.1.10 + */ + public static boolean hasNullField(Object bean, String... ignoreFiledNames) { + if (null == bean) { + return true; + } + for (Field field : ReflectUtil.getFields(bean.getClass())) { + if (ModifierUtil.isStatic(field)) { + continue; + } + if ((false == ArrayUtil.contains(ignoreFiledNames, field.getName())) + && null == ReflectUtil.getFieldValue(bean, field)) { + return true; + } + } + return false; + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index 56ec38dacd..ff79d1322e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -5,6 +5,7 @@ import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; import lombok.Data; import lombok.Getter; import lombok.Setter; @@ -16,13 +17,7 @@ import java.io.Serializable; import java.lang.reflect.Type; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; /** * Bean工具单元测试 @@ -31,6 +26,25 @@ import java.util.UUID; */ public class BeanUtilTest { + @Test + public void getFieldValue(){ + TestPojo testPojo = new TestPojo(); + testPojo.setName("名字"); + + TestPojo2 testPojo2 = new TestPojo2(); + testPojo2.setAge(2); + TestPojo2 testPojo3 = new TestPojo2(); + testPojo3.setAge(3); + + + testPojo.setTestPojo2List(new TestPojo2[]{testPojo2,testPojo3}); + + BeanPath beanPath = BeanPath.create("testPojo2List.age"); + Object o = beanPath.get(testPojo); + Object age = ArrayUtil.get(o,1); + System.out.println(age); + } + @Test public void isBeanTest() { @@ -537,4 +551,13 @@ public class BeanUtilTest { private Long sortOrder; private Date createTime; } + @Data + public static class TestPojo{ + private String name; + private TestPojo2[] testPojo2List; + } + @Data + public static class TestPojo2{ + private int age; + } } -- Gitee From 2e3c753d2336c63c78e16f04903aec34fb0db413 Mon Sep 17 00:00:00 2001 From: yangchuanlong Date: Tue, 12 Jan 2021 09:47:14 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=88=A4=E6=96=AD=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E6=AD=A3=E5=88=99=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 39d6676a36..b6b59cf0b0 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 @@ -265,7 +265,7 @@ public class BeanUtil { } // 是否是索引 - boolean isIndex = fieldNameOrIndex.matches("\\d"); + boolean isIndex = fieldNameOrIndex.matches("\\d+"); if (isIndex) { if (bean instanceof Collection) { -- Gitee