欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

动态代理模式原理之反射动态加载技术

程序员文章站 2022-06-30 19:55:35
动态代理模式原理之反射动态加载技术Hello,大家好!我又来了,最近工作一直在忙于USB外接打印机接入速度高性能优化。从刚开始的接入到打印40s时长,极致性能优化到15s时长,期间踩了不少吭最终达到理想效果。哎呀,话题扯的远了。前几天,一个小伙伴说:元哥,我在代码里老是看到 invoke() 这个方法,就是反射时候用到的,这是到底是干嘛的呀??正好我这几天在优化时,有阅读到底层源码里,用到动态代理。所以呢决定来写写,巩固一下知识,总结一下。好啦,发车 本文主要通过以下几个部分来写1.什么是反射,反射的...

动态代理模式原理之反射动态加载技术

        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

        这里怎么打印出来这个包名了,还有Proxy0钱符号了,后面仅接着是Proxy,数字0 ,这个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