飞道的博客

你说反射有点难追,我觉得应该知难而退。

404人阅读  评论(0)

问题

今天小伙伴遇到一个问题,有关于反射的,写个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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场