动态代理
前言
HI,欢迎来到裴智飞的《每周一博》。今天是十二月第三周,我给大家介绍一下动态代理的知识,为什么要介绍这些呢,因为插件化技术同样需要它。
一. 代理模式和静态代理
代理模式是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用,通俗的来讲代理模式就是我们生活中常见的中介。下面介绍下代理模式的主要作用;
A. 中介隔离作用
在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
B. 符合开闭原则,增加类的功能
代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。比如想在业务功能执行的前后加入一些公共的服务,加入缓存和日志这些功能,就可以使用代理类来完成,而没必要修改已经封装好的委托类。
代理模式主要有静态代理和动态代理两种,静态代理是先生成源代码,再对其编译,在程序运行之前,代理类class文件就已经被创建了,动态代理是在程序运行时通过反射机制动态创建的。下面我们来看下静态代理的实现;
- 定义抽象主题Subject
public interface Subject {
public void request();
}
- 定义具体主题,也就是被代理的对象
public class RealSubject implements Subject {
public void request() {
}
}
- 定义代理对象,也就是中介
public class Proxy implements Subject {
// 要代理哪个实现类
private Subject subject = null;
// 通过构造函数传递代理者
public Proxy(Subject subject){
this.subject = subject;
}
public void request() {
this.before();
this.subject.request();
this.after();
}
// 预处理
private void before(){
//do something
}
// 善后处理
private void after(){
//do something
}
}
这里的before和after不是必须的,只是演示装饰代理方法的一个例子。
- 使用代理
Subject subject = new RealSubject();
Subject proxy = new Proxy(subject);
proxy. request();
这里我们可以把Subject理解为房东,Proxy理解为中介,调用者是买房的人,中介代理了许多个房东,每一个房东都是一个RealSubject,都需要实现Subject。
静态代理优缺点总结:
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展;
缺点:我们得为每一个服务都得创建代理类,另外接口一旦发生改变,代理类也得相应修改;
二. 动态代理
动态代理是指利用Java的反射技术,在运行时创建一个实现给定接口的新类及其对象。在动态代理中不需要手动的创建代理类,只需要编写一个动态处理器就可以了,真正的代理对象由JDK运行时动态的创建。
我们来看下动态代理的具体实现
- 编写动态处理器:
实现InvocationHandler,接口里有一个invoke方法,在这里去实现需求,比如拦截方法,装饰方法等。invoke方法有三个参数;
A. 动态代理类的引用,可以通过getClass方法得到代理类的一些信息;
B. 方法对象的引用,代表被动态代理类调用的方法,从中可得到方法名,参数类型,返回类型等;
C. args对象数组,代表被调用方法的参数,基本类型int会被装箱成对象类型Interger;
public class DynamicProxyHandler implements InvocationHandler {
private Object object;
public DynamicProxyHandler(final Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("预处理");
Object result = method.invoke(object, args);
System.out.println("善后处理");
return result;
}
}
- 使用动态代理:
这里主要使用了Proxy.newProxyInstance这个方法,它接受三个参数;
A. ClassLoader loader:目标对象使用的类加载器;
B. Class<?>[] interfaces:指定目标对象实现的接口的类型;
C. InvocationHandler:动态处理器,执行目标对象的方法时会触发事件处理器的invoke方法;
Subject subject = new RealSubject();
Subject proxy = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(),
new Class[]{Subject.class},
new DynamicProxyHandler(subject));
proxy. request();
一般情况下这三个参数都是同一个接口类的ClassLoader,Interfaces,具体代理对象,动态代理代理的是接口,不是类,也不是抽象类。
前面的静态代理中我们只代理了request这一个方法,假如需要代理10个方法,每个方法里我们都要加入统计功能,那么相当于before和after要写10遍,动态代理就可以解决这个问题,我们只需要在invoke前加入befor,后加入after就可以了,后面我会写一个具体的例子来描述这一点。
其实动态代理与静态代理的本质一样,都需要生成一个与被代理类实现了同样接口的实现类对象,只不过动态代理支持运行时指定这种实现方式而已。
三. 动态代理原理
动态代理的基础是反射,其实这些方法的增加是交给JVM去做了,当我们调用Proxy.newProxyInstance时,JVM会为每一个代理对象生成"$Proxy0"的类,后面的数字是递增的,这个类经过反编译后可以看到具体内容;
public class Proxy0 extends Proxy implements Subject {
// 第一步, 生成构造器
protected Proxy0(InvocationHandler h) {
super(h);
}
// 第二步, 生成静态域
private static Method m1; // hashCode方法
private static Method m2; // equals方法
private static Method m3; // toString方法
private static Method m4; // ...
// 第三步, 生成代理方法
@Override
public int hashCode() {
try {
return (int) h.invoke(this, m1, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public boolean equals(Object obj) {
try {
Object[] args = new Object[] {obj};
return (boolean) h.invoke(this, m2, args);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public String toString() {
try {
return (String) h.invoke(this, m3, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void request() {
try {
// 构造参数数组, 如果有多个参数往后面添加就行了
Object[] args = new Object[] {};
h.invoke(this, m4, args);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
// 第四步, 生成静态初始化方法
static {
try {
Class c1 = Class.forName(Object.class.getName());
Class c2 = Class.forName(Subject.class.getName());
m1 = c1.getMethod("hashCode", null);
m2 = c1.getMethod("equals", new Class[]{Object.class});
m3 = c1.getMethod("toString", null);
m4 = c2.getMethod("request", new Class[]{Subject.class});
//...
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里我们可以看到在代理类的构造函数里传入了InvocationHandler,然后生成Object的几个方法和Subject接口里的方法,当调用指定的方法时会调用InvocationHandler的对应方法,从这个角度来看,它可以拦截任何方法。
另外还可以看出:
A. 代理类默认继承Porxy类,因为Java中只支持单继承,所以JDK动态代理只能去实现接口;
B. 代理方法都会去调用InvocationHandler的invoke方法,因此我们需要重写InvocationHandler的invoke方法;
C. 调用invoke方法时会传入代理实例本身,目标方法和目标方法参数,这解释了invoke方法的参数是怎样来的;
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
四. 拦截方法
前一篇我也说过反射是不能拦截方法的,动态代理可以做到,而且插件化的hook技术就会用到,我们来看一下如何拦截IActivityManagerHandler的方法。
安卓源码的东西就不去读了,我们想拦截IActivityManager对象,在安卓O上,ActivityManager里的IActivityManager实例叫IActivityManagerSingleton,它是一个单例对象,需要从Singleton类里取出mInstance,这里我们先用反射获得该对象,然后动态代理它。
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManager");
Object iActivityManagerSingleton = ReflectUtils.getValue(activityManagerNativeClass, "iActivityManagerSingleton", null);
Class<?> singleton = Class.forName("android.util.Singleton");
// 这个iActivityManagerSingleton是一个Singleton类型的,我们需要从Singleton中再取出这个单例的AMS代理
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// ams的代理对象
Object rawIActivityManager = mInstanceField.get(iActivityManagerSingleton);
// 创建一个这个对象的代理对象, 然后替换这个字段
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class<?>[]{iActivityManagerInterface},
new IActivityManagerHandler(rawIActivityManager));
mInstanceField.set(iActivityManagerSingleton, proxy);
编写具体的IActivityManagerHandler对象;
public class IActivityManagerHandler implements InvocationHandler {
Object mBase;
public IActivityManagerHandler(Object base) {
mBase = base;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e("gzq", "invoke拦截了" + method.getName());
if ("startActivity".equals(method.getName())) {
Log.e("gzq", "startActivity方法拦截了");
return method.invoke(mBase, args);
}
return method.invoke(mBase, args);
}
}
我们把这段代码放在Application的attachBaseContext里面,然后点击跳转Activity就会发现打印出 "invoke拦截了XXX",很多方法都会被拦截到。
五. 动态代理的用途
- 实现AOP:面向切面编程
先来简单看下面向切面编程的思想,传统程序的流程比如银行系统会有一个取款流程,还有一个查询余额流程,我们把这两个流程放到一起,它们都有一个相同的验证流程。
有没有想过可以把这个验证用户的代码是提取出来,不放到主流程里去呢,有了AOP写代码时就不需要把这个验证用户步骤写进去,框架会自动把代码加过去。这样写代码的时候,只需考虑主流程,而不用考虑那些不重要的流程,比如把效验参数,加log,统计方法耗时这些全部都做成一个个面,所以AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面。
这里我们以统计方法耗时和增加log为例,写一个通过动态代理实现AOP的代码,传统方法就是方法前后分别计时,然后做差,有多少个方法写多少遍,通过动态代理可以一次做到。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method:"+ method.getName());
long start = System.currentTimeMillis();
Object res = method.invoke(obj, args);
System.out.println("耗时:" + (System.currentTimeMillis() - start));
return res;
}
我们在InvocationHandler里面代理对象,在invoke里面对方法进行润饰,增加log和统计,这样代理对象的所有方法都会有该功能。
-
实现设计模式,比如装饰者模式
装饰者模式就是在不改变原有类的基础上实现对方法的修改,从上面的例子很明显可以看出来动态代理拦截了方法,自然可以在方法前后进行装饰,比起静态的装饰模式要省不少事; -
通过配置实现框架
比如我们可以自定义注解,然后在invoke里面去取注解,如果method.getAnnotation是我们定义的注解,就可以做一些处理。
六. 总结
本文介绍了动态代理的一些基本知识,这也是通过Hook方式实现插件化的基础,后面我会介绍插件化和热更新的一些知识,感谢大家的阅读,我们下周再见。