This commit is contained in:
Looly 2022-01-08 13:36:57 +08:00
parent c075b30858
commit f914faa0de
6 changed files with 129 additions and 41 deletions

View File

@ -3,10 +3,11 @@
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.7.20 (2022-01-07) # 5.7.20 (2022-01-08)
### 🐣新特性 ### 🐣新特性
### 🐞Bug修复 ### 🐞Bug修复
* 【core 】 修复setter重载导致匹配错误issue#2082@Github
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.7.19 (2022-01-07) # 5.7.19 (2022-01-07)

View File

@ -140,14 +140,12 @@ public class BeanDesc implements Serializable {
* @return this * @return this
*/ */
private BeanDesc init() { private BeanDesc init() {
final Method[] methods = ReflectUtil.getMethods(this.beanClass); final Method[] gettersAndSetters = ReflectUtil.getMethods(this.beanClass, ReflectUtil::isGetterOrSetterIgnoreCase);
PropDesc prop; PropDesc prop;
for (Field field : ReflectUtil.getFields(this.beanClass)) { for (Field field : ReflectUtil.getFields(this.beanClass)) {
if (false == ModifierUtil.isStatic(field) && // 排除静态属性和对象子类
// 排除对象子类 if (false == ModifierUtil.isStatic(field) && false == ReflectUtil.isOuterClassField(field)) {
false == "this$0".equals(field.getName())) { prop = createProp(field, gettersAndSetters);
//只针对非static属性
prop = createProp(field, methods);
// 只有不存在时才放入防止父类属性覆盖子类属性 // 只有不存在时才放入防止父类属性覆盖子类属性
this.propMap.putIfAbsent(prop.getFieldName(), prop); this.propMap.putIfAbsent(prop.getFieldName(), prop);
} }
@ -190,12 +188,12 @@ public class BeanDesc implements Serializable {
/** /**
* 查找字段对应的Getter和Setter方法 * 查找字段对应的Getter和Setter方法
* *
* @param field 字段 * @param field 字段
* @param methods 类中所有的方法 * @param gettersOrSetters 类中所有的Getter或Setter方法
* @param ignoreCase 是否忽略大小写匹配 * @param ignoreCase 是否忽略大小写匹配
* @return PropDesc * @return PropDesc
*/ */
private PropDesc findProp(Field field, Method[] methods, boolean ignoreCase) { private PropDesc findProp(Field field, Method[] gettersOrSetters, boolean ignoreCase) {
final String fieldName = field.getName(); final String fieldName = field.getName();
final Class<?> fieldType = field.getType(); final Class<?> fieldType = field.getType();
final boolean isBooleanField = BooleanUtil.isBoolean(fieldType); final boolean isBooleanField = BooleanUtil.isBoolean(fieldType);
@ -203,24 +201,19 @@ public class BeanDesc implements Serializable {
Method getter = null; Method getter = null;
Method setter = null; Method setter = null;
String methodName; String methodName;
Class<?>[] parameterTypes; for (Method method : gettersOrSetters) {
for (Method method : methods) {
parameterTypes = method.getParameterTypes();
if (parameterTypes.length > 1) {
// 多于1个参数说明非Getter或Setter
continue;
}
methodName = method.getName(); methodName = method.getName();
if (parameterTypes.length == 0) { if (method.getParameterCount() == 0) {
// 无参数可能为Getter方法 // 无参数可能为Getter方法
if (isMatchGetter(methodName, fieldName, isBooleanField, ignoreCase)) { if (isMatchGetter(methodName, fieldName, isBooleanField, ignoreCase)) {
// 方法名与字段名匹配则为Getter方法 // 方法名与字段名匹配则为Getter方法
getter = method; getter = method;
} }
} else if (isMatchSetter(methodName, fieldName, isBooleanField, ignoreCase)) { } else if (isMatchSetter(methodName, fieldName, isBooleanField, ignoreCase)) {
// 只有一个参数的情况下方法名与字段名对应匹配则为Setter方法 // setter方法的参数类型和字段类型必须一致或参数类型是字段类型的子类
setter = method; if(fieldType.isAssignableFrom(method.getParameterTypes()[0])){
setter = method;
}
} }
if (null != getter && null != setter) { if (null != getter && null != setter) {
// 如果Getter和Setter方法都找到了不再继续寻找 // 如果Getter和Setter方法都找到了不再继续寻找
@ -261,15 +254,6 @@ public class BeanDesc implements Serializable {
handledFieldName = StrUtil.upperFirst(fieldName); handledFieldName = StrUtil.upperFirst(fieldName);
} }
if (false == methodName.startsWith("get") && false == methodName.startsWith("is")) {
// 非标准Getter方法
return false;
}
if ("getclass".equals(methodName)) {
//跳过getClass方法
return false;
}
// 针对Boolean类型特殊检查 // 针对Boolean类型特殊检查
if (isBooleanField) { if (isBooleanField) {
if (fieldName.startsWith("is")) { if (fieldName.startsWith("is")) {

View File

@ -88,9 +88,8 @@ public class BeanUtil {
*/ */
public static boolean hasSetter(Class<?> clazz) { public static boolean hasSetter(Class<?> clazz) {
if (ClassUtil.isNormalClass(clazz)) { if (ClassUtil.isNormalClass(clazz)) {
final Method[] methods = clazz.getMethods(); for (Method method : clazz.getMethods()) {
for (Method method : methods) { if (method.getParameterCount() == 1 && method.getName().startsWith("set")) {
if (method.getParameterTypes().length == 1 && method.getName().startsWith("set")) {
// 检测包含标准的setXXX方法即视为标准的JavaBean // 检测包含标准的setXXX方法即视为标准的JavaBean
return true; return true;
} }
@ -110,7 +109,7 @@ public class BeanUtil {
public static boolean hasGetter(Class<?> clazz) { public static boolean hasGetter(Class<?> clazz) {
if (ClassUtil.isNormalClass(clazz)) { if (ClassUtil.isNormalClass(clazz)) {
for (Method method : clazz.getMethods()) { for (Method method : clazz.getMethods()) {
if (method.getParameterTypes().length == 0) { if (method.getParameterCount() == 0) {
if (method.getName().startsWith("get") || method.getName().startsWith("is")) { if (method.getName().startsWith("get") || method.getName().startsWith("is")) {
return true; return true;
} }

View File

@ -12,6 +12,7 @@ import java.lang.reflect.Method;
/** /**
* 方法句柄{@link MethodHandle}封装工具类<br> * 方法句柄{@link MethodHandle}封装工具类<br>
* 方法句柄是一个有类型的可以直接执行的指向底层方法构造器field等的引用可以简单理解为函数指针它是一种更加底层的查找调整和调用方法的机制
* 参考 * 参考
* <ul> * <ul>
* <li>https://stackoverflow.com/questions/22614746/how-do-i-invoke-java-8-default-methods-reflectively</li> * <li>https://stackoverflow.com/questions/22614746/how-do-i-invoke-java-8-default-methods-reflectively</li>
@ -113,7 +114,7 @@ public class MethodHandleUtil {
} }
/** /**
* 执行接口或对象中的方法<br> * 执行接口或对象中的特殊方法privatestatic等<br>
* *
* <pre class="code"> * <pre class="code">
* interface Duck { * interface Duck {
@ -159,7 +160,7 @@ public class MethodHandleUtil {
} }
/** /**
* 执行接口或对象中的方法<br> * 执行接口或对象中的特殊方法privatestatic等<br>
* *
* <pre class="code"> * <pre class="code">
* interface Duck { * interface Duck {

View File

@ -344,6 +344,17 @@ public class ReflectUtil {
} }
} }
/**
* 是否为父类引用字段<br>
* 当字段所在类是对象子类时对象中定义的非static的class会自动生成一个以"this$0"为名称的字段指向父类对象
* @param field 字段
* @return 是否为父类引用字段
* @since 5.7.20
*/
public static boolean isOuterClassField(Field field){
return "this$0".equals(field.getName());
}
// --------------------------------------------------------------------------------------------------------- method // --------------------------------------------------------------------------------------------------------- method
/** /**
@ -673,11 +684,12 @@ public class ReflectUtil {
* @return 是否为equals方法 * @return 是否为equals方法
*/ */
public static boolean isEqualsMethod(Method method) { public static boolean isEqualsMethod(Method method) {
if (method == null || false == "equals".equals(method.getName())) { if (method == null ||
1 != method.getParameterCount() ||
false == "equals".equals(method.getName())) {
return false; return false;
} }
final Class<?>[] paramTypes = method.getParameterTypes(); return (method.getParameterTypes()[0] == Object.class);
return (1 == paramTypes.length && paramTypes[0] == Object.class);
} }
/** /**
@ -712,9 +724,67 @@ public class ReflectUtil {
* @since 5.1.1 * @since 5.1.1
*/ */
public static boolean isEmptyParam(Method method) { public static boolean isEmptyParam(Method method) {
return method.getParameterTypes().length == 0; return method.getParameterCount() == 0;
} }
/**
* 检查给定方法是否为Getter或者Setter方法规则为<br>
* <ul>
* <li>方法参数必须为0个或1个</li>
* <li>如果是无参方法则判断是否以getis开头</li>
* <li>如果方法参数1个则判断是否以set开头</li>
* </ul>
*
* @param method 方法
* @return 是否为Getter或者Setter方法
* @since 5.7.20
*/
public static boolean isGetterOrSetterIgnoreCase(Method method) {
return isGetterOrSetter(method, true);
}
/**
* 检查给定方法是否为Getter或者Setter方法规则为<br>
* <ul>
* <li>方法参数必须为0个或1个</li>
* <li>方法名称不能是getClass</li>
* <li>如果是无参方法则判断是否以getis开头</li>
* <li>如果方法参数1个则判断是否以set开头</li>
* </ul>
*
* @param method 方法
* @param ignoreCase 是否忽略方法名的大小写
* @return 是否为Getter或者Setter方法
* @since 5.7.20
*/
public static boolean isGetterOrSetter(Method method, boolean ignoreCase) {
if (null == method) {
return false;
}
// 参数个数必须为0或1
final int parameterCount = method.getParameterCount();
if (parameterCount > 1) {
return false;
}
String name = method.getName();
// 跳过getClass这个特殊方法
if("getClass".equals(name)){
return false;
}
if(ignoreCase){
name = name.toLowerCase();
}
switch (parameterCount) {
case 0:
return name.startsWith("get") || name.startsWith("is");
case 1:
return name.startsWith("set");
default:
return false;
}
}
// --------------------------------------------------------------------------------------------------------- newInstance // --------------------------------------------------------------------------------------------------------- newInstance
/** /**

View File

@ -0,0 +1,33 @@
package cn.hutool.core.bean;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
/**
* https://github.com/dromara/hutool/issues/2082<br>
* 当setXXX有重载方法的时候BeanDesc中会匹配到重载方法增加类型检查来规避之
*/
public class Issue2082Test {
@Test
public void toBeanTest() {
TestBean2 testBean2 = new TestBean2();
TestBean test = BeanUtil.toBean(testBean2, TestBean.class);
Assert.assertNull(test.getId());
}
@Data
static class TestBean {
private Long id;
public void setId(String id) {
this.id = Long.valueOf(id);
}
}
@Data
static class TestBean2 {
private String id;
}
}