动态代理是 Hook 技术的基础技能,下一篇暂定 activity 的启动流程,这篇先来搞一下这个代理设计模式吧。
定义
先上定义。
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
说实话我也看不太懂,但是说到代理,基本上往中介方向理解就差不多。
房产中介,就是为租客提供一种服务,以帮助租客租到心仪的房子。一个租客不可能直接接触到租房市场上所有的房源,而中介可以在租客和房子中间起到协调的作用。
哈哈,我可真他娘的是个人才,反正大概就这么个意思吧!
静态代理
下边正经点儿,代理模式就是客户端不直接操作对象,而是由一个中间者(即代理),来对目标对象进行操作,从而间接地操纵或者增强目标对象。UML 类图我还没学会,直接上代码吧!
public interface IRentHouse{
void rentHouse();
}
public class Couple implements IRentHouse{
public void rentHouse(){
System.out.println("新婚快乐!但是押一付三再加服务费好多钱,自如黑中介!");
}
}
public class RentAgent implements IRentHouse{
private IRentHouse mClient;
public RentAgent(IRentHouse client){
mClient = client;
}
public void rentHouse(){
System.out.println("联系房东,商谈价格");
if(mClient != null){
mClient.rentHouse();
}
System.out.println("双周保洁,家电保修");
}
}
如上,客户不需要关心租房前后一系列繁琐的事,把这一切都委托给代理去做,自己只管付钱。在这个事例中,RentAgent
即为 Couple
的代理类,帮助 Couple
处理租房事宜,客户端即通过 RentAgent
间接操纵了 Couple
的动作。
具体调用以及输入结果:
public static void main(String[] args) {
Couple couple = new Couple();
RentAgent rentAgent = new RentAgent(couple);
rentAgent.rentHouse();
}
联系房东,商谈价格
新婚快乐!但是押一付三再加服务费好多钱,自如黑中介!
双周保洁,家电保修
以上就是静态代理。
这种模式很简单,也很清晰,但是缺点也很明显,即代理类和主体类都需要实现接口,而且接口方法一旦发生变动,同时这一动作并非双方都需要的(比如说需要为房产中介提供一个和房东协商价格的方法,这个动作本身和租客并没有关系),那么代码可维护性会变得非常差。
动态代理
在这种情况下,我们就需要了解一下动态代理。动态代理又称为 JDK 代理或者接口代理,其主要是利用 JDK API,从内存层面在运行时为目标接口动态地创建对象,从而实现对目标对象的代理功能,而不受接口方法变动的影响。
虽然思路大致相同,但是动态代理与静态代理在实现上有本质的区别。静态代理需要显式地写出代理类,委托类,接口等,开发者需要在编译期就手动编码实现代理模式;而动态代理省略了创建代理类的过程,把这个工作交给 JDK 在运行时处理。很明显,这样的设计使得 JDK 代理要求目标对象必须实现接口,否则无法使用动态代理。
废话不多说,还是直接看代码。
动态代理的实现主要依靠几个类:
-
java.lang.reflect.Proxy
// Proxy 类静态方法,返回一个指定接口的代理类实例 // 方法传入 InvocationHandler 对象,代理类会拦截所有的执行方法,并通过该处理器自行处理 Object newProxyInstance(ClassLoader loader, // 指定当前目标对象使用类加载器 Class>[] interfaces, // 目标对象实现的接口的类型 InvocationHandler h // 自定义方法处理器 ){}
-
java.lang.reflect.InvocationHandler
// 代理类会拦截所有的方法,并经由此方法重新调用 // 代理类即可以在此进行自己的处理 Object invoke(Object proxy, Method method, Object[] args){}
下面我们使用动态代理机制,来实现一下我们之前的租房案例。
class RentInvocationHandler implements InvocationHandler {
private Object mClient;
public RentInvocationHandler(Object client) {
this.mClient = client;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("联系房东,商谈价格");
Object invoke = method.invoke(mClient, args);
System.out.println("双周保洁,家电保修");
return invoke;
}
}
具体调用:
Couple couple = new Couple();
// 使用动态代理,办理租房业务
IRentHouse rentProxy =(IRentHouse)Proxy.newProxyInstance(
couple.getClass().getClassLoader(),
couple.getClass().getInterfaces(),
new RentInvocationHandler(couple));
rentProxy.rentHouse(); // 代理执行方法
输入结果:
联系房东,商谈价格
新婚快乐!但是押一付三再加服务费好多钱,自如黑中介!
双周保洁,家电保修
具体使用并不复杂,就不再多说,但是关于动态代理生成的代理类本身也有一些特点,下面大概罗列一下:
-
类修饰符
该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承
-
类名
格式是 “$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是 Proxy 内部对动态代理类做了缓存,如果以前生成过相应的对象,则会直接返回该对象而不是重新创建
-
继承关系
$Proxy0 extends Proxy implements InterfaceA, InterfaceB, InterfaceX
动态代理避免了静态代理代码维护的缺点,动态生成代理类,较为灵活,但是缺点也比较明显:因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),只能针对接口创建代理类,不能针对类创建代理类。
Cglib 代理
这个东西就很厉害了。前文说了,动态代理的缺点是要求目标对象必须实现接口,否则就无法实现动态代理。Cglib 就是为了解决这个问题出现的,使用cglib代理的对象则无需实现接口,达到代理类无侵入。
Cglib 在 Android 使用的不多,后端的同学可能更熟悉一些,Spring 框架就集成了这个库。
使用cglib需要引入cglib的jar包,具体使用如下:
public class ProxyFactory implements MethodInterceptor{
private Object target; //维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}
//为目标对象生成代理对象
public Object getProxyInstance() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类对象代理
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事务");
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("关闭事务");
return returnValue;
}
}
具体和动态代理大体相同,就不再赘述了。
代理模式 vs 装饰模式
熟悉装饰者模式的同学可能会有点儿头疼,因为代理模式感觉和装饰者模式一毛一样……反正都是定义个接口,包裹一下委托类,然后操作一下代理类,实际操作了委托类嘛!
二者确实比较相似,从 UML 类图看都没有任何区别,代理类/装饰类与委托类都继承自同一个接口或者抽象类,然后代理类/装饰类包装委托类。
-
装饰模式:能动态的新增或组合对象的行为
在不改变接口的前提下,动态扩展对象的功能
-
代理模式:为其他对象提供一种代理以控制对这个对象的访问
在不改变接口的前提下,控制对象的访问
装饰模式是“新增行为”,而代理模式是“控制访问”。关键就是我们如何判断是“新增行为”还是“控制访问”。
具体举例来说,
网上很多封装了带上拉刷新下拉加载的 RecyclerView,实现方式就是装饰模式,一般都是定义一个 Wrapper 来包裹原 adapter 以及原 RecyclerView,在此基础上新增了「下拉刷新」、「上拉加载」的行为;
-
Retrofit 的 create 方法就是动态代理的应用,不对接口做出任何新增行为,只是通过动态代理创建接口对象,控制对接口的访问;
public
T create(final Class service) { return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class>[] { service }, new InvocationHandler() {}}); }
参考文献
代理模式及Java实现动态代理
Java 动态代理机制分析及扩展
Java代理(jdk静态代理、动态代理和cglib动态代理)
Java三种代理模式:静态代理、动态代理和cglib代理
Java的三种代理模式