正确使用Kotlin动态代理

4 篇文章 0 订阅
1 篇文章 0 订阅

前言:

说到设计模式,想必很多人都会想到,常见的设计模式之一的动态代理。特别是,对很多中高级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<out Any>?

人家要个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?
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值