This commit is contained in:
Looly 2024-05-17 19:52:12 +08:00
parent 7999baec85
commit 3a73cdea51
17 changed files with 658 additions and 492 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
@ -12,95 +12,31 @@
package org.dromara.hutool.core.bean;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.map.CaseInsensitiveMap;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.reflect.ModifierUtil;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.BooleanUtil;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Bean信息描述做为BeanInfo替代方案此对象持有JavaBean中的setters和getters等相关信息描述<br>
* 查找Getter和Setter方法时会
* Bean描述通过反射等方式获取Bean的settergetter字段等信息
*
* <pre>
* 1. 忽略字段和方法名的大小写
* 2. Getter查找getXXXisXXXgetIsXXX
* 3. Setter查找setXXXsetIsXXX
* 4. Setter忽略参数值与字段值不匹配的情况因此有多个参数类型的重载时会调用首次匹配的
* </pre>
*
* @author looly
* @since 3.1.2
* @author Looly
* @since 6.0.0
*/
public class BeanDesc implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Bean类
*/
private final Class<?> beanClass;
/**
* 属性Map
*/
private final Map<String, PropDesc> propMap = new LinkedHashMap<>();
/**
* 构造
*
* @param beanClass Bean类
*/
public BeanDesc(final Class<?> beanClass) {
Assert.notNull(beanClass);
this.beanClass = beanClass;
init();
}
/**
* 获取Bean的全类名
*
* @return Bean的类名
*/
public String getName() {
return this.beanClass.getName();
}
/**
* 获取Bean的简单类名
*
* @return Bean的类名
*/
public String getSimpleName() {
return this.beanClass.getSimpleName();
}
public interface BeanDesc extends Serializable {
/**
* 获取字段名-字段属性Map
*
* @param ignoreCase 是否忽略大小写true为忽略false不忽略
* @return 字段名-字段属性Map
*/
public Map<String, PropDesc> getPropMap(final boolean ignoreCase) {
return ignoreCase ? new CaseInsensitiveMap<>(1, this.propMap) : this.propMap;
}
Map<String, PropDesc> getPropMap(final boolean ignoreCase);
/**
* 获取字段属性列表
*
* @return {@link PropDesc} 列表
*/
public Collection<PropDesc> getProps() {
return this.propMap.values();
}
Collection<PropDesc> getProps();
/**
* 获取属性如果不存在返回null
@ -108,272 +44,5 @@ public class BeanDesc implements Serializable {
* @param fieldName 字段名
* @return {@link PropDesc}
*/
public PropDesc getProp(final String fieldName) {
return this.propMap.get(fieldName);
}
/**
* 获得字段名对应的字段对象如果不存在返回null
*
* @param fieldName 字段名
* @return 字段值
*/
public Field getField(final String fieldName) {
final PropDesc desc = this.propMap.get(fieldName);
return null == desc ? null : desc.getField();
}
/**
* 获取Getter方法如果不存在返回null
*
* @param fieldName 字段名
* @return Getter方法
*/
public Method getGetter(final String fieldName) {
final PropDesc desc = this.propMap.get(fieldName);
return null == desc ? null : desc.getGetter();
}
/**
* 获取Setter方法如果不存在返回null
*
* @param fieldName 字段名
* @return Setter方法
*/
public Method getSetter(final String fieldName) {
final PropDesc desc = this.propMap.get(fieldName);
return null == desc ? null : desc.getSetter();
}
// ------------------------------------------------------------------------------------------------------ Private method start
/**
* 初始化<br>
* 只有与属性关联的相关Getter和Setter方法才会被读取无关的getXXX和setXXX都被忽略
*/
private void init() {
if (RecordUtil.isRecord(this.beanClass)) {
initForRecord();
} else{
initForBean();
}
}
/**
* 针对Record类的反射初始化
*/
private void initForRecord() {
final Method[] getters = MethodUtil.getPublicMethods(this.beanClass, method -> 0 == method.getParameterCount());
// 排除静态属性和对象子类
final Field[] fields = FieldUtil.getFields(this.beanClass, field -> !ModifierUtil.isStatic(field) && !FieldUtil.isOuterClassField(field));
for (final Field field : fields) {
for (final Method getter : getters) {
if (field.getName().equals(getter.getName())) {
//record对象getter方法与字段同名
final PropDesc prop = new PropDesc(field, getter, null);
this.propMap.putIfAbsent(prop.getFieldName(), prop);
}
}
}
}
/**
* 普通Bean初始化
*/
private void initForBean() {
final Method[] gettersAndSetters = MethodUtil.getPublicMethods(this.beanClass, MethodUtil::isGetterOrSetterIgnoreCase);
// 排除静态属性和对象子类
final Field[] fields = FieldUtil.getFields(this.beanClass, field -> !ModifierUtil.isStatic(field) && !FieldUtil.isOuterClassField(field));
PropDesc prop;
for (final Field field : fields) {
prop = createProp(field, gettersAndSetters);
// 只有不存在时才放入防止父类属性覆盖子类属性
this.propMap.putIfAbsent(prop.getFieldName(), prop);
}
}
/**
* 根据字段创建属性描述<br>
* 查找Getter和Setter方法时会
*
* <pre>
* 1. 忽略字段和方法名的大小写
* 2. Getter查找getXXXisXXXgetIsXXX
* 3. Setter查找setXXXsetIsXXX
* 4. Setter忽略参数值与字段值不匹配的情况因此有多个参数类型的重载时会调用首次匹配的
* </pre>
*
* @param field 字段
* @param methods 类中所有的方法
* @return {@link PropDesc}
* @since 4.0.2
*/
private PropDesc createProp(final Field field, final Method[] methods) {
final PropDesc prop = findProp(field, methods, false);
// 忽略大小写重新匹配一次
if (null == prop.getter || null == prop.setter) {
final PropDesc propIgnoreCase = findProp(field, methods, true);
if (null == prop.getter) {
prop.getter = propIgnoreCase.getter;
}
if (null == prop.setter) {
prop.setter = propIgnoreCase.setter;
}
}
return prop;
}
/**
* 查找字段对应的Getter和Setter方法
*
* @param field 字段
* @param gettersOrSetters 类中所有的Getter或Setter方法
* @param ignoreCase 是否忽略大小写匹配
* @return PropDesc
*/
private PropDesc findProp(final Field field, final Method[] gettersOrSetters, final boolean ignoreCase) {
final String fieldName = field.getName();
final Class<?> fieldType = field.getType();
final boolean isBooleanField = BooleanUtil.isBoolean(fieldType);
// Getter: name -> getName, Setter: name -> setName
final Method[] getterAndSetter = findGetterAndSetter(fieldName, fieldType, gettersOrSetters, ignoreCase);
if (isBooleanField) {
if (null == getterAndSetter[0]) {
// isName -> isName or isIsName
// name -> isName
getterAndSetter[0] = findGetterForBoolean(fieldName, gettersOrSetters, ignoreCase);
}
if (null == getterAndSetter[1]) {
// isName -> setName
getterAndSetter[1] = findSetterForBoolean(fieldName, gettersOrSetters, ignoreCase);
}
}
return new PropDesc(field, getterAndSetter[0], getterAndSetter[1]);
}
/**
* 查找字段对应的Getter和Setter方法<br>
* 此方法不区分是否为boolean字段查找规则为
* <ul>
* <li>Getter要求无参数且返回值是字段类型或字段的父类</li>
* <li>Getter中如果字段为name匹配getName</li>
* <li>Setter要求一个参数且参数必须为字段类型或字段的子类</li>
* <li>Setter中如果字段为name匹配setName</li>
* </ul>
*
* @param fieldName 字段名
* @param fieldType 字段类型
* @param gettersOrSetters 类中所有的Getter或Setter方法
* @return PropDesc
*/
private Method[] findGetterAndSetter(final String fieldName, final Class<?> fieldType,
final Method[] gettersOrSetters, final boolean ignoreCase) {
Method getter = null;
Method setter = null;
String methodName;
for (final Method method : gettersOrSetters) {
methodName = method.getName();
if (0 == method.getParameterCount()) {
// 无参数可能为Getter方法
if (StrUtil.equals(methodName, StrUtil.genGetter(fieldName), ignoreCase) &&
method.getReturnType().isAssignableFrom(fieldType)) {
// getter的返回类型必须为字段类型或字段的父类
getter = method;
}
} else if (StrUtil.equals(methodName, StrUtil.genSetter(fieldName), ignoreCase) &&
fieldType.isAssignableFrom(method.getParameterTypes()[0])) {
// setter方法的参数必须为字段类型或字段的子类
setter = method;
}
if (null != getter && null != setter) {
// 如果Getter和Setter方法都找到了不再继续寻找
break;
}
}
return new Method[]{getter, setter};
}
/**
* 针对Boolean或boolean类型字段查找其对应的Getter方法规则为
* <ul>
* <li>方法必须无参数且返回boolean或Boolean</li>
* <li>如果字段为isName, 匹配isNameisIsName方法两个方法均存在则按照提供的方法数组优先匹配</li>
* <li>如果字段为name, 匹配isName方法</li>
* </ul>
* <p>
* 需要注意的是以下两种格式不匹配{@link #findGetterAndSetter(String, Class, Method[], boolean)}完成
* <ul>
* <li>如果字段为name, 匹配getName</li>
* <li>如果字段为isName, 匹配getIsName</li>
* </ul>
*
* @param fieldName 字段名
* @param gettersOrSetters 所有方法
* @param ignoreCase 是否忽略大小写
* @return 查找到的方法{@code null}表示未找到
*/
private Method findGetterForBoolean(final String fieldName, final Method[] gettersOrSetters, final boolean ignoreCase) {
// 查找isXXX
return ArrayUtil.get(gettersOrSetters, m -> {
if (0 != m.getParameterCount() || false == BooleanUtil.isBoolean(m.getReturnType())) {
// getter方法要求无参数且返回boolean或Boolean
return false;
}
if (StrUtil.startWith(fieldName, "is", ignoreCase)) {
// isName - isName
if (StrUtil.equals(fieldName, m.getName(), ignoreCase)) {
return true;
}
}
// name - isName
// isName - isIsName
return StrUtil.equals(StrUtil.upperFirstAndAddPre(fieldName, "is"), m.getName(), ignoreCase);
});
}
/**
* 针对Boolean或boolean类型字段查找其对应的Setter方法规则为
* <ul>
* <li>方法必须为1个boolean或Boolean参数</li>
* <li>如果字段为isName匹配setName</li>
* </ul>
* <p>
* 需要注意的是以下两种格式不匹配{@link #findGetterAndSetter(String, Class, Method[], boolean)}完成
* <ul>
* <li>如果字段为name, 匹配setName</li>
* <li>如果字段为isName, 匹配setIsName</li>
* </ul>
*
* @param fieldName 字段名
* @param gettersOrSetters 所有方法
* @param ignoreCase 是否忽略大小写
* @return 查找到的方法{@code null}表示未找到
*/
private Method findSetterForBoolean(final String fieldName, final Method[] gettersOrSetters, final boolean ignoreCase) {
// 查找isXXX
return ArrayUtil.get(gettersOrSetters, m -> {
if (1 != m.getParameterCount() || false == BooleanUtil.isBoolean(m.getParameterTypes()[0])) {
// setter方法要求1个boolean或Boolean参数
return false;
}
if (StrUtil.startWith(fieldName, "is", ignoreCase)) {
// isName - setName
return StrUtil.equals(
"set" + StrUtil.removePrefix(fieldName, "is", ignoreCase),
m.getName(), ignoreCase);
}
// 其它不匹配
return false;
});
}
// ------------------------------------------------------------------------------------------------------ Private method end
PropDesc getProp(final String fieldName);
}

