动态代理的使用-功能增强

1,188 阅读8分钟

背景

接手某项目时碰到切换主线程的逻辑, 原项目代码流程如下:

切换主线程时序图.png
  1. xxPresenter 会创建observer直接用于二方库的 SDKService (通常在子线程中回调),记为 innerObserver
  2. xxActivity 也需要创建observer用于主线程回调, 记为 uiObserver
  3. xxPresenter 在收到 innerObserver 的回调后通过主线程handler进行线程切换, 最终触发 uiObserver 的对应方法
  4. 我司业务需求回调后都在 xxActivity 的主线程中执行后续操作, innerObserver 几乎仅用于线程切换而已

举个栗子:

// 假设有个回调接口如下:
interface ICallback {
    fun onCallback(a: Int, b: Boolean, c: String?)
}
object XXPresenter {
    // 存储activity中设置过来的 uiObserver
    var uiObserver: ICallback? = null

    // 创建用于 sdkService, 子线程回调
    private val innerObserver = object : ICallback {
        override fun onCallback(a: Int, b: Boolean, c: String?) {
            MainHandler.sendMessage(Message().also {
                it.what = 101  // 不同的回调方法需要指定不同的what值
                it.data = Bundle().apply {
                    putInt("a", a) // 需要根据回调方法形参顺序手动拼接bundle
                    putBoolean("b", b)
                    putString("c", c)
                }
            })
        }
    }
}
// 切换主线程handler
object MainHandler : Handler(Looper.getMainLooper()) {
    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        when (msg.what) {// 根据what值进行接口区分
            101 -> {
                msg.data?.let { data -> 
                    // 获取activity设置的监听对象,并触发对应的回调方法
                    XXPresenter.uiObserver?.onCallback(
                        data.getInt("a"), // 手动提取实参, key、类型和顺序都不能弄错
                        data.getBoolean("b"),
                        data.getString("c")
                    )
                }
            }
        }
    }
}

存在的问题

  1. 如图第2/3步, 对于同一类型的observer, 需要在 activity , presenter中各实现一次, presenter中会产生大量模板代码
  2. 如图第6步, 收到 SDKService 回调后, presenter需要构建Message, 设置各回调实参, 这完全依赖开发人员手动配置, 效率低下且易发生错误, 灵活度低
  3. 对应的, 第11步通过handler线程切换时, 又需要从 message 中依次还原各实参, 这一步同样依赖开发人员手动处理
  4. observer变化时(如形参列表顺序/类型发生变更), 均需要同步更新 prenter 和 handler
  5. 我司项目最多时, 某个SDKService有将近100个observer需要设置, 部分observer的方法数甚至超过45个, 导致单纯在 Presenter 中创建observer的空白匿名内部类时, 代码就超过100行, 模板代码过多
  6. ...

改造思路

根据已知条件:

  1. 各observer均为接口 interface 类型
  2. presenter 中实现的 innerObserver 仅用于进行线程切换,最终触发UI层创建的observer而已 --> 即:有统一的功能增强逻辑

自然联想到 代理模式 中的动态代理:

代理模式-图侵删,来源于C语言中文网
  1. 创建一个 ThreadSwitcher 辅助类, 可根据传入的 observer 的类型Class,自动生成动态代理类对象,即之前的 innerObserver, 然后作用于sdk中 --> 此步骤可节省prsetner中因 new observer(){} 产生的大量模板代码, 且在observer接口发生变更时, 也不需要修改代码,自动完成适配, 伪代码如下:
    Observer innerOb = ThreadSwitcher.generateInnerObserver(Observer.class)

  2. ThreadSwitcher 类同时透出接口供UI层传入用于主线程的observer, 缓存在 Map<Class,IObserver> 中, 供后续切换主线程时使用

  3. 当下层sdk回调动态代理对象时, 最终都会触发 InvocationHandler#invoke 方法, 其方法签名如下, 我们只需要在其方法体中构造runnable, 按需post到主线程中即可:

// package java.lang.reflect;  
// InvocationHandler.java
/**
 * @param method 接口中被触发的回调方法
 * @param args 方法实参列表
 */
