前言
平时写代码的时候可能为了完成某一个任务而只是应付性地编码,然后写完理直气壮地来一句"又不是不能用!",但如果要把编码当作一项艺术来打造,那就需要结合我们的设计模式了。设计模式可以运用在诸多方面,让我们的代码更加优雅。设计模式在Android中也是无处不在,动态代理、建造者、工厂、适配器....等等等等,可见它对我们的重要性。最近在看Retrofit的源码,刚好看到了动态代理,想着总结下这个模式。
什么是代理模式?
代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理。
乍这么一看,好像还是说不明白,代理模式分为两种,静态代理和动态代理,静态代理是由我们手动编写代理类,动态代理是在运行时由程序生成的代理类,二者的最终目的都是为了代理另外一个类(被代理类)的功能。以我们日常代购火车票为例,这里先简要了解下静态代理模式,方便理解动态代理。
静态代理模式
首先定义一个接口,声明被代理的类所需要实现的功能
/**
* 买票接口
*/
public interface IBuy {
//买票
void buyTicket();
}
定义被代理类(火车站),实现买票功能:
/**
* 被代理类
*/
public class TrainStation implements IBuy{
private String destination;
public TrainStation(String destination) {
this.destination = destination;
}
@Override
public void buyTicket() {
Log.d("Proxy", "在火车站售票处买到了去" + destination + "的火车票");
}
}
定义代理类,实现同一个接口:
/**
* 票贩子 代理火车站卖票
*/
public class TrainStationProxy implements IBuy{
private String destination;
//持有火车站(相当于能直接访问火车站内部)
private TrainStation trainStation;
public TrainStationProxy(String destination) {
this.destination = destination;
}
@Override
public void buyTicket() {
if(trainStation == null){
trainStation = new TrainStation(destination);
}
//本质还是通过火车站买票
trainStation.buyTicket();
Log.d("Proxy", "通过票贩子买到了去"+ destination + "的火车票");
}
}
客户端调用:
//客户端通过票贩子,间接买到了票
IBuy proxy = new TrainStationProxy("北京");
proxy.buyTicket();
通过上面的例子,可以看出票贩子就是一个火车站的代理,客户端本质上买的票其实还是火车站的,但是是通过票贩子来帮我们代理买票这个动作,以上就是静态代理模式。
为什么要使用动态代理?
静态代理模式实现了我们通过票贩子买票的功能,但票贩子这个代理类需要我们手动编码定义,如果需要代理的对象多了,比如有一百多个需要代理的功能(代理进站、代理托运、代理换乘...等等),那岂不是要建一百多个代理类,这个时候就需要我们的动态代理模式了,动态代理与静态代理最大的区别是,在运行时让虚拟机帮我们生成一个对应的代理类,来调用对应的方法,并且在使用结束后回收,解决了静态代理的局限性。
如何实现动态代理?
动态代理模式就不需要我们定义代理类了,但需要借助InvocationHandler
这个类来实现,主要有如下几个步骤:
1.声明调用处理器类InvocationHandler
2.声明目标对象类的抽象接口
3.声明目标对象类
4.动态生成代理对象,通过代理对象来调用目标对象的方法
1.声明调用处理器类InvocationHandler
/**
* Created by Y on 2019/2/25.
*/
public class DynamicProxy implements InvocationHandler {
private Object proxyObject;
public Object newProxyInstance(Object proxyObject) {
this.proxyObject = proxyObject;
return Proxy.newProxyInstance(proxyObject.getClass().getClassLoader(),
proxyObject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Log.d("Dynamic", "代理对象准备开始调用目标对象方法");
Object result = method.invoke(proxyObject, args);
Log.d("Dynamic", "代理对象调用目标对象方法完毕");
return result;
}
}
可以看到,newProxyInstance
方法中,调用了Proxy.newPorxyInstance
方法,方法参数如下:
Object newProxyInstance(ClassLoader loader, Class>[] interfaces,InvocationHandler h)
这个方法有什么用呢?先说下这几个参数的意义
ClassLoader loader:指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
Class>[] interfaces:指定目标对象的实现接口,即要给目标对象提供一组什么接口。若提供了一组接口给它,那么该代理对象就默认实现了该接口,这样就能调用这组接口中的方法
InvocationHandler h:指定InvocationHandler对象。即动态代理对象在调用方法时,会关联到哪个InvocationHandler对象
可以看到头两个对象传进去了目标类的ClassLoader对象,以及它所声明的接口,到时候程序运行起来就会在Proxy.newPorxyInstance
内部通过反射来生成目标类的实例,并且提供一组接口给它实现,到时候外界需要这些接口的时候才能调用(就类似于刚才举例的Retrofit的ServiceApi.interface)。然后将我们的InvocationHandler对象传进去,代理对象在调用方法时内部就会使用该对象调用invoke(),进而回调回我们的invoke方法。
上面通过newProxyInstance生成代理实例,那invoke自然就是用来代理调用目标方法,method.invoke同样是代理类运用反射调用目标方法并返回结果。
2.声明目标对象类的抽象接口
public interface TestInterface {
void test();
}
3.声明目标对象类
public class TestImpl implements TestInterface{
@Override
public void test() {
Log.d("Android小Y", "调用了目标类的test方法");
}
}
4.通过动态代理对象,调用目标对象的方法
// 1. 创建调用处理器类对象
DynamicProxy dynamicProxy = new DynamicProxy();
// 2. 创建目标对象对象
TestImpl testImpl = new TestImpl();
// 3. 通过调用处理器类对象newProxyInstance创建动态代理类对象
TestInterface proxy = (TestInterface) dynamicProxy.newProxyInstance(testImpl);
//代理调用test,从而调用到了invoke,最后调用到了目标类的test方法
proxy.test();
运行结果:
02-15 15:48:30.099 6578-6578/com.example.test.main D/Android小Y: 代理对象准备开始调用目标对象方法
02-15 15:48:30.101 6578-6578/com.example.test.main D/Android小Y: 调用了目标类的test方法
02-15 15:48:30.101 6578-6578/com.example.test.main D/Android小Y: 代理对象调用目标对象方法完毕
可以看到,动态代理模式下,我们只需要将具体被代理类传给处理器,即可为我们动态生成对应的代理类,不再需要像静态代理那样繁琐。
Retrofit中的动态代理
用过Retrofit网络请求库的朋友都知道,Retrofit的基本用法就是:
1.先定义一个接口:
public interface ServerApi {
@GET("xxxxx/xxx/xxx")
Call getData();
}
2.接着创建Retrofit实例:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://xxx.xxx.com/") //设置网络请求的Url地址
.addConverterFactory(GsonConverterFactory.create()) //设置数据解析器
.build();
3.发起请求
ServerApi request = retrofit.create(ServerApi.class); //创建代理实例
request.getData(); //发送请求
注意第三步骤,表面上,传进去我们接口的类对象即可生成对应的实例并调用getData
,是因为这里面实际上retrofit利用动态代理模式生成了一个代理对象(并且实现了ServiceApi),然后一旦调用了ServiceApi的方法,就会触发代理对象的invoke方法,从而在invoke总拦截并获得了ServiceApi的所有方法以及对应的那些@GET
@POST
等Retrofit的注解信息,然后利用OKhttp完成真正的请求操作。
我们从Retrofit
的create
方法一探究竟:
public T create(final Class service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
可以看到,也是通过Proxy.newProxyInstance
生成了代理类,并new了一个匿名内部InvocationHandler
对象,重写它的invoke
方法,相当于通过我们传进来的接口,生成了一个接口的代理类,并且实现了接口是所有方法(因为InvocationHandler的参数里传进去了new Class>[] { service }),然后返回给外部。
外部一旦调用了接口方法,例如request.getData();
,就会触发InvocationHandler
的invoke
,从而根据注解生成Retrofit的ServiceMethod
和OkHttpCall
对象,这些都是发起网络请求的必备信息,然后最终发起请求。
那么Retrofit为何要这么设计呢?
我们一个应用的请求接口可能有很多个,通过动态代理模式,能动态为每个接口都生成一个具体的代理类,并且实现了我们的接口,我们不需要关心具体请求细节是怎样的,只需要声明我们的接口并传递给Retrofit即可,然后由Retrofit动态生成具体请求对象,发起请求并将结果返回给我们,我想这也就是为何Retrofit这么受欢迎的原因之一。
总结
综上,一般为了在不修改原有类的情况下扩展其功能,并且保护被代理类不被访问,我们可以选择代理模式。动态代理模式是通过动态生成代理类的代理模式,它其实就是将目标类的类加载器和相应的接口传进去代理器(InvocationHandler),通过代理器生成了一份新的”备份“——代理类,这个动态生成的代理类里实现了接口中的所有方法,然后在 InvocationHandler.invoke()
中通过反射机制,调用目标类对象的方法。动态代理运用了很多反射,在性能上会有所影响,所以并不是随处滥用,用得得当会有很好的效果,比如常说的AOP切面编程,实现无侵入式的代码扩展。
GitHub:GitHubZJY
CSDN博客:IT_ZJYANG
简 书:Android小Y