!810 反射新增:setFieldModify(field)方法,设置final字段可以被修改,并设置到:setFieldValue中

Merge pull request !810 from dazer007/v5-dev-ReflectUtil-add-setFieldModify
This commit is contained in:
Looly 2022-09-21 10:33:26 +00:00 committed by Gitee
commit 0941210553
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
2 changed files with 80 additions and 1 deletions

View File

@ -9,6 +9,7 @@ import cn.hutool.core.exceptions.InvocationTargetRuntimeException;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Filter;
import java.lang.reflect.Modifier;
import cn.hutool.core.lang.reflect.MethodHandleUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.WeakConcurrentMap;
@ -303,6 +304,7 @@ public class ReflectUtil {
* 设置字段值<br>
* 若值类型与字段类型不一致则会尝试通过 {@link Convert} 进行转换<br>
* 若字段类型是原始类型而传入的值是 null则会将字段设置为对应原始类型的默认值 {@link ClassUtil#getDefaultValue(Class)}
* 如果是final字段setFieldValue调用这可以先调用 {@link ReflectUtil#removeFinalModify(Field)}方法去除final修饰符<br>
*
* @param obj 对象,static字段则此处传Class
* @param fieldName 字段名
@ -321,7 +323,8 @@ public class ReflectUtil {
/**
* 设置字段值<br>
* 若值类型与字段类型不一致则会尝试通过 {@link Convert} 进行转换<br>
* 若字段类型是原始类型而传入的值是 null则会将字段设置为对应原始类型的默认值 {@link ClassUtil#getDefaultValue(Class)}
* 若字段类型是原始类型而传入的值是 null则会将字段设置为对应原始类型的默认值 {@link ClassUtil#getDefaultValue(Class)}<br>
* 如果是final字段setFieldValue调用这可以先调用 {@link ReflectUtil#removeFinalModify(Field)}方法去除final修饰符
*
* @param obj 对象如果是static字段此参数为null
* @param field 字段
@ -1108,6 +1111,58 @@ public class ReflectUtil {
return accessibleObject;
}
/**
* 设置final的field字段可以被修改
* <p>
* 只要不会被编译器内联优化的 final 属性就可以通过反射有效的进行修改 -- 修改后代码中可使用到新的值;
* <br/>
* <h3>以下属性编译器会内联优化无法通过反射修改</h3>
* <ul>
* <li> 基本类型 byte, char, short, int, long, float, double, boolean</li>
* <li> Literal String 类型(直接双引号字符串)</li>
* </ul>
* <h3>以下属性可以通过反射修改</h3>
* <ul>
* <li>基本类型的包装类 ByteCharacterShortLongFloatDoubleBoolean</li>
* <li>字符串通过 new String("")实例化</li>
* <li>自定义java类</li>
* </ul>
* </p>
* <code>
* //示例移除final修饰符
* class JdbcDialects {private static final List<Number> dialects = new ArrayList<>();}
* Field field = ReflectUtil.getField(JdbcDialects.class, fieldName);
* ReflectUtil.removeFinalModify(field);
* ReflectUtil.setFieldValue(JdbcDialects.class, fieldName, dialects);
* </code>
* @param field 被修改的field不可以为空
* @throws UtilException IllegalAccessException等异常包装
* @since 5.8.8
* @author dazer
*/
public static void removeFinalModify(Field field) {
if (field != null) {
if (ModifierUtil.hasModifier(field, ModifierUtil.ModifierType.FINAL)) {
//将字段的访问权限设为true即去除private修饰符的影响
if (!field.isAccessible()) {
field.setAccessible(true);
}
try {
//去除final修饰符的影响将字段设为可修改的
Field modifiersField = Field.class.getDeclaredField("modifiers");
//Field modifiers 是私有的
modifiersField.setAccessible(true);
//& 位与运算符按位与 运算规则两个数都转为二进制然后从高位开始比较如果两个数都为1则为1否则为0
//~ 位非运算符按位取反运算规则转成二进制如果位为0结果是1如果位为1结果是0.
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
} catch (NoSuchFieldException | IllegalAccessException e) {
//内部工具类基本不抛出异常
throw new UtilException(e, "IllegalAccess for {}.{}", field.getDeclaringClass(), field.getName());
}
}
}
}
/**
* 获取方法的唯一键结构为:
* <pre>

View File

@ -13,7 +13,9 @@ import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
@ -268,4 +270,26 @@ public class ReflectUtilTest {
int[] intArray = ReflectUtil.newInstanceIfPossible(int[].class);
Assert.assertArrayEquals(new int[0], intArray);
}
public static class JdbcDialects {
private static final List<Number> DIALECTS =
Arrays.asList(1L, 2L, 3L);
}
@Test
public void setFieldValueTest() {
String fieldName = "DIALECTS";
final List<Number> dialects =
Arrays.asList(
1,
2,
3,
99
);
Field field = ReflectUtil.getField(JdbcDialects.class, fieldName);
ReflectUtil.removeFinalModify(field);
ReflectUtil.setFieldValue(JdbcDialects.class, fieldName, dialects);
Assert.assertEquals(dialects, ReflectUtil.getFieldValue(JdbcDialects.class, fieldName));
}
}