当前位置: 移动技术网 > 科技>办公>打印机 > 动态代理模式原理之反射动态加载技术

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

2020年07月31日  | 移动技术网科技  | 我要评论
动态代理模式原理之反射动态加载技术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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网