问题
今天小伙伴遇到一个问题,有关于反射的,写个demo,大家看一下。
如上,运行之后会报错:出现了非法参数。
Exception in thread "main" java.lang.IllegalArgumentException: Can not set java.lang.Integer field com.ossa.web3.reflect.A.age to (int)10
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:191)
at java.base/jdk.internal.reflect.UnsafeObjectFieldAccessorImpl.setInt(UnsafeObjectFieldAccessorImpl.java:114)
at java.base/java.lang.reflect.Field.setInt(Field.java:984)
at com.ossa.web3.reflect.TestReflect.main(TestReflect.java:15)
源码解析
就此问题,我们来解析一下源码。
步入setInt()方法,注意这三个红色框框:一个是上面我们抛出的错误,这里主要是解包失败,类型没匹配成功。
getFieldAccessor(obj)方法获取一个属性访问器FieldAccessor。
我们来看一看是如何获取FieldAccessor的。
步入getFieldAccessor(obj)方法,在步入acquireFieldAccessor(ov)方法
这里会由反射工厂创建一个UnsafeObjectFieldAccessorImpl属性访问器tmp,然后将这个访问器添加进root中
这个UnsafeObjectFieldAccessorImpl可就有意思了。
它里面会有一下setXx、getXx方法,除了set()方法之外,其余都是抛出非法参数异常。
行吧,都到这了,我们顺便看看反射工厂是如何创建属性访问器的:
tmp = reflectionFactory.newFieldAccessor(this, overrideFinalCheck);
步入newFieldAccessor方法中,最终调用UnsafeFieldAccessorFactory.newFieldAccessor(field, isReadOnly);
步入核心方法newFieldAccessor中:
我们来好好看看这个方法,这个方法中是反射修改属性的核心方法,包括修改静态属性、final属性、volatile属性等。
整体来说,就是优先判断属性是不是静态的,再判断其是否是final和volatile。当然我们这里不是静态,也不是final和volatile。
来看看吧:
我们这里属性是type = class Java.lang.Integer
这里面都是各种类型判断。
溯源
那我们继续走,看看会发生什么。
哦?到这里,貌似是可以匹配上。
但是实际并没有。
Integer.TYPE = int
而我们的type是class Java.lang.Integer
一个是基本数据类型,一个是引用类型,噶了。
所以最后都没匹配成功,创建了一个UnsafeObjectFieldAccessorImpl。然而这个访问器最后调用setInt()方法会抛出非法参数异常,那么问题根源就找到了。
问题解决方案
所以,我们该怎么解决这个问题呢?
第一种:
我们可以将属性的类型改为int,就像这样:
然后看一下核心源码的判断部分:这里就匹配上了,创建了UnsafeIntegerFieldAccessorImpl对象。
最后调用UnsafeIntegerFieldAccessorImpl对象的setInt方法。
然后调用本地方法 @IntrinsicCandidate public native void putInt(Object o, long offset, int x);
第二种:
解决方法(无效);
你说有没有可能将属性的类型由Integer变成int,这样不就可以用setInt方法,最后也可以匹配成功么?
我们来试试,
jdk8:类型是可以转换成功,但是值设置不进去,具体原因不详,谁知道记得评论告诉我。
jdk17:就更有意思了,压根连type属性就反射不到。
第三种:
解决方法:
使用set方法,这样在核心源码匹配的时候就都匹配不上,直接创建UnsafeObjectFieldAccessorImpl对象,调用set方法:
这个靠谱,没有对应类型的就都可以用这个去调用本地native方法。
完结了,撒花,又浪费10分钟写文章,你们谁要是看到这里,就帮忙看看上面为什么修改属性类型之后不能设置值?
爱你们,么么。♥♥♥
转载:https://blog.csdn.net/CSDN_SAVIOR/article/details/128832995