View File

@ -27,17 +27,17 @@ public enum BeanDescCache {
*/
INSTANCE;
private final WeakConcurrentMap<Class<?>, BeanDesc> bdCache = new WeakConcurrentMap<>();
private final WeakConcurrentMap<Class<?>, StrictBeanDesc> bdCache = new WeakConcurrentMap<>();
/**
* 获得属性名和{@link BeanDesc}Map映射
* 获得属性名和{@link StrictBeanDesc}Map映射
*
* @param beanClass Bean的类
* @param supplier 对象不存在时创建对象的函数
* @return 属性名和 {@link BeanDesc}映射
* @return 属性名和 {@link StrictBeanDesc}映射
* @since 5.4.2
*/
public BeanDesc getBeanDesc(final Class<?> beanClass, final SerSupplier<BeanDesc> supplier) {
public StrictBeanDesc getBeanDesc(final Class<?> beanClass, final SerSupplier<StrictBeanDesc> supplier) {
return bdCache.computeIfAbsent(beanClass, (key) -> supplier.get());
}

View File

@ -76,14 +76,14 @@ public class BeanUtil {
}
/**
* 获取{@link BeanDesc} Bean描述信息
* 获取{@link StrictBeanDesc} Bean描述信息
*
* @param clazz Bean类
* @return {@link BeanDesc}
* @return {@link StrictBeanDesc}
* @since 3.1.2
*/
public static BeanDesc getBeanDesc(final Class<?> clazz) {
return BeanDescCache.INSTANCE.getBeanDesc(clazz, () -> new BeanDesc(clazz));
public static StrictBeanDesc getBeanDesc(final Class<?> clazz) {
return BeanDescCache.INSTANCE.getBeanDesc(clazz, () -> new StrictBeanDesc(clazz));
}
/**

View File

@ -16,10 +16,7 @@ import org.dromara.hutool.core.annotation.AnnotationUtil;
import org.dromara.hutool.core.annotation.PropIgnore;
import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.func.LambdaUtil;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.reflect.ModifierUtil;
import org.dromara.hutool.core.reflect.ReflectUtil;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.reflect.*;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import java.beans.Transient;
@ -392,11 +389,11 @@ public class PropDesc {
* @since 5.3.11
*/
private boolean isTransientForGet() {
boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT);
boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierType.TRANSIENT);
// 检查Getter方法
if (!isTransient && null != this.getter) {
isTransient = ModifierUtil.hasModifier(this.getter, ModifierUtil.ModifierType.TRANSIENT);
isTransient = ModifierUtil.hasModifier(this.getter, ModifierType.TRANSIENT);
// 检查注解
if (!isTransient) {
@ -414,11 +411,11 @@ public class PropDesc {
* @since 5.3.11
*/
private boolean isTransientForSet() {
boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierUtil.ModifierType.TRANSIENT);
boolean isTransient = ModifierUtil.hasModifier(this.field, ModifierType.TRANSIENT);
// 检查Getter方法
if (!isTransient && null != this.setter) {
isTransient = ModifierUtil.hasModifier(this.setter, ModifierUtil.ModifierType.TRANSIENT);
isTransient = ModifierUtil.hasModifier(this.setter, ModifierType.TRANSIENT);
// 检查注解
if (!isTransient) {

View File

@ -0,0 +1,278 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.bean;
import org.dromara.hutool.core.bean.path.AbstractBeanDesc;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.reflect.ModifierUtil;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.BooleanUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 严格的Bean信息描述做为BeanInfo替代方案此对象持有JavaBean中的setters和getters等相关信息描述<br>
* 查找Getter和Setter方法时会
*
* <pre>
* 1. 忽略字段和方法名的大小写
* 2. Getter查找getXXXisXXXgetIsXXX
* 3. Setter查找setXXXsetIsXXX
* 4. Setter忽略参数值与字段值不匹配的情况因此有多个参数类型的重载时会调用首次匹配的
* </pre>
*
* @author looly
* @since 3.1.2
*/
public class StrictBeanDesc extends AbstractBeanDesc {
private static final long serialVersionUID = 1L;
/**
* 构造
*
* @param beanClass Bean类
*/
public StrictBeanDesc(final Class<?> beanClass) {
super(beanClass);
init();
}
// ------------------------------------------------------------------------------------------------------ Private method start
/**
* 初始化<br>
* 只有与属性关联的相关Getter和Setter方法才会被读取无关的getXXX和setXXX都被忽略
*/
private void init() {
if (RecordUtil.isRecord(getBeanClass())) {
initForRecord();
} else{
initForBean();
}
}
/**
* 针对Record类的反射初始化
*/
private void initForRecord() {
final Class<?> beanClass = this.beanClass;
final Map<String, PropDesc> propMap = this.propMap;
final Method[] getters = MethodUtil.getPublicMethods(beanClass, method -> 0 == method.getParameterCount());
// 排除静态属性和对象子类
final Field[] fields = FieldUtil.getFields(beanClass, field -> !ModifierUtil.isStatic(field) && !FieldUtil.isOuterClassField(field));
for (final Field field : fields) {
for (final Method getter : getters) {
if (field.getName().equals(getter.getName())) {
//record对象getter方法与字段同名
final PropDesc prop = new PropDesc(field, getter, null);
propMap.putIfAbsent(prop.getFieldName(), prop);
}
}
}
}
/**
* 普通Bean初始化
*/
private void initForBean() {
final Class<?> beanClass = this.beanClass;
final Map<String, PropDesc> propMap = this.propMap;
final Method[] gettersAndSetters = MethodUtil.getPublicMethods(beanClass, MethodUtil::isGetterOrSetterIgnoreCase);
// 排除静态属性和对象子类
final Field[] fields = FieldUtil.getFields(beanClass, field -> !ModifierUtil.isStatic(field) && !FieldUtil.isOuterClassField(field));
PropDesc prop;
for (final Field field : fields) {
prop = createProp(field, gettersAndSetters);
// 只有不存在时才放入防止父类属性覆盖子类属性
propMap.putIfAbsent(prop.getFieldName(), prop);
}
}
/**
* 根据字段创建属性描述<br>
* 查找Getter和Setter方法时会
*
* <pre>
* 1. 忽略字段和方法名的大小写
* 2. Getter查找getXXXisXXXgetIsXXX
* 3. Setter查找setXXXsetIsXXX
* 4. Setter忽略参数值与字段值不匹配的情况因此有多个参数类型的重载时会调用首次匹配的
* </pre>
*
* @param field 字段
* @param methods 类中所有的方法
* @return {@link PropDesc}
* @since 4.0.2
*/
private PropDesc createProp(final Field field, final Method[] methods) {
final PropDesc prop = findProp(field, methods, false);
// 忽略大小写重新匹配一次
if (null == prop.getter || null == prop.setter) {
final PropDesc propIgnoreCase = findProp(field, methods, true);
if (null == prop.getter) {
prop.getter = propIgnoreCase.getter;
}
if (null == prop.setter) {
prop.setter = propIgnoreCase.setter;
}
}
return prop;
}
/**
* 查找字段对应的Getter和Setter方法
*
* @param field 字段
* @param gettersOrSetters 类中所有的Getter或Setter方法
* @param ignoreCase 是否忽略大小写匹配
* @return PropDesc
*/
private PropDesc findProp(final Field field, final Method[] gettersOrSetters, final boolean ignoreCase) {
final String fieldName = field.getName();
final Class<?> fieldType = field.getType();
final boolean isBooleanField = BooleanUtil.isBoolean(fieldType);
// Getter: name -> getName, Setter: name -> setName
final Method[] getterAndSetter = findGetterAndSetter(fieldName, fieldType, gettersOrSetters, ignoreCase);
if (isBooleanField) {
if (null == getterAndSetter[0]) {
// isName -> isName or isIsName
// name -> isName
getterAndSetter[0] = getGetterForBoolean(gettersOrSetters, fieldName, ignoreCase);
}
if (null == getterAndSetter[1]) {
// isName -> setName
getterAndSetter[1] = getSetterForBoolean(gettersOrSetters, fieldName, ignoreCase);
}
}
return new PropDesc(field, getterAndSetter[0], getterAndSetter[1]);
}
/**
* 查找字段对应的Getter和Setter方法<br>
* 此方法不区分是否为boolean字段查找规则为
* <ul>
* <li>Getter要求无参数且返回值是字段类型或字段的父类</li>
* <li>Getter中如果字段为name匹配getName</li>
* <li>Setter要求一个参数且参数必须为字段类型或字段的子类</li>
* <li>Setter中如果字段为name匹配setName</li>
* </ul>
*
* @param fieldName 字段名
* @param fieldType 字段类型
* @param gettersOrSetters 类中所有的Getter或Setter方法
* @return PropDesc
*/
private Method[] findGetterAndSetter(final String fieldName, final Class<?> fieldType,
final Method[] gettersOrSetters, final boolean ignoreCase) {
Method getter = null;
Method setter = null;
String methodName;
for (final Method method : gettersOrSetters) {
methodName = method.getName();
if (0 == method.getParameterCount()) {
// 无参数可能为Getter方法
if (StrUtil.equals(methodName, StrUtil.genGetter(fieldName), ignoreCase) &&
method.getReturnType().isAssignableFrom(fieldType)) {
// getter的返回类型必须为字段类型或字段的父类
getter = method;
}
} else if (StrUtil.equals(methodName, StrUtil.genSetter(fieldName), ignoreCase) &&
fieldType.isAssignableFrom(method.getParameterTypes()[0])) {
// setter方法的参数必须为字段类型或字段的子类
setter = method;
}
if (null != getter && null != setter) {
// 如果Getter和Setter方法都找到了不再继续寻找
break;
}
}
return new Method[]{getter, setter};
}
/**
* 针对Boolean或boolean类型字段查找其对应的Getter方法规则为
* <ul>
* <li>方法必须无参数且返回boolean或Boolean</li>
* <li>如果字段为isName, 匹配isNameisIsName方法两个方法均存在则按照提供的方法数组优先匹配</li>
* <li>如果字段为name, 匹配isName方法</li>
* </ul>
*
* @param gettersOrSetters 所有方法
* @param fieldName 字段名
* @param ignoreCase 是否忽略大小写
* @return 查找到的方法{@code null}表示未找到
*/
private static Method getGetterForBoolean(final Method[] gettersOrSetters, final String fieldName, final boolean ignoreCase) {
// 查找isXXX
return MethodUtil.getMethod(gettersOrSetters, m -> {
if (0 != m.getParameterCount() || false == BooleanUtil.isBoolean(m.getReturnType())) {
// getter方法要求无参数且返回boolean或Boolean
return false;
}
if (StrUtil.startWith(fieldName, "is", ignoreCase)) {
// isName - isName
if (StrUtil.equals(fieldName, m.getName(), ignoreCase)) {
return true;
}
}
// name - isName
// isName - isIsName
return StrUtil.equals(StrUtil.upperFirstAndAddPre(fieldName, "is"), m.getName(), ignoreCase);
});
}
/**
* 针对Boolean或boolean类型字段查找其对应的Setter方法规则为
* <ul>
* <li>方法必须为1个boolean或Boolean参数</li>
* <li>如果字段为isName匹配setName</li>
* </ul>
*
* @param fieldName 字段名
* @param gettersOrSetters 所有方法
* @param ignoreCase 是否忽略大小写
* @return 查找到的方法{@code null}表示未找到
*/
private static Method getSetterForBoolean(final Method[] gettersOrSetters, final String fieldName, final boolean ignoreCase) {
// 查找isXXX
return MethodUtil.getMethod(gettersOrSetters, m -> {
if (1 != m.getParameterCount() || false == BooleanUtil.isBoolean(m.getParameterTypes()[0])) {
// setter方法要求1个boolean或Boolean参数
return false;
}
if (StrUtil.startWith(fieldName, "is", ignoreCase)) {
// isName - setName
return StrUtil.equals(
"set" + StrUtil.removePrefix(fieldName, "is", ignoreCase),
m.getName(), ignoreCase);
}
// 其它不匹配
return false;
});
}
// ------------------------------------------------------------------------------------------------------ Private method end
}

View File

@ -12,7 +12,7 @@
package org.dromara.hutool.core.bean.copier.provider;
import org.dromara.hutool.core.bean.BeanDesc;
import org.dromara.hutool.core.bean.StrictBeanDesc;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.bean.PropDesc;
import org.dromara.hutool.core.bean.copier.ValueProvider;
@ -28,7 +28,7 @@ import java.lang.reflect.Type;
public class BeanValueProvider implements ValueProvider<String> {
private final Object bean;
private final BeanDesc beanDesc;
private final StrictBeanDesc beanDesc;
/**
* 构造

View File

@ -0,0 +1,128 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.bean.path;
import org.dromara.hutool.core.bean.BeanDesc;
import org.dromara.hutool.core.bean.PropDesc;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.map.CaseInsensitiveMap;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Bean描述抽象类
*
* @author Looly
* @since 6.0.0
*/
public abstract class AbstractBeanDesc implements BeanDesc {
private static final long serialVersionUID = 1L;
/**
* Bean类
*/
protected final Class<?> beanClass;
/**
* 属性Map
*/
protected final Map<String, PropDesc> propMap = new LinkedHashMap<>();
/**
* 构造
*
* @param beanClass Bean类
*/
public AbstractBeanDesc(final Class<?> beanClass) {
this.beanClass = Assert.notNull(beanClass);
}
/**
* 获取Bean的全类名
*
* @return Bean的类名
*/
public String getName() {
return this.beanClass.getName();
}
/**
* 获取Bean的简单类名
*
* @return Bean的类名
*/
public String getSimpleName() {
return this.beanClass.getSimpleName();
}
/**
* 获取Bean类
*
* @return Bean类
*/
public Class<?> getBeanClass() {
return this.beanClass;
}
@Override
public Map<String, PropDesc> getPropMap(final boolean ignoreCase) {
return ignoreCase ? new CaseInsensitiveMap<>(1, this.propMap) : this.propMap;
}
@Override
public Collection<PropDesc> getProps() {
return this.propMap.values();
}
@Override
public PropDesc getProp(final String fieldName) {
return this.propMap.get(fieldName);
}
/**
* 获得字段名对应的字段对象如果不存在返回null
*
* @param fieldName 字段名
* @return 字段值
*/
public Field getField(final String fieldName) {
final PropDesc desc = this.propMap.get(fieldName);
return null == desc ? null : desc.getField();
}
/**
* 获取Getter方法如果不存在返回null
*
* @param fieldName 字段名
* @return Getter方法
*/
public Method getGetter(final String fieldName) {
final PropDesc desc = this.propMap.get(fieldName);
return null == desc ? null : desc.getGetter();
}
/**
* 获取Setter方法如果不存在返回null
*
* @param fieldName 字段名
* @return Setter方法
*/
public Method getSetter(final String fieldName) {
final PropDesc desc = this.propMap.get(fieldName);
return null == desc ? null : desc.getSetter();
}
}

View File

@ -15,6 +15,7 @@ package org.dromara.hutool.core.func;
import org.dromara.hutool.core.stream.StreamUtil;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* 一些{@link Predicate}相关封装
@ -103,4 +104,16 @@ public class PredicateUtil {
public static <T> Predicate<T> or(final Predicate<T>... components) {
return StreamUtil.of(components).reduce(Predicate::or).orElseGet(() -> o -> false);
}
/**
* 用于组合多个方法匹配器的方法匹配器即所有条件都为false时才返回true也可理解为任一条件为true时返回false
*
* @param <T> 判断条件的对象类型
* @param components 多个条件
* @return 复合条件
*/
@SafeVarargs
public static <T> Predicate<T> none(final Predicate<T>... components){
return t -> Stream.of(components).noneMatch(matcher -> matcher.test(t));
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.reflect;
import java.lang.reflect.Modifier;
/**
* 修饰符枚举
*
* @author looly
* @since 4.0.5
*/
public enum ModifierType {
/**
* public修饰符所有类都能访问
*/
PUBLIC(Modifier.PUBLIC),
/**
* private修饰符只能被自己访问和修改
*/
PRIVATE(Modifier.PRIVATE),
/**
* protected修饰符自身子类及同一个包中类可以访问
*/
PROTECTED(Modifier.PROTECTED),
/**
* static修饰符静态修饰符指定变量被所有对象共享即所有实例都可以使用该变量变量属于这个类
*/
STATIC(Modifier.STATIC),
/**
* final修饰符最终修饰符指定此变量的值不能变使用在方法上表示不能被重载
*/
FINAL(Modifier.FINAL),
/**
* synchronized同步修饰符在多个线程中该修饰符用于在运行前对他所属的方法加锁以防止其他线程的访问运行结束后解锁
*/
SYNCHRONIZED(Modifier.SYNCHRONIZED),
/**
* 易失修饰符指定该变量可以同时被几个线程控制和修改
*/
VOLATILE(Modifier.VOLATILE),
/**
* 过度修饰符指定该变量是系统保留暂无特别作用的临时性变量序列化时忽略
*/
TRANSIENT(Modifier.TRANSIENT),
/**
* native本地修饰符指定此方法的方法体是用其他语言在程序外部编写的
*/
NATIVE(Modifier.NATIVE),
/**
* abstract将一个类声明为抽象类没有实现的方法需要子类提供方法实现
*/
ABSTRACT(Modifier.ABSTRACT),
/**
* strictfp一旦使用了关键字strictfp来声明某个类接口或者方法时那么在这个关键字所声明的范围内所有浮点运算都是精确的符合IEEE-754规范的
*/
STRICT(Modifier.STRICT);
/**
* 修饰符枚举对应的int修饰符值
*/
private final int value;
/**
* 构造
*
* @param modifier 修饰符int表示{@link Modifier}
*/
ModifierType(final int modifier) {
this.value = modifier;
}
/**
* 获取修饰符枚举对应的int修饰符值值见{@link Modifier}
*
* @return 修饰符枚举对应的int修饰符值
*/
public int getValue() {
return this.value;
}
/**
* 多个修饰符做操作表示存在任意一个修饰符
*
* @param modifierTypes 修饰符列表元素不能为空
* @return 之后的修饰符
*/
public static int orToInt(final ModifierType... modifierTypes) {
int modifier = modifierTypes[0].getValue();
for (int i = 1; i < modifierTypes.length; i++) {
modifier |= modifierTypes[i].getValue();
}
return modifier;
}
/**
* 多个修饰符做操作表示存在任意一个修饰符
*
* @param modifierTypes 修饰符列表元素不能为空
* @return 之后的修饰符
*/
public static int orToInt(final int... modifierTypes) {
int modifier = modifierTypes[0];
for (int i = 1; i < modifierTypes.length; i++) {
modifier |= modifierTypes[i];
}
return modifier;
}
}

View File

@ -28,83 +28,6 @@ import java.lang.reflect.Modifier;
*/
public class ModifierUtil {
/**
* 修饰符枚举
*
* @author looly
* @since 4.0.5
*/
public enum ModifierType {
/**
* public修饰符所有类都能访问
*/
PUBLIC(Modifier.PUBLIC),
/**
* private修饰符只能被自己访问和修改
*/
PRIVATE(Modifier.PRIVATE),
/**
* protected修饰符自身子类及同一个包中类可以访问
*/
PROTECTED(Modifier.PROTECTED),
/**
* static修饰符静态修饰符指定变量被所有对象共享即所有实例都可以使用该变量变量属于这个类
*/
STATIC(Modifier.STATIC),
/**
* final修饰符最终修饰符指定此变量的值不能变使用在方法上表示不能被重载
*/
FINAL(Modifier.FINAL),
/**
* synchronized同步修饰符在多个线程中该修饰符用于在运行前对他所属的方法加锁以防止其他线程的访问运行结束后解锁
*/
SYNCHRONIZED(Modifier.SYNCHRONIZED),
/**
* 易失修饰符指定该变量可以同时被几个线程控制和修改
*/
VOLATILE(Modifier.VOLATILE),
/**
* 过度修饰符指定该变量是系统保留暂无特别作用的临时性变量序列化时忽略
*/
TRANSIENT(Modifier.TRANSIENT),
/**
* native本地修饰符指定此方法的方法体是用其他语言在程序外部编写的
*/
NATIVE(Modifier.NATIVE),
/**
* abstract将一个类声明为抽象类没有实现的方法需要子类提供方法实现
*/
ABSTRACT(Modifier.ABSTRACT),
/**
* strictfp一旦使用了关键字strictfp来声明某个类接口或者方法时那么在这个关键字所声明的范围内所有浮点运算都是精确的符合IEEE-754规范的
*/
STRICT(Modifier.STRICT);
/**
* 修饰符枚举对应的int修饰符值
*/
private final int value;
/**
* 构造
*
* @param modifier 修饰符int表示{@link Modifier}
*/
ModifierType(final int modifier) {
this.value = modifier;
}
/**
* 获取修饰符枚举对应的int修饰符值值见{@link Modifier}
*
* @return 修饰符枚举对应的int修饰符值
*/
public int getValue() {
return this.value;
}
}
/**
* 是否同时存在一个或多个修饰符可能有多个修饰符如果有指定的修饰符则返回true
*
@ -116,7 +39,7 @@ public class ModifierUtil {
if (null == clazz || ArrayUtil.isEmpty(modifierTypes)) {
return false;
}
return 0 != (clazz.getModifiers() & modifiersToInt(modifierTypes));
return 0 != (clazz.getModifiers() & ModifierType.orToInt(modifierTypes));
}
/**
@ -130,7 +53,34 @@ public class ModifierUtil {
if (null == member || ArrayUtil.isEmpty(modifierTypes)) {
return false;
}
return 0 != (member.getModifiers() & modifiersToInt(modifierTypes));
return 0 != (member.getModifiers() & ModifierType.orToInt(modifierTypes));
}
/**
* 是否同时存在一个或多个修饰符可能有多个修饰符如果有指定的修饰符则返回true
*
* @param modifiers 构造字段或方法的修饰符
* @param checkedModifiers 需要检查的修饰符如果为空返回{@code false}
* @return 是否有指定修饰符如果有返回true否则false如果提供参数为null返回false
*/
public static boolean hasModifier(final int modifiers, final int... checkedModifiers) {
return 0 != (modifiers & ModifierType.orToInt(checkedModifiers));
}
/**
* 是否同时存在一个或多个修饰符可能有多个修饰符如果有指定的修饰符则返回true
*
* @param modifiers 构造字段或方法的修饰符
* @param checkedModifiers 需要检查的修饰符如果为空返回{@code false}
* @return 是否有指定修饰符如果有返回true否则false如果提供参数为null返回false
*/
public static boolean hasAllModifier(final int modifiers, final int... checkedModifiers) {
for (final int checkedModifier : checkedModifiers) {
if (0 == (modifiers & checkedModifier)) {
return false;
}
}
return true;
}
/**
@ -318,20 +268,4 @@ public class ModifierUtil {
throw new HutoolException(e, "IllegalAccess for [{}.{}]", field.getDeclaringClass(), field.getName());
}
}
//-------------------------------------------------------------------------------------------------------- Private method start
/**
* 多个修饰符做操作表示同时存在多个修饰符
*
* @param modifierTypes 修饰符列表元素不能为空
* @return 之后的修饰符
*/
private static int modifiersToInt(final ModifierType... modifierTypes) {
int modifier = modifierTypes[0].getValue();
for (int i = 1; i < modifierTypes.length; i++) {
modifier |= modifierTypes[i].getValue();
}
return modifier;
}
//-------------------------------------------------------------------------------------------------------- Private method end
}

View File

@ -14,7 +14,9 @@ package org.dromara.hutool.core.reflect.method;
import org.dromara.hutool.core.annotation.AnnotatedElementUtil;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.func.PredicateUtil;
import org.dromara.hutool.core.reflect.ClassUtil;
import org.dromara.hutool.core.reflect.ModifierUtil;
import org.dromara.hutool.core.text.CharSequenceUtil;
import org.jetbrains.annotations.NotNull;
@ -22,7 +24,6 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
@ -45,7 +46,7 @@ public class MethodMatcherUtil {
*/
@SafeVarargs
public static Predicate<Method> noneMatch(final Predicate<Method>... matchers) {
return method -> Stream.of(matchers).noneMatch(matcher -> matcher.test(method));
return PredicateUtil.none(matchers);
}
/**
@ -57,7 +58,7 @@ public class MethodMatcherUtil {
*/
@SafeVarargs
public static Predicate<Method> anyMatch(final Predicate<Method>... matchers) {
return method -> Stream.of(matchers).anyMatch(matcher -> matcher.test(method));
return PredicateUtil.or(matchers);
}
/**
@ -69,7 +70,7 @@ public class MethodMatcherUtil {
*/
@SafeVarargs
public static Predicate<Method> allMatch(final Predicate<Method>... matchers) {
return method -> Stream.of(matchers).allMatch(matcher -> matcher.test(method));
return PredicateUtil.and(matchers);
}
// region ============= 访问修饰符 =============
@ -108,10 +109,7 @@ public class MethodMatcherUtil {
* @return 方法匹配器
*/
public static Predicate<Method> forModifiers(final int... modifiers) {
return method -> {
final int methodModifiers = method.getModifiers();
return Arrays.stream(modifiers).allMatch(modifier -> (methodModifiers & modifier) != 0);
};
return method -> ModifierUtil.hasAllModifier(method.getModifiers(), modifiers);
}
// endregion

View File

@ -54,6 +54,18 @@ public class MethodUtil {
// region ----- getMethods
/**
* 通过给定的条件Predicate从一个Method数组中查找第一个匹配的方法
*
* @param methods Method数组是被搜索的目标对象
* @param predicate 一个Predicate接口实例用于定义查找方法的条件
* @return 返回第一个满足predicate条件的Method对象如果没有找到匹配的方法则返回null
*/
public static Method getMethod(final Method[] methods, final Predicate<Method> predicate) {
// 使用ArrayUtil的get方法通过predicate对methods数组进行搜索
return ArrayUtil.get(methods, predicate);
}
/**
* 获得指定类本类及其父类中的Public方法名<br>
* 去重重载的方法
@ -81,15 +93,7 @@ public class MethodUtil {
if (null == clazz || StrUtil.isBlank(methodName)) {
return null;
}
final Method[] methods = getPublicMethods(clazz, method ->
StrUtil.equals(methodName, method.getName(), ignoreCase)
&& ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes)
//排除桥接方法pr#1965@Github
//排除协变桥接方法pr#1965@Github
&& (method.getReturnType().isAssignableFrom(method.getReturnType())));
return ArrayUtil.isEmpty(methods) ? null : methods[0];
return getMethod(getPublicMethods(clazz), ignoreCase, methodName, paramTypes);
}
/**
@ -164,15 +168,40 @@ public class MethodUtil {
if (null == clazz || StrUtil.isBlank(methodName)) {
return null;
}
return getMethod(getMethods(clazz), ignoreCase, methodName, paramTypes);
}
final Method[] methods = getMethods(clazz, method ->
StrUtil.equals(methodName, method.getName(), ignoreCase)
&& ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes)
//排除桥接方法pr#1965@Github
//排除协变桥接方法pr#1965@Github
&& (method.getReturnType().isAssignableFrom(method.getReturnType())));
/**
* 查找指定方法 如果找不到对应的方法则返回{@code null}<br>
* 此方法为精准获取方法名即方法名和参数数量和类型必须一致否则返回{@code null}<br>
* 如果查找的方法有多个同参数类型重载查找最后一个非协变桥接方法
*
* @param methods 方法列表
* @param ignoreCase 是否忽略大小写
* @param methodName 方法名如果为空字符串返回{@code null}
* @param paramTypes 参数类型指定参数类型如果是方法的子类也算
* @return 方法
* @throws SecurityException 无权访问抛出异常
* @since 3.2.0
*/
public static Method getMethod(final Method[] methods, final boolean ignoreCase, final String methodName, final Class<?>... paramTypes) throws SecurityException {
if (ArrayUtil.isEmpty(methods) || StrUtil.isBlank(methodName)) {
return null;
}
return ArrayUtil.isEmpty(methods) ? null : methods[0];
Method res = null;
if (ArrayUtil.isNotEmpty(methods)) {
for (final Method method : methods) {
if (StrUtil.equals(methodName, method.getName(), ignoreCase)
&& ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes)
//排除协变桥接方法pr#1965@Github
&& (res == null
|| res.getReturnType().isAssignableFrom(method.getReturnType()))) {
res = method;
}
}
}
return res;
}
/**
@ -724,5 +753,4 @@ public class MethodUtil {
return actualArgs;
}
}

View File

@ -13,7 +13,7 @@
package org.dromara.hutool.core.text.placeholder.template;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.BeanDesc;
import org.dromara.hutool.core.bean.StrictBeanDesc;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
@ -344,7 +344,7 @@ public class NamedPlaceholderStrTemplate extends StrTemplate {
if (beanOrMap instanceof Map) {
return format((Map<String, ?>) beanOrMap);
} else if (BeanUtil.isReadableBean(beanOrMap.getClass())) {
final BeanDesc beanDesc = BeanUtil.getBeanDesc(beanOrMap.getClass());
final StrictBeanDesc beanDesc = BeanUtil.getBeanDesc(beanOrMap.getClass());
return format(fieldName -> {
final Method getterMethod = beanDesc.getGetter(fieldName);
if (getterMethod == null) {
@ -553,7 +553,7 @@ public class NamedPlaceholderStrTemplate extends StrTemplate {
@SuppressWarnings("unchecked") final Map<String, String> map = (Map<String, String>) obj;
matchesByKey(str, map::put);
} else if (BeanUtil.isReadableBean(obj.getClass())) {
final BeanDesc beanDesc = BeanUtil.getBeanDesc(obj.getClass());
final StrictBeanDesc beanDesc = BeanUtil.getBeanDesc(obj.getClass());
matchesByKey(str, (key, value) -> {
final Field field = beanDesc.getField(key);
final Method setterMethod = beanDesc.getSetter(key);

View File

@ -16,7 +16,7 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* {@link BeanDesc} 单元测试类
* {@link StrictBeanDesc} 单元测试类
*
* @author looly
*
@ -25,7 +25,7 @@ public class BeanDescTest {
@Test
public void propDescTes() {
final BeanDesc desc = BeanUtil.getBeanDesc(User.class);
final StrictBeanDesc desc = BeanUtil.getBeanDesc(User.class);
Assertions.assertEquals("User", desc.getSimpleName());
Assertions.assertEquals("age", desc.getField("age").getName());
@ -38,7 +38,7 @@ public class BeanDescTest {
@Test
public void propDescTes2() {
final BeanDesc desc = BeanUtil.getBeanDesc(User.class);
final StrictBeanDesc desc = BeanUtil.getBeanDesc(User.class);
final PropDesc prop = desc.getProp("name");
Assertions.assertEquals("name", prop.getFieldName());
@ -50,7 +50,7 @@ public class BeanDescTest {
@Test
public void propDescOfBooleanTest() {
final BeanDesc desc = BeanUtil.getBeanDesc(User.class);
final StrictBeanDesc desc = BeanUtil.getBeanDesc(User.class);
Assertions.assertEquals("isAdmin", desc.getGetter("isAdmin").getName());
Assertions.assertEquals("setAdmin", desc.getSetter("isAdmin").getName());
@ -60,7 +60,7 @@ public class BeanDescTest {
@Test
public void propDescOfBooleanTest2() {
final BeanDesc desc = BeanUtil.getBeanDesc(User.class);
final StrictBeanDesc desc = BeanUtil.getBeanDesc(User.class);
Assertions.assertEquals("isIsSuper", desc.getGetter("isSuper").getName());
Assertions.assertEquals("setIsSuper", desc.getSetter("isSuper").getName());
@ -68,7 +68,7 @@ public class BeanDescTest {
@Test
public void propDescOfBooleanTest3() {
final BeanDesc desc = BeanUtil.getBeanDesc(User.class);
final StrictBeanDesc desc = BeanUtil.getBeanDesc(User.class);
Assertions.assertEquals("setLastPage", desc.getSetter("lastPage").getName());
Assertions.assertEquals("setIsLastPage", desc.getSetter("isLastPage").getName());
@ -76,7 +76,7 @@ public class BeanDescTest {
@Test
public void getSetTest() {
final BeanDesc desc = BeanUtil.getBeanDesc(User.class);
final StrictBeanDesc desc = BeanUtil.getBeanDesc(User.class);
final User user = new User();
desc.getProp("name").setValue(user, "张三");

View File

@ -20,7 +20,7 @@ public class BeanWithReturnThisTest {
@Test
public void setValueTest() {
final BeanWithRetuenThis bean = new BeanWithRetuenThis();
final BeanDesc beanDesc = BeanUtil.getBeanDesc(BeanWithRetuenThis.class);
final StrictBeanDesc beanDesc = BeanUtil.getBeanDesc(BeanWithRetuenThis.class);
final PropDesc prop = beanDesc.getProp("a");
prop.setValue(bean, "123");

View File

@ -19,7 +19,7 @@ public class Issue3096Test {
@Test
void beanDescTest() {
final BeanDesc desc = BeanUtil.getBeanDesc(User.class);
final StrictBeanDesc desc = BeanUtil.getBeanDesc(User.class);
// https://github.com/dromara/hutool/issues/3096
// 新修改的规则中isLastPage字段优先匹配setIsLastPage这个顺序固定

View File

@ -13,6 +13,7 @@
package org.dromara.hutool.core.util;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.reflect.ModifierType;
import org.dromara.hutool.core.reflect.ModifierUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@ -28,10 +29,10 @@ public class ModifierUtilTest {
@Test
public void hasModifierTest() throws NoSuchMethodException {
final Method method = ModifierUtilTest.class.getDeclaredMethod("ddd");
Assertions.assertTrue(ModifierUtil.hasModifier(method, ModifierUtil.ModifierType.PRIVATE));
Assertions.assertTrue(ModifierUtil.hasModifier(method, ModifierType.PRIVATE));
Assertions.assertTrue(ModifierUtil.hasModifier(method,
ModifierUtil.ModifierType.PRIVATE,
ModifierUtil.ModifierType.STATIC)
ModifierType.PRIVATE,
ModifierType.STATIC)
);
}