public Object invoke(Object proxy, Method method, Object[] args);
  1. 构造的runnable时, 需查找UI层注入的observer,并触发对应的方法, 而由于 InvocationHandler中已告知我们方法 method 及其实参 args , 因此可直接通过 method.invoke(uiObserver,args) 来触发 uiObserver 的对应方法, 具体代码见下一节

动态代理的使用

import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy

object ThreadSwitcher {
    // ui层注入的observer, 会在主线程中回调
    val uiObserverMap = mutableMapOf<Class<*>, Any>()
    val targetHandler: Handler = Handler(Looper.mainLooper())

    private fun runOnUIThread(runnable: Runnable) {
        // 此处省略切换主线程代码,创建一个mainLooper的handler, post Runnable即可
    }

    // 生成代理类
    fun <O> generateInnerObserver(clz: Class<O>): O? {
        // 固定写法, 传入classLoader 和 待实现的接口列表, 以及核心的 InvocationHandler 的实现, 在其内部进行功能增强
        return Proxy.newProxyInstance(clz.classLoader, arrayOf(clz), object : InvocationHandler {
            override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {

                // 1. 构造runnable, 用于主线程切换
                val runnable = Runnable {
                    
                    // 3. 查找 uiObserver, 若存在则触发
                    uiObserverMap[clz]?.let { uiObserver ->
                        val result = method?.invoke(uiObserver, args)
                        result
                    }
                }

                // 2. 将runnable抛主线程
                runOnUIThread(runnable)

                // 4. 触发method方法得到的返回值, 根据实际类型构造, void时返回null, 此处仅做示意
                return null
            }
        }) as O  // 按需强转为实现的接口类型
    }
}

具体封装实现可参考如下链接:

改造后的流程如下:

改造后的时序图.png

示例代码如下:

object XXPresenter {
    // 构造一个线程切换工具类, uiObserver的注册、缓存及自动切换主线程等操作均通过该对象实现
    // 开发人员无需再关注 mainHandler的消息处理且不会出错
    val threadSwitcher = ThreadSwitcher.newInstance()

    // innerObserver 一句话实现, 接口方法变更时, 无需二次适配
    // 且回调方法被触发时, 自动切换到主线程并回调对应的uiObserver方法,无需手动拼接和解析
    private val innerObserver = threadSwitcher.generateInnerObserverImpl(ICallback::class.java)
}
class XXActivity {

    fun initObserver() {
        // 创建UI层观察者, 这一步不变
        val uiObserver = object : ICallback {
            override fun onCallback(a: Int, b: Boolean, c: String?) {
                // 在ui层主线程执行后续操作
            }
        }

        // 注册到 presenter, 不管有多少种接口, 均通过本方法进行注册-->操作一致
        XXPresenter.threadSwitcher.registerOuterObserver(uiObserver, ICallback::class.java)
    }
}

源码分析

动态代理的实现很简单, 两三行代码就可以搞定, 系统肯定做了很多封装, 把脏活累活给做了, 我们简单看下

从入口方法开始: java.lang.reflect.Proxy#newProxyInstance

// package java.lang.reflect;
// Proxy.java  基于api 29
private static final Class<?>[] constructorParams = { InvocationHandler.class };

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
    final Class<?>[] intfs = interfaces.clone();

    // 从缓存中查找已生成过的class类型,若不存在则进行生成
    Class<?> cl = getProxyClass0(loader, intfs);

    // 反射调用构造方法 Proxy(InvocationHandler), 创建并返回实例
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    if (!Modifier.isPublic(cl.getModifiers())) {
        cons.setAccessible(true);
    }
    return cons.newInstance(new Object[]{h});
}

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

/**
 * 创建代理类class
 */
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
    // 接口方法数限制
    if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); }

    // 优先从缓存中获取已创建过的代理类, 若不存在, 则创建
    return proxyClassCache.get(loader, interfaces);
}

关键的 proxyClassCache 是个二级缓存类(WeakCache), 通过调用其 get 方法得到最终的实现类, 其构造方法签名如下:

