每周一博

动态代理

2018-12-12  本文已影响10人  健身营养爱好者

前言

HI,欢迎来到裴智飞的《每周一博》。今天是十二月第三周,我给大家介绍一下动态代理的知识,为什么要介绍这些呢,因为插件化技术同样需要它。

一. 代理模式和静态代理

代理模式是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用,通俗的来讲代理模式就是我们生活中常见的中介。下面介绍下代理模式的主要作用;

A. 中介隔离作用
在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

B. 符合开闭原则,增加类的功能
代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。比如想在业务功能执行的前后加入一些公共的服务,加入缓存和日志这些功能,就可以使用代理类来完成,而没必要修改已经封装好的委托类。

代理模式主要有静态代理和动态代理两种,静态代理是先生成源代码,再对其编译,在程序运行之前,代理类class文件就已经被创建了,动态代理是在程序运行时通过反射机制动态创建的。下面我们来看下静态代理的实现;

  1. 定义抽象主题Subject
public interface Subject {
    public void request();
}
  1. 定义具体主题,也就是被代理的对象
public class RealSubject implements Subject {
    public void request() {
    }
}
  1. 定义代理对象,也就是中介
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不是必须的,只是演示装饰代理方法的一个例子。

  1. 使用代理
        Subject subject = new RealSubject();
        Subject proxy = new Proxy(subject);
        proxy. request();

这里我们可以把Subject理解为房东,Proxy理解为中介,调用者是买房的人,中介代理了许多个房东,每一个房东都是一个RealSubject,都需要实现Subject。

静态代理优缺点总结:
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展;
缺点:我们得为每一个服务都得创建代理类,另外接口一旦发生改变,代理类也得相应修改;

二. 动态代理

动态代理是指利用Java的反射技术,在运行时创建一个实现给定接口的新类及其对象。在动态代理中不需要手动的创建代理类,只需要编写一个动态处理器就可以了,真正的代理对象由JDK运行时动态的创建。

我们来看下动态代理的具体实现

  1. 编写动态处理器:
    实现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;
    }
}
  1. 使用动态代理:
    这里主要使用了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",很多方法都会被拦截到。

五. 动态代理的用途

  1. 实现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和统计,这样代理对象的所有方法都会有该功能。

  1. 实现设计模式,比如装饰者模式
    装饰者模式就是在不改变原有类的基础上实现对方法的修改,从上面的例子很明显可以看出来动态代理拦截了方法,自然可以在方法前后进行装饰,比起静态的装饰模式要省不少事;

  2. 通过配置实现框架
    比如我们可以自定义注解,然后在invoke里面去取注解,如果method.getAnnotation是我们定义的注解,就可以做一些处理。

六. 总结

本文介绍了动态代理的一些基本知识,这也是通过Hook方式实现插件化的基础,后面我会介绍插件化和热更新的一些知识,感谢大家的阅读,我们下周再见。

上一篇下一篇

猜你喜欢

热点阅读