!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
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>基本类型的包装类 Byte、Character、Short、Long、Float、Double、Boolean</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));
}
}