// package java.lang.reflect;
// WeakCache.java
/**
    * Construct an instance of {@code WeakCache}
    *
    * @param subKeyFactory a function mapping a pair of
    *                      {@code (key, parameter) -> sub-key}
    * @param valueFactory  a function mapping a pair of
    *                      {@code (key, parameter) -> value}
    * @throws NullPointerException if {@code subKeyFactory} or
    *                              {@code valueFactory} is null.
    */
public WeakCache(BiFunction<K, P, ?> subKeyFactory, BiFunction<K, P, V> valueFactory) {

通过参数名也可以猜到最终是通过 valueFactory 生成的, 我们回到 Proxy 类看下:

// package java.lang.reflect;
// Proxy.java
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> 
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

/**
 * A factory function that generates, defines and returns the proxy class given
 * the ClassLoader and array of interfaces.
 */
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // 所有动态代理类名的前缀
    private static final String proxyClassNamePrefix = "$Proxy";

    // 每一个动态代理类类名中唯一的数字,可猜测最终是分层的代理类名就是: $Proxy+数字
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        // 省略部分代码: 对传入的接口数组进行一些校验

        String proxyPkg = null; // 最终实现类所在的包路径
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL; // 生成的代理类默认访问权限是: public final

        // 对接口数组校验: 若待实现的接口是非public的, 则最终实现的代理类也是非public的,并且非public的接口需要在同一个包下
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        // 若待实现的接口均为 public, 则使用默认的包路径
        if (proxyPkg == null) { proxyPkg = ""; }

        {
            List<Method> methods = getMethods(interfaces); // 递归获取所有接口(包括其父接口)的方法,并手动添加了 equals/hashCode/toString 三个方法
            Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE); // 对所有接口方法排序
            validateReturnTypes(methods); // 校验接口方法: 确保同名方法得返回类型一致
            List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods); // 去除重复的方法,并获取每个方法对应的异常值信息

            Method[] methodsArray = methods.toArray(new Method[methods.size()]);
            Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);

            long num = nextUniqueNumber.getAndIncrement(); // 生成当前代理实现类的数字信息
            String proxyName = proxyPkg + proxyClassNamePrefix + num; // 拼接生成代理类名,默认为: $Proxy+数字

            return generateProxy(proxyName, interfaces, loader, methodsArray, exceptionsArray); // 通过native方法生成代理类Class
        }
    }

    @FastNative
    private static native Class<?> generateProxy(String name, Class<?>[] interfaces, ClassLoader loader, Method[] methods, Class<?>[][] exceptions);

    /**
     * 根据传入的接口class信息,获取所有的接口方法,并额外添加 equals/hashCode/toString 三个方法
     */
    private static List<Method> getMethods(Class<?>[] interfaces) {
        List<Method> result = new ArrayList<Method>();
        try {
            result.add(Object.class.getMethod("equals", Object.class));
            result.add(Object.class.getMethod("hashCode", EmptyArray.CLASS));
            result.add(Object.class.getMethod("toString", EmptyArray.CLASS));
        } catch (NoSuchMethodException e) {
            throw new AssertionError();
        }

        getMethodsRecursive(interfaces, result); // 通过递归反射的方式一次获取接口所有的方法
        return result;
    }
}

动态代理生成的类长啥样?

上面我们简单分析了下动态代理的源码, 我们可以知道/推测得到以下信息:

  1. 生成的代理类叫做 $ProxyN 其中 N 是一个数字,随代理类的增加而递增
  2. $ProxyN 实现了所有接口方法,并自动添加了 equals/hashCode/toString 三个方法,因此: --> a. 动态代理生成类应可以强转为任何传入的接口类型 --> b. 额外增加的三个方法通常会影响对象的比较,需要手动赋值区分
  3. 触发动态代理类的方法最终都会回调 InvocationHandler#invoke 方法,而 InvocationHandler 是通过 Proxy#newProxyInstance 传入的,因此: --> 猜测生成 $ProxyN 类应是继承自 Proxy

猜测归猜测, 最好能导出生成的 $ProxyN 看下实际代码:

  1. 网上查到的通常是使用 JVM 提供的 sun.misc.ProxyGenerator 类, 但这个类在android中不存在,手动拷贝对应jar包到android中使用也有问题
  2. 尝试使用字节码操作库或者 Class#getResourceAsStream 等方式也失败了, 终究是JVM上的工具, 在android虚拟机上无法直接使用
  3. 最终退而求其次, 先通过反射获取 $ProxyN 的类结构, 至于方法的调用则通过 InvocationHandler#invoke 方法中打印堆栈来查看
// 1. 自定义接口如下
package org.lynxz.utils.observer
interface ICallback {
    fun onCallback(a: Int, b: Boolean, c: String?)
}

// 2. 通过反射获取类结构
package org.lynxz.utils.reflect.ReflectUtilTest
@Test
fun oriProxyTest() {
    val proxyObj = Proxy.newProxyInstance(
        javaClass.classLoader,
        arrayOf(ICallback::class.java)
    ) { proxy, method, args -> // InvocationHandler#invoke 方法体
        RuntimeException("===> 调用堆栈:${method?.name}").printStackTrace() // 3. 打印调用堆栈信息
        args?.forEachIndexed { index, any -> // 4. 打印方法得实参
            LoggerUtil.w(TAG, "===> 方法参数: $index - $any")
        }
        ReflectUtil.generateDefaultTypeValue(method!!.returnType) // 根据方法返回类型生成对应数据
    }

    // ProxyGeneratorImpl 是自定义的通过反射获取类结构的实现类, 具体代码请查看上面给出的github仓库
    LoggerUtil.w(TAG, "===>类结构:\n${ProxyGeneratorImpl(proxyObj.javaClass).generate()}")
    if (proxyObj is ICallback) { // 强转生成的动态代理类为自定义的接口
        proxyObj.onCallback(1, true, "hello") // 触发接口方法,以便触发 InvocationHandler#invoke 方法, 进而打印堆栈
    }
}

最终得到日志如下, 验证了之前的猜测:

// ===>类结构:
public final class $Proxy6 extends java.lang.reflect.Proxy implements ICallback{
    public static final Class[] NFC;
    public static final Class[][] NFD;
    public $Proxy6(Class){...}
    public final boolean equals(Object){...} // 方法体的内容不可知, 此处用省略号替代
    public final int hashCode(){...}
    public final String toString(){...}
    public final void onCallback(int,boolean,String){...}
}

// 调用堆栈:
===> 调用堆栈:onCallback 
at org.lynxz.utils.reflect.ReflectUtilTest$oriProxyTest$proxyObj$1.invok(ReflectUtilTest.kt:86) // 对应上方代码: RuntimeException("===> 调用堆栈:${method?.name}").printStackTrace()
at java.lang.reflect.Proxy.invoke(Proxy.java:913) // 触发 Proxy#invoke 方法, 其内部直接触发 InvocationHandler#invoke 方法
at $Proxy6.onCallback(Unknown Source) // 对应上方代码: proxyObj.onCallback(1, true, "hello")

// 打印方法实参数据, 序号 - 值, 与我们传入的相同
===> 方法参数: 0 - 1
===> 方法参数: 1 - true
===> 方法参数: 2 - hello

Proxy#invoke 源码, 就是简单的触发 InvocationHandler#invoke 而已

// package java.lang.reflect;
// Proxy.java
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h;
}

// 直接触发 invocationHandler 方法
// 而 InvocationHandler 是通过 Proxy#newProxyInstance 传入的, 最终传到 $Proxy6 的构造方法
private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {
    InvocationHandler h = proxy.h;  // 此处的proxy就是上面动态代理生成 `$Proxy6` 类 
    return h.invoke(proxy, method, args);
}

小结

  1. 上面介绍的动态代理是基于接口形式的代理, 是JVM内置提供的机制, android也支持, 至于普通类的动态代理, 需要使用字节码操作库等库进行支持
  2. 对于不关注具体实现的场景, 可使用动态代理直接生成接口实现类对象
  3. 对于需要进行功能增强的场景, 也可使用动态代理实现, 聚焦于 InvocationHandler 的即可
  4. 系统会对生成的动态代理class进行缓存, 并非每次都创建,具体性能比较请参考其他文章
  5. 相关源码: