阅读 349

动态代理说明以及模拟 Retrofit 实践 | 创作者训练营第二期

什么是动态代理

动态代理的核心为代理模式,代理模式在实践的过程分为静态代理和动态代理。 关于代理模式的说明,百度百科是这样说的:

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

使用代码的话语去说明,其实就是定义一个接口,后续的对象 A、B 可以实现该接口并实现其抽象方法 method,但是在访问 A、B 的时候,可以不直接引用 A、B 对象,而是通过接口引用直接调用 method。

静态代理

em...用文字说明还是太麻烦了,以下使用静态代理进行说明。show me the code!

Animal.kt

interface Animal {
    fun run()
}
复制代码

RabbitProxy.kt

class RabbitProxy : Animal{
    override fun run() {
        println("RabbitProxy run")
    }
}
复制代码

main()

fun main() {
    var animal : Animal = RabbitProxy()
    animal.run()
}
复制代码

运行结果:

RabbitProxy run
Process finished with exit code 0
复制代码

动态代理

RabbitProxy 是在编写代码的时候就固定写好了,而动态代理其实就是在运行时再生出一个类似 RabbitProxy 的对象。

在 Java 中可以使用通过以下方法来实现动态代理:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
复制代码

在说明这个方法之前,我们可以先看看怎么去使用:

    //创建对象
    var myObject = Proxy.newProxyInstance(Animal::class.java.classLoader, arrayOf(Animal::class.java),
        object : InvocationHandler {
            override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
                println("invoke ${method?.name}")
                return null
            }
        });
    //方法调用
    if (myObject is Animal){
        myObject.run()
    }
复制代码

运行结果:

invoke run
Process finished with exit code 0
复制代码

我们稍微分析下:

  • 由 Proxy.newProxyInstance() 创建的 myObject。
  • myObject 调用 run()。
  • 执行了 InvocationHandler 的 invoke 方法。

em...这个流程怎么这么熟悉?

这不是和我们平常创建的匿名内部类一样吗!!!

    //创建对象
    var myObject = object : Animal {
        override fun run() {
            println("invoke run")
        }
    }
    //方法调用
    if (myObject is Animal) {
        myObject.run()
    }
复制代码

所以,其实 newProxyInstance() 并没有那么复杂,其实就是帮我们动态创建一个对象,并且提供一个回调方法给我们重写而已,我们再次去分析它就很简单了:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
复制代码
  • ClassLoader:因为类是由 ClassLoader 进行加载的,所以我们需要传入 ClassLoader。
  • Class<?>[]:具体我们要动态代理那个接口,需要在这里声明。
  • InvocationHandler:这个就是回调了,调用动态代理生成的方法,就是回调到这里。

看到这里,估计大家都对于动态代理都有一定的了解了,但是假如用于平时的项目开发,能有什么用?

模拟Retrofit实践

下面,我们通过模拟 Retrofit 进行说明。

由于下面有涉及到注解,若还不懂注解的可以看看这文章从手写ButterKnife到掌握注解、AnnotationProcessor

进行网络请求,首先我们需要知道请求链接,以及请求方式,我们通过两个注解进行设置:

HttpUrl.kt

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class HttpUrl(val value: String)
复制代码

HttpMethod.kt

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class HttpMethod (val value: String)
复制代码

通过HttpRequest.kt存储访问的接口:

interface HttpRequest {
    @HttpMethod("GET")
    @HttpUrl("https://www.baidu.com/login")
    fun login(name: String, password: String) : UserInfo
}
复制代码

下面这个就是重点了,也就是要通过动态代理去调用 login(),并将返回的 UserInfo 输出出来。

UserInfo 其实只是个模拟访问接口成功,返回的 json 实体:

UserInfo.kt

data class UserInfo(var name: String)
复制代码

至于动态代理的回调部分,我主要是获取注解的值以及参数值进行输出,毕竟只要获取到值,至于怎么构建 Okhttp 进行网络请求,这些不是本文重点,稍微搜索也有一大堆内容。

MyRetrofit.kt

class MyRetrofit {
    companion object{
        fun <T> create(interfaces: Class<T>): T {
            //直接返回生成的对象
            return Proxy.newProxyInstance(Animal::class.java.classLoader,
                arrayOf(HttpRequest::class.java),
                object : InvocationHandler {
                    override fun invoke(
                        proxy: Any,
                        method: java.lang.reflect.Method,
                        args: Array<out Any>
                    ): Any? {
                        //获取注解
                        var httpUrl = method.getAnnotation(HttpUrl::class.java)
                        var httpMethod = method.getAnnotation(HttpMethod::class.java)
                        //将注解的值进行输出
                        println("Url链接:${httpUrl.value}")
                        println("请求方式:${httpMethod.value}")
                        //获取参数类型
                        var parameters = method.parameterTypes
                        //输出参数
                        for (i in args.indices){
                            println("参数名字:${parameters[i].name}  参数值:${args[i]}")
                        }
                        //模拟请求成功,直接返回一个实体
                        return UserInfo("不近视的猫")
                    }
                }) as T
        }
    }
}
复制代码

看着有点长,但是核心其实就是这些:

                        //获取注解
                        var httpUrl = method.getAnnotation(HttpUrl::class.java)
                        var httpMethod = method.getAnnotation(HttpMethod::class.java)
                        //将注解的值进行输出
                        println("Url链接:${httpUrl.value}")
                        println("请求方式:${httpMethod.value}")
                        //获取参数类型
                        var parameters = method.parameterTypes
                        //输出参数
                        for (i in args.indices){
                            println("参数类型:${parameters[i].name}  参数值:${args[i]}")
                        }
                        //模拟请求成功,直接返回一个实体
                        return UserInfo("不近视的猫")
复制代码

剩下就是进行调用了:

    var userInfo = MyRetrofit.create(HttpRequest::class.java).login("不近视的猫", "123456")
    println("登录成功!${userInfo.name}")
复制代码

运行:

Url链接:https://www.baidu.com/login
请求方式:GET
参数类型:java.lang.String  参数值:不近视的猫
参数类型:java.lang.String  参数值:123456
登录成功!不近视的猫
Process finished with exit code 0
复制代码

猜你喜欢

文章分类
Android
文章标签