最近发现微信多了个专辑功能,可以把一系列的原创文章聚合,刚好我每周都会遇到很多同学问我各种各样的问题,部分问题还是比较有意义的,我会在周末详细的写demo验证,简单扩展一下写成文章分享给大家。
来一起看一段代码:
public?class?Student??{?????private?Student()?{?????????throw?new?IllegalArgumentException("can?not?create.");?????}?????public?String?name;??}?
我们如何通过Java代码创建一个Student对象?
我们先想下通过Java创建对象大概有哪些方式:
好了,已经超出我的知识点范畴了。
不免心中嘀咕:
是的,确实很偏,跳过这个问题,我们往下看,看看是怎么在Android开发过程中遇到的,而且看完后,这个问题就迎刃而解了。
上周一个群有个小伙伴,遇到了一个Kotlin写的Bean,在做Gson将字符串转化成具体的Bean对象时,发生了一个不符合预期的问题。
因为是他们项目的代码,我就不贴了,我写了个类似的小例子来替代。
对于Java Bean,kotlin可以用data class,网上也有很多博客表示:
在 Kotlin 中,不需要自己动手去写一个 JavaBean,可以直接使用 DataClass,使用 DataClass 编译器会默默地帮我们生成一些函数。
我们先写个Bean:
data?class?Person(var?name:?String,?var?age:?Int)?{???}?
这个Bean是用于接收服务器数据,通过Gson转化为对象的。
简化一下代码为:
val?gson?=?Gson()?val?person?=?gson.fromJson<Person>("{\"age\":\"12\"}",?Person::class.java)?
我们传递了一个json字符串,但是没有包含key为name的值,并且注意:
那么上面的代码,我运行起来结果是什么呢?
感觉1最合理,也符合Kotlin的空安全检查。
验证一下,修改一下代码,看一下输出:
val?gson?=?Gson()?val?person?=?gson.fromJson<Person>("{\"age\":\"12\"}",?Person::class.java)?println(person.name?)?
输出结果:
null?
是不是有些奇怪,?感觉意外绕过了Kotlin的空类型检查。
所以那位出问题的同学,在这里之后数据就出了问题,导致一直排查困难。
我们再改一下代码:
data?class?Person(var?name:?String,?var?age:?Int):?People(){?}?
我们让Person继承自People类:
public?class?People?{??????public?People(){?????????System.out.println("people?cons");?????}??}?
在People类的构造方法中打印日志。
我们都清楚,正常情况下,一般构造子类对象,必然会先执行父类的构造方法。
运行一下:
这里可以猜到,?Person对象的构建,并不是常规的构建对象,没有走构造方法。
只能去Gson的源码中去找答案了。
找到其怎么做的,其实就相当于解答了我们文首的问题。
Gson这样构造出一个对象,但是没有走父类构造这种,如果真是的这样,那么是极其危险的。
会让程序完全不符合运行预期,少了一些必要逻辑。
首先我们把Person这个kotlin的类,转成Java,避免背后藏了一些东西:
#?反编译之后的显示?public?final?class?Person?extends?People?{????@NotNull????private?String?name;????private?int?age;?????@NotNull????public?final?String?getName()?{???????return?this.name;????}?????public?final?void?setName(@NotNull?String?var1)?{???????Intrinsics.checkParameterIsNotNull(var1,?"<set-?>");???????this.name?=?var1;????}?????public?final?int?getAge()?{???????return?this.age;????}?????public?final?void?setAge(int?var1)?{???????this.age?=?var1;????}?????public?Person(@NotNull?String?name,?int?age)?{???????Intrinsics.checkParameterIsNotNull(name,?"name");???????super();???????this.name?=?name;???????this.age?=?age;????}?????//?省略了一些方法。?}?
可以看到Person有一个包含两参的构造方法,并且这个构造方法中有name的空安全检查。
也就是说,正常通过这个构造方法构建一个Person对象,是不会出现空安全问题的。
那么只能去看看Gson的源码了:
Gson的逻辑,一般都是根据读取到的类型,然后找对应的TypeAdapter去处理,本例为Person对象,所以会最终走到`ReflectiveTypeAdapterFactory.create`然后返回一个TypeAdapter。
我们看一眼其内部代码:
#?ReflectiveTypeAdapterFactory.create?@Override??public?<T>?TypeAdapter<T>?create(Gson?gson,?final?TypeToken<T>?type)?{?????Class<??super?T>?raw?=?type.getRawType();??????if?(!Object.class.isAssignableFrom(raw))?{???????return?null;?//?it's?a?primitive!?????}??????ObjectConstructor<T>?constructor?=?constructorConstructor.get(type);?????return?new?Adapter<T>(constructor,?getBoundFields(gson,?type,?raw));?}?
重点看constructor这个对象的赋值,它一眼就知道跟构造对象相关。
#?ConstructorConstructor.get?public?<T>?ObjectConstructor<T>?get(TypeToken<T>?typeToken)?{?????final?Type?type?=?typeToken.getType();?????final?Class<??super?T>?rawType?=?typeToken.getRawType();??????//?...省略一些缓存容器相关代码??????ObjectConstructor<T>?defaultConstructor?=?newDefaultConstructor(rawType);?????if?(defaultConstructor?!=?null)?{???????return?defaultConstructor;?????}??????ObjectConstructor<T>?defaultImplementation?=?newDefaultImplementationConstructor(type,?rawType);?????if?(defaultImplementation?!=?null)?{???????return?defaultImplementation;?????}??????//?finally?try?unsafe?????return?newUnsafeAllocator(type,?rawType);???}?
可以看到该方法的返回值有3个流程:
我们先看第一个newDefaultConstructor
private?<T>?ObjectConstructor<T>?newDefaultConstructor(Class<??super?T>?rawType)?{?????try?{???????final?Constructor<??super?T>?constructor?=?rawType.getDeclaredConstructor();???????if?(!constructor.isAccessible())?{?????????constructor.setAccessible(true);???????}???????return?new?ObjectConstructor<T>()?{?????????@SuppressWarnings("unchecked")?//?T?is?the?same?raw?type?as?is?requested?????????@Override?public?T?construct()?{?????????????Object[]?args?=?null;?????????????return?(T)?constructor.newInstance(args);??????????????//?省略了一些异常处理???????};?????}?catch?(NoSuchMethodException?e)?{???????return?null;?????}???}?
可以看到,很简单,尝试获取了无参的构造函数,如果能够找到,则通过newInstance反射的方式构建对象。
返回null会走newDefaultImplementationConstructor,这个方法里面都是一些集合类相关对象的逻辑,直接跳过。
那么,最后只能走:?newUnsafeAllocator?方法了。
从命名上面就能看出来,这是个不安全的操作。
往下看,最终执行的是:
public?static?UnsafeAllocator?create()?{?//?try?JVM?//?public?class?Unsafe?{?//???public?Object?allocateInstance(Class<?>?type);?//?}?try?{???Class<?>?unsafeClass?=?Class.forName("sun.misc.Unsafe");???Field?f?=?unsafeClass.getDeclaredField("theUnsafe");???f.setAccessible(true);???final?Object?unsafe?=?f.get(null);???final?Method?allocateInstance?=?unsafeClass.getMethod("allocateInstance",?Class.class);???return?new?UnsafeAllocator()?{?????@Override?????@SuppressWarnings("unchecked")?????public?<T>?T?newInstance(Class<T>?c)?throws?Exception?{???????assertInstantiable(c);???????return?(T)?allocateInstance.invoke(unsafe,?c);?????}???};?}?catch?(Exception?ignored)?{?}??//?try?dalvikvm,?post-gingerbread?use?ObjectStreamClass?//?try?dalvikvm,?pre-gingerbread?,?ObjectInputStream??}?
可以看到Gson在没有找到无参的构造方法后,通过?sun.misc.Unsafe?构造了一个对象。
我们这里暂时只讨论sun.misc.Unsafe,其他的其实一个意思。
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。
https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
具体可以参考美团的这篇文章。
好了,到这里就真相大白了。
到这里,我们收获了:
Java中咋么构造一个下面的Student对象呢?
public?class?Student??{?????private?Student()?{?????????throw?new?IllegalArgumentException("can?not?create.");?????}?????public?String?name;?}?
我们模仿Gson的代码,编写如下:
try?{?????val?unsafeClass?=?Class.forName("sun.misc.Unsafe")?????val?f?=?unsafeClass.getDeclaredField("theUnsafe")?????f.isAccessible?=?true?????val?unsafe?=?f.get(null)?????val?allocateInstance?=?unsafeClass.getMethod("allocateInstance",?Class::class.java)?????val?student?=?allocateInstance.invoke(unsafe,?Student::class.java)?????(student?as?Student).apply?{?????????name?=?"zhy"?????}?????println(student.name)?}?catch?(ignored:?Exception)?{?????ignored.printStackTrace()?}?
输出:
shy?
成功构建。
Unsafe 一点用没有?
看到这里,大家可能最大的收获就是了解Gson构建对象流程,以及以后写Bean的时候会注意提供默认的无参构造方法,尤其在使用Kotlin ?`data class `的时候。
这个类,提供了类似C语言指针一样操作内存空间的能力。
大家都知道在Android P上面,Google限制了app对hidden API的访问。
但是,Google不能限制自己对hidden API访问对吧,所以它自己的相关类,是允许访问hidden API的。
其中有一个办法就是通过ClassLoader,系统认为如果ClassLoader为BootStrapClassLoader则就认为是系统类,则放行。
怎么换呢?
参考代码:
private?void?testJavaPojie()?{?????try?{???????Class?reflectionHelperClz?=?Class.forName("com.example.support_p.ReflectionHelper");???????Class?classClz?=?Class.class;???????Field?classLoaderField?=?classClz.getDeclaredField("classLoader");???????classLoaderField.setAccessible(true);???????classLoaderField.set(reflectionHelperClz,?null);?????}?catch?(Exception?e)?{???????????e.printStackTrace();?????}?}?来自:https://juejin.im/post/5ba0f3f7e51d450e6f2e39e0
但是这样有个问题,上面的代码用到了反射修改一个类的classLoader成员,假设google有一天把反射设置classLoader也完全限制掉,就不行了。
参考代码:
@Keep?public?class?ReflectWrapper?{??????//just?for?finding?the?java.lang.Class?classLoader?field's?offset?????@Keep?????private?Object?classLoaderOffsetHelper;??????static?{?????????try?{?????????????Class<?>?VersionClass?=?Class.forName("android.os.Build$VERSION");?????????????Field?sdkIntField?=?VersionClass.getDeclaredField("SDK_INT");?????????????sdkIntField.setAccessible(true);?????????????int?sdkInt?=?sdkIntField.getInt(null);?????????????if?(sdkInt?>=?28)?{?????????????????Field?classLoader?=?ReflectWrapper.class.getDeclaredField("classLoaderOffsetHelper");?????????????????long?classLoaderOffset?=?UnSafeWrapper.getUnSafe().objectFieldOffset(classLoader);?????????????????if?(UnSafeWrapper.getUnSafe().getObject(ReflectWrapper.class,?classLoaderOffset)?instanceof?ClassLoader)?{?????????????????????Object?originalClassLoader?=?UnSafeWrapper.getUnSafe().getAndSetObject(ReflectWrapper.class,?classLoaderOffset,?null);?????????????????}?else?{?????????????????????throw?new?RuntimeException("not?support");?????????????????}?????????????}?????????}?catch?(Exception?e)?{?????????????throw?new?RuntimeException(e);?????????}?????}?}?来自作者区长:一种纯?Java?层绕过?Android?P?私有函数调用限制的方式,一文。?
Unsafe赋予了我们操作内存的能力,也就能完成一些平时只能依赖C++完成的代码。
好了,从一位朋友遇到的问题,由此引发了一整篇文章的讨论,希望你能有所收获。
领取专属 10元无门槛券
私享最新 技术干货