正确使用Kotlin动态代理

前言:

说到设计模式,想必很多人都会想到,常见的设计模式之一的动态代理。特别是,对很多中高级Android程序员而言,更是如此。因为著名的网络框架Retrofit,关于网络调用部分,就是采用动态代理,将网络请求,委托给OkHttp实现。但在使用Kotlin语言,来实现动态代理时,存在一些坑。这篇博文,将为你揭开这些坑的生成原因和填坑的方法。


首先,我们先来看一个Java写法的Kotlin版动态代理,怎么实现:

第一步,我们先创建动态代理类KotlinDynamicProxy:

class KotlinDynamicProxy(private val baseProxy: BaseProxy?) : InvocationHandler {

    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {

        try {
            if (baseProxy== null) return null
            // 动态代理调用
            method?.let { return it.invoke(baseProxy, args)  }
            return null
        } catch (e: Exception) {
            Log.d("Debug","动态代理异常:${e.message}")
        }
        return null
    }

}

下一步,我们定义个接口IAnimalProxy:

interface IAnimalProxy {
   fun noneArgs()
   fun singlePrimitiveArg(test: Int)
   fun singleLambdaArg(listener: (Int)->Unit)
   fun multipArgs(a: Int, b: Int)
}

下一步,我们创建,代理实现类AnimalProxy

class AnimalProxy:IAnimalProxy {
   override fun noneArgs(){ Log.d("Debug","noneArgs") }
   override fun singlePrimitiveArg(test: Int){ Log.d("Debug","singlePrimitiveArg") }
   override fun singleLambdaArg(listener: (Int)->Unit){ Log.d("Debug","singleLambdaArg") }
   override fun multipArgs(a: Int, b: Int){ Log.d("Debug","multipArgs") }
}

下一步,我们foo()方法里面,创建动态代理,并执行所有API的调用:

fun foo() {

     val proxy = Proxy.newProxyInstance(
            IAnimalProxy ::class.java.classLoader,
            arrayOf(IAnimalProxy ::class.java),
            BinderDynamicProxy(new AnimalProxy())
        ) as IAnimalProxy 
     noneArgs(proxy)
     singlePrimitiveArg(proxy)
     singleLambdaArg(proxy)
     multipArgs(proxy)
}

接下来,我们先来看看各个方法执行的情况:

1. noneArgs的实现和结果:

 fun noneArgs(proxy: AnimalProxy){ proxy.noneArgs() }

执行结果:成功,log正常打出,没问题。

2.singlePrimitiveArg的实现和结果:

 fun singlePrimitiveArg(proxy: AnimalProxy){ proxy.singlePrimitiveArg(1) }

执行结果:成功,log正常打出,没问题。

3.singleLambdaArg的实现和结果:

 fun singleLambdaArg(proxy: AnimalProxy){ proxy.singleLambdaArg{} }

执行结果:失败,抛出异常:

method com.xxx.AnimalProxy.singleLambdaArg argument 1 has type kotlin.jvm.functions.Function1, got java.lang.Object[]

失败的原因是,编译器会将,lambda表达式,即函数类型参数,转成kotlin的通用标准接口Function1。也就是说,编译后的代码是不同的,具体如下:

// 编译前
fun singleLambdaArg(listener: (Int)->Unit)

// 编译后
void singleLambdaArg(Function1 function1)

你可能会好奇,这个Function1是啥,Function1的本质就是一个接口,这个接口接受单个参数,这个参数是个泛型。所以,它可以完善的替换上述例子中的lambda表达式(listener: (Int)->Unit)。附带学习资料:Kotlin 高阶函数。

好了,我们回归到问题本身,即编译后的方法是这样的:

// 编译后
void singleLambdaArg(Function1 function1)

但是,我们动态代理时,传过去的参数却是个Array类型:args: Array?

人家要个Function1参数,我们却传了个Array,那肯定不行,所以,才会抛出异常。解决方案我们先不说,继续看最后一种场景。

4.multipArgs的实现和结果:

 fun multipArgs(proxy: AnimalProxy){ proxy.multipArgs(100,200) }

执行结果:失败,抛出异常:

Wrong number of arguments; expected 2, got 1

失败的原因,很简单,因为multipArgs方法,需要2个参数,我们只却只传了一个参数。你可能会问,我不是已经传了100和200,2个参数了吗?怎么说是1个。问的好!我们再来看看,我们动态代理传参数是怎么写的:

 override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? { 
         // 省略代码
          method?.let { return it.invoke(baseProxy, args)  }
         // 省略代码
    }

我们这里,只传了一个参数args,这个参数的类型是:Array。那你可能会问,这里的Array,即args内部已经包含了100和200,2个Int参数了呀。

但实际上,如果你这个类是个Java类,则没问题,如你所想,是正确的。
但如果是个kotlin类,则有问题,编译器会认为你是要传一个Array类型的参数过去。

好了,问题和原因都解释清楚了,那我们怎么解决,3和4,两种情况的问题呢?
答案是:延展操作符(spread operator):*(星号)【个人觉得叫拆包操作符更合适】

正确的动态代理写法如下

class KotlinDynamicProxy(private val baseProxy: BaseProxy?) : InvocationHandler {

    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {

        try {
            if (baseProxy== null) return null
            method?.let {
                // 延展操作符,不能作用在可空对象上。无参数时,直接传null即可。
                if (args == null) return it.invoke(baseProxy)
                // args ==变成==> *args
                else return it.invoke(baseProxy, *args)
            }
            return null
        } catch (e: Exception) {
            Log.d("Debug","动态代理异常:${e.message}")
        }
        return null
    }
}

关键修改it.invoke(baseProxy, args) 改成 it.invoke(baseProxy, *args)

这样修改后,
基于第3种情况,编译器,会拆包,取出Array中的Function1对象,传给method.invoke方法。
基于第4种情况,编译器,会拆包,取出Array中的两个参数,传给method.invoke方法。

好了,到这里,你应该学会了如何正确的使用Kotlin动态代理了,另外我再附上相关的学习资料:

  1. 延展操作符(spread operator)。
  2. Why is Kotlin throw IllegalArgumentException when using Proxy?

你可能感兴趣的:(Kotlin,Java,设计模式,kotlin,android,kotlin动态代)