动态代理模式原理之反射动态加载技术
动态代理模式原理之反射动态加载技术
Hello,大家好!我又来了,最近工作一直在忙于USB外接打印机接入速度高性能优化。从刚开始的接入到打印40s时长,极致性能优化到15s~19s时长,期间踩了不少吭最终达到理想效果。哎呀,话题扯的远了。前几天,一个小伙伴说:元哥,我在代码里老是看到 invoke() 这个方法,就是反射时候用到的,这是到底是干嘛的呀??正好我这几天在优化时,有阅读到底层源码里,用到动态代理。所以呢决定来写写,巩固一下知识,总结一下。
好啦,
本文主要通过以下几个部分来写
1.什么是反射,反射的用途?
引用官方文档的原话
Reflection
Reflection is a set of language and library features that allows for introspecting the structure of your own program at runtime.
意思大概是说,反射是一组语言和库特性,允许在运行时反思自己程序的结构。
读者:我嚓,你要不要这样,你这么直白的翻译,怎么一脸蒙呢。
作者:哈哈哈,被你说中了。别急请听我用简单的话来讲,一切简单话
反射,简单来讲,就是一开始并不清楚,不知道我们要去初始化的类是什么?
像平时我们在使用一个对象时,我们明确清楚要去怎么初始化对象,如:
正:val person = Person()
而我们反射呢,体现在一个“反” 上面。
反射就是在运行时才知道我们要操作的类是什么,包括获取类的构造,使用类的成员方法等等
反射的用途
举几个例子
-
通过反射去调用其类的私有方法.。
-
设计框架时为了扩展 使用反射。
-
通过反射配合注解实现APT技术。
。。。
。。。 装逼,喜欢搞逼格高的代码!
用途很多
2.反射的常用手段
这里我们简单起见呢,首先就定义一个类,用于测试。类如下:
/**
* @description 描述人,用于验证反射
* @author 陈元
* @date 2020 07/14
*/
class Person {
lateinit var name: String
var age: Int = 0
constructor()
constructor(name: String, age: Int){
this.name = name
this.age = age
}
private var address = "南京市蓬莱仙岛"
var doing = "练习反射,为动态代理的原理作准备!"
private fun speakLanguage(name: String = "吃葡萄不吐葡萄皮,不吃葡萄倒吐葡萄皮") {
println("南京人说:$name")
}
fun goodFood() {
println("南京正宗桂花鸭")
}
fun sayAddress(){
println(address)
}
var lambdaTest: (() -> Unit)? = null
}
这里呢,基本我们上平时写的类的结构都有了,什么构造器呀,有参的,无参的,公开方法,私有方法,私有字段,公开字段 等等。
我们说,一切皆对象!我们的class 也一样,也有一个专门的保存类的结构化信息的Class对象
1.获取类的Class对象,我们JDK为我们提供了三种方式。
示例:
/**
* description: 反射初级用法
* author: 陈元
* data:2020/7/21
*/
fun main() {
/**
* class对象 获取方式一,通过 类的完整包名
*/
val personClass = Class.forName("com.myapplication.Person")
println(personClass)
val personal = Person("刘亦菲", 18)
/**
* class对象 获取方式二,通过 javaClass
*/
val personClass1 = personal.javaClass
println(personClass1)
/**
* Class对象 获取方式三,通过 类名::class.java
*/
val personClass2 = Person::class.java
println(personClass2)
通过反射获取构造器
/**
* 通过反射获取Person的实例对象,无参的
*/
val person1 = getPerson(personClass as Class<Person>)
println(person1)
/**
* 通过反射获取Person的实例对象,带参数的
*/
val person2 = getPerson1(personClass1,"周芷若",18)
val person3 = getPerson1(personClass2,"赵敏",18)
通过反射获取方法
示例:
/**
* 反射获取类的方法,包括父类的方法
*/
personClass.methods.forEach {
println(it.name)
}
println("_____________ 疑问? 为啥打印出来没有私有方法呢?? 原来需要使用declared _______________________")
/**
/**
* 反射获取类的自身的所有方法
*/
personClass.declaredMethods.forEach {
println(it.name)
}
结果:
这里包括父类的wait notify 等等方法都能获取到。
getName
setName
sayAddress
getDoing
setAge
getLambdaTest
setDoing
getAge
setLambdaTest
goodFood
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
____________________________________
getName
setName
sayAddress
getDoing
speakLanguage
speakLanguage$default
setAge
getLambdaTest
setDoing
getAge
setLambdaTest
goodFood
setLambadaTest方法 哪来的??
哎,兄弟。请看,这就是呀,在我们kotlin里面函数可以类似于前端写的ES6一样,函数声明
var lambdaTest: (() -> Unit)? = null
哦,这样啊。
还可以获取属性,这里就简单提一下
/**
* 反射获取类的自身所有属性 成员变量
*/
personClass.declaredFields.forEach {
println(it)
}
还可以获取,注解、修饰符、等等 这些就不一一例举了,只要是类有的结构都能拿得到,具体可以查阅官方文档
我们来玩一点,好玩的。
平时我们在开发中,总会遇到需要修改或调用源码里的私有方法,或属性,或者呢,调用Android父类里面某个私有方法,或属性。这时反射就可以很好的解决这个问题了。
示例:
/**
* 反射私有方法,并调用
*/
fun usePrivateMethod(clazz: Class<Person>){
val method = clazz.getDeclaredMethod("speakLanguage",String::class.java)
//由于是私有对象,所以需要授权
method.isAccessible = true
method.invoke(getPerson(clazz),"反射调用speakLanguage方法")
}
哦,原来这个invoke 是这么用的呀!哈哈哈哈
这里面的getDeclaredMethod 是获取你要调用 的私有方法,参数1:方法名,参数2:参数类形,最后使用invoke调用,就可以了。就这么简单
调用私有属性,并使用,就不一一举例,和使用私有方法调用差不多,有兴趣的小伙伴可以查阅一下文档哦。这里只是抛砖引玉,后面可以和大家家说说 通过反射配合注解实现APT技术。
3.代理
今天我们主要来说说代理,代理这个东西,很简单,举个现实生活中的例子,我们要购物,我们如果自己到街上或店里买东西麻烦, 我们呢可以找第三方平台,我们要买 衣服,没问题,第三平台给你展示你想要的裤子,自己只要在平台上挑选好买就可以了。 我们要买 情趣用品,没问题 ,只要挑选好,交付订金就可以了。
喂喂,你说着说着,怎么开车了,还提到情趣用品, 我们平时程序中有什么用处呢?
我们平时开发当中,目的,
1.通过引入代理对象来间接的访问目标对象,防止直接访问目标对象,给程序带来不必要的复杂性,和可扩展性。
2.。何为扩展性,就是我们平时开发中,还可以使用代理对象对原有的业务实现一个增强。
这里简单用图来描述:
4.静态代理模式
在程序中,我们平时虽然不用静态代理,我们也提一下吧。多使用动态代理
根据上述图示例,我们简单来实现一个,需要打印功能。
示例:
一,首先,我们根据图知,我们需要接口对象,如下
/**
* description: 打印接口
* author: 陈元
* data:2020/7/29
*
*/
interface Call {
fun printCall(path:String)
}
二,我们需要目标对象,接口实现
/**
* description: 实现打印
* author: 陈元
* data:2020/7/29
*
*/
class UsbCall : Call {
override fun printCall(path: String) {
println("打印了。。 $path")
}
}
三,我们需要代理对象,接口程序
/**
* description: 第三方代理
* author: 陈元
* data:2020/7/29
*
*/
class PrintUtils(var usbCall: Call) : Call {
/**
* 这就是使用代理的好处,不直接访问目标对象。间接访问,
* 还可以对其功能进行丰富扩展,我们在打印之前,可以加
*/
override fun printCall(path: String) {
loading()
beforPrintTodo()
usbCall.printCall(path)
afterPrintTodo()
dismissLoading()
}
// TODO: 2020/7/29 0029 这里可以扩展业务代码
private fun beforPrintTodo() {
TODO("Not yet implemented")
}
// TODO: 2020/7/29 0029 这里可以扩展业务代码,之后做事
private fun afterPrintTodo() {
TODO("Not yet implemented")
}
fun loading(){
println("打印中。。。。")
}
fun dismissLoading(){
println("完成打印")
}
}
使用静态代理,就简单了
/**
* 静态代理
*/
PrintUtils(UsbCall()).printCall("d://print.pdf")
结果:
开始打印
打印中。。。 d://print.pdf
完成打印
静态代理优缺点
这里我们先说经验结论:我们有了静态代理,为什么还要动态代理,我们平时应该知道工程开发开发,维护,性能优化,甚至有时,还会偶尔重构起来了,重构起来麻烦,原有代码复杂。
静态代理首先的缺点就是,每实现一个功能,我都要去实现一个代理,功能少时,感觉问题不大,功能多时,一对一 会发现,代理对象较多,代码冗余,代码量大,维护成本高,有人会说,那使用一对多呢,就放在一个代理类里面,这样更吭,导致代码阅读困难,扩展能力也是相当差。
这样违背了,我们开发所说的,六大原则中的 《开闭原则》
因此我们使用动态代理,来解决这个问题
5.动态代理模式
我们首先来创建一个动态代理
/**
* description: 打印工厂
* author: 陈元
* data:2020/7/29
*
*/
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
class CallCompany(var iObject: Any):InvocationHandler {
/**
* 获取动态代理对象
*/
fun getProxy(): Any {
return Proxy.newProxyInstance(iObject.javaClass.classLoader,iObject.javaClass.interfaces,this)
}
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
beforTodo()
return method?.invoke(iObject, *args.orEmpty()).apply {
afterTOdo()
}
}
// TODO: 2020/7/29 0029 扩展业务代码
private fun beforTodo() {
}
// TODO: 2020/7/29 0029 扩展业务代码
private fun afterTOdo() {
}
}
使用动态代理,首先们需要实现 InvocationHandler 接口, 我们不禁会想为什么会用这个东西,这是JDK给我们提供的。还需要我们重写invoke方法。先不探究其原理,稍后我们再看。我们先看使用
使用
/**
* 动态代理
*/
val proxy:Call = CallCompany(UsbCall()).getProxy()
println(proxy.javaClass.name) //留意一下这个输出
proxy.printCall("e://print.pdf")
结果:
com.sun.proxy.$Proxy0
开始打印
打印中。。。 e://print.pdf
完成打印
动态代理优缺点
动态代理优点:
首先使用一个代理对象,解决了,开闭原则、去掉了多个静态代理类,使代码简洁,代码量变少
避免重复,还有最大的优点,就是灵活,多变。
缺点:
等会我们会一起去分析一下源码,会发现由于动态代理,使用到反射的动态加载技术,因此在效率上面不如静态代理。因此如果需要频繁的循环调用,例用反射的话, 会使性能上有损耗。
6.JDK动态代理底层原理
好了,讲了这么多,我们一起去探究一下,我们上面提到的两个问题,为什么需要用InvocationHandler ,invoke又是什么? 背后的原理是什么?
首先我们在上面让大家留意的那句注释,请看输出结果
com.sun.proxy.$Proxy0
这里怎么打印出来这个包名了,还有Proxy 这个类我们并没有实现呀,我们工程里是没有的。我们去点开调用处 Proxy.newProxyInstance 进去看一看源码:
*/
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
// Android-removed: SecurityManager calls
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
// Android-removed: SecurityManager / permission checks.
/*
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
*/
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
// BEGIN Android-removed: Excluded AccessController.doPrivileged call.
/*
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
*/
cons.setAccessible(true);
// END Android-removed: Excluded AccessController.doPrivileged call.
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
我们再看源码的时候,别去弄懂,每一行代码是什么意思,那个不容易,需要画费不少精力,而且容易钻牛角尖的,一不留神就会陷进去。发现云里雾里,我们带着目标去找,这样相对简单。而且,我们看源码时,还要会多猜测,这是什么意思 。 我们主要是学习是思想,是优秀代码是怎么处理的,我们平时也照着这样的思想去在自己的程序中使用。或使用第三方框架时,发现疑难杂证的问题,我们好定位问题。解决问题。
好了,我们开始,首先,我们看到上面代码里面,像这种代码不需要去看的,也不需要去关心的。通过名字就简单猜测,是检查的,检查是不是空啊,什么类写的合不合理啊,之类的。
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
// Android-removed: SecurityManager calls
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
读着读着,我们发现,
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
我们先留意一下,看到Proxy了,我们继续阅读发现,这行代码入了我们的法眼
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
哦,原来,是Jdk给我们创建了对象实例,通过cl 来使用反射机制构造器,创建cons 这个类,这个h 不正是我们实现动态代理要实现的InvocationHandler 吗。
怪不得我们必须得实现InvocationHandler 呢,这个cl通过前面的留意,应该是jdk给我们创建 的对象,我们再去看看。Class<?> cl = getProxyClass0(loader, intfs); 点进去,
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
我们进来,首先发现,Waht if (interfaces.length > 65535) ,尼玛,从这里我们得出一个结论,我们Java 支持的接口数量,最大只能是65534 个。好了,我们继续阅读
return proxyClassCache.get(loader, interfaces); 这句代码,入了我们的法眼,
通过这个ProxyClassCache.get() 我们可以推测,JDK内部应该是采用了某种缓存机制,缓存了我们的动态代理class对象,并且通过这个get 方法,我们发现,里面用到了,被代理类的类加载器,和类实现的接口。
我们再继续 点进去,查看get 方法
* @return the cached value (never null)
* @throws NullPointerException if {@code parameter} passed in or
* {@code sub-key} calculated by
* {@code subKeyFactory} or {@code value}
* calculated by {@code valueFactory} is null.
*/
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
Object cacheKey = CacheKey.valueOf(key, refQueue);
// lazily install the 2nd level valuesMap for the particular cacheKey
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}
// else no supplier in cache
检查的相关操作,我们不关心,我们首先看到什么 CachKey 什么的,果然,我们猜测没错,我们不关心怎么缓存的, 我们要找到主要目标,我们的key 和 parameter 是在哪用到。我们继续往下阅读,发现下面的一行,
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
哎,这个入了我们的法眼,这个用用到我们的参数 ,我们点进去看看
尼玛,这个是一个接口,是jdk 一个高阶接口,就是类似 我们kotlin 里面四大高阶函数,一样,什么 apply, also,let 等等,我们先不管这些,我们点击 红色圈,看谁实现了这个类,发现有很多实现了这个类,我们发现有一个类,正是我们要找的
/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
我们发现,这个ProxyClassFactory,正好是实现了这个apply,,我们欣喜若狂,哈哈哈哈
继续阅读, 我们发现
我嚓,我嚓,尼玛,终于找到了,原来是这边,我们创建了 之前,我们一直苦苦思所的,$Proxy0 哪来的,这个proxyPKg 就等等于 com.sun.proxy. 请看代码上面
proxyPkg = ReflectUtil.PROXY_PACKAGE + “.”; 这个常量,
public final class ReflectUtil {
public static final String PROXY_PACKAGE = "com.sun.proxy";
原来是这么拼起来的,拼成我们的 com.sun.proxy.$Proxy0 ,豁然开朗,那么还有一个小小的疑惑,这个怎么创建的,请看蓝色 处 generateProxyClass ,我们点进去看看了,发现点不到了,到头了,下面是底层实现的,我们只知道 这个返回值就是这个类的字节码就行,目标就达到了。
- Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
这个 generateProxyClass 这个方法,就是给我们生成这个Class字节码的,返回的是Byte数组,这个数据,都是0 1 0 1 ,十六进制的,用反编译工具可以查看对应字符串。这里就不带着大家去查看了。上一章,讲 kotlin 之 OUT 最后有看到字节码 ,就是和这个一样。 这个大家感兴趣的话,可以要去看看字节码语法规则。包括JVM虚拟机等。
其实有很多开源框架都用到了,包括大名顶顶的 Retrofit ,后绪有机会,再和大家分享这个框架里面的具体实现。
本文地址:https://blog.csdn.net/summerSpringwinter/article/details/107372621
上一篇: Java DVD租借系统
下一篇: 详解Maven私服Nexus的安装与使用