什么是动态代理
动态代理的核心为代理模式,代理模式在实践的过程分为静态代理和动态代理。
关于代理模式的说明,百度百科是这样说的:
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
使用代码的话语去说明,其实就是定义一个接口,后续的对象 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
复制代码