不一样的视角来学习Spring源码之AOP---中

x33g5p2x  于2022-03-29 转载在 Spring  
字(10.6k)|赞(0)|评价(0)|浏览(363)

系列文章:

不一样的视角来学习Spring源码之容器与Bean—上

不一样的视角来学习Spring源码之容器与Bean—下

不一样的视角来学习Spring源码之AOP—上

cglib 代理进阶

模拟 cglib 代理

cglib的思路和jdk的差不多,大家首先一定要看过上,再来看下,下面我们通过一张图给大家回顾一下两者的相似之处和区别:

JDK的动态代理使用InvocationHandler作为拦截器,拦截所有代理方法的执行,从而可以通过InvocationHandler的invoke方法完成逻辑织入功能

本节的标题是模拟cglib动态代理,我们这里模拟的是cglib生成的$Proxy0代理类的样子

  • 被代理对象
public class Target {
    public void save() {
        System.out.println("save()");
    }

    public void save(int i) {
        System.out.println("save(int)");
    }

    public void save(long j) {
        System.out.println("save(long)");
    }
}
  • $Proxy0代理类
public class Proxy extends Target {

    private MethodInterceptor methodInterceptor;

    public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    static Method save0;
    static Method save1;
    static Method save2;
    static MethodProxy save0Proxy;
    static MethodProxy save1Proxy;
    static MethodProxy save2Proxy;
    static {
        try {
            save0 = Target.class.getMethod("save");
            save1 = Target.class.getMethod("save", int.class);
            save2 = Target.class.getMethod("save", long.class);
            save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");
            save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");
            save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 带原始功能的方法
    public void saveSuper() {
        super.save();
    }
    public void saveSuper(int i) {
        super.save(i);
    }
    public void saveSuper(long j) {
        super.save(j);
    }
    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 带增强功能的方法
    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(long j) {
        try {
            methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

MethodProxy这里的功能主要是避免方法的反射调用,这里大家先知道即可,下面会详细解释cglib是如何实现的

  • 测试类
public class A13 {

    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        Target target = new Target();
        proxy.setMethodInterceptor(new MethodInterceptor() {
            @Override
            public Object intercept(Object p, Method method, Object[] args,
                                    MethodProxy methodProxy) throws Throwable {
                System.out.println("before...");
//                return method.invoke(target, args); // 反射调用
                // FastClass
//                return methodProxy.invoke(target, args); // 内部无反射, 结合目标用
                return methodProxy.invokeSuper(p, args); // 内部无反射, 结合代理用
            }
        });

        proxy.save();
        proxy.save(1);
        proxy.save(2L);
    }
}

收获💡

和 jdk 动态代理原理查不多

  1. 回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor

  2. 调用目标时有所改进,见下面代码片段

  3. method.invoke 是反射调用,必须调用到足够次数才会进行优化

  4. methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)

  5. methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象

public class A14Application {
    public static void main(String[] args) throws InvocationTargetException {

        Target target = new Target();
        Proxy proxy = new Proxy();
        
        proxy.setCallbacks(new Callback[]{(MethodInterceptor) (p, m, a, mp) -> {
            System.out.println("proxy before..." + mp.getSignature());
            // ⬇️调用目标方法(三种)
//            Object result = m.invoke(target, a);  // ⬅️反射调用
//            Object result = mp.invoke(target, a); // ⬅️非反射调用, 结合目标用
            Object result = mp.invokeSuper(p, a);   // ⬅️非反射调用, 结合代理用
            System.out.println("proxy after..." + mp.getSignature());
            return result;
        }});
        
        // ⬇️调用代理方法
        proxy.save();
    }
}

注意

  • 调用 Object 的方法, 后两种在 jdk >= 9 时都有问题, 需要 --add-opens java.base/java.lang=ALL-UNNAMED

cglib 避免反射调用

上面讲到了,通过MethodProxy,cglib可以避免通过反射调用目标对象里面的方法,那么具体是如何做到的呢?

注意看这一行代码:

//这里()V是方法描述符号,使用的是底层jvm表示方法
        save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");

看一下create方法的具体实现;

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        MethodProxy proxy = new MethodProxy();
        //创建了两个方法签名---这里方法签名包裹了两个信息,为被代理对象原始方法名--save和方法描述符号---》()V
        //name1是为了方便通过目标对象直接调用,避免反射调用
        proxy.sig1 = new Signature(name1, desc);
        //此方法前面的name2指的是---saveSuper---主要是为了代理对象调用该方法,从而避免反射---该方法代码回顾一下:
        proxy.sig2 = new Signature(name2, desc);
        proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);
        return proxy;
    }

回顾一下saveSuper方法:

public void saveSuper() {
    //父类不就是调用目标对象的方法吗,这样就不需要反射调用了
        super.save();
    }

createInfo方法干了啥子?

//c1是目标对象class,c2是代理对象class
        proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);

主要关注c1和c2就可以了: 分别封装了目标对象和代理对象的class类型

private static class CreateInfo {
        Class c1;
        Class c2;
        NamingPolicy namingPolicy;
        GeneratorStrategy strategy;
        boolean attemptLoad;

        public CreateInfo(Class c1, Class c2) {
            this.c1 = c1;
            this.c2 = c2;
            AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
            if (fromEnhancer != null) {
                this.namingPolicy = fromEnhancer.getNamingPolicy();
                this.strategy = fromEnhancer.getStrategy();
                this.attemptLoad = fromEnhancer.getAttemptLoad();
            }

        }
    }

回顾上面cglib的测试代码:

MethodInterceptor:

public interface MethodInterceptor extends Callback {
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
//上面说过,通过MethodInterceptor 的invoke和invokesuper方法调用可以避免对方法的反射调用,那么如何实现的呢?
            
            //传入的是目标对象和方法参数---猜测和上面的-----》proxy.sig1 = new Signature(save, desc);有关
            Object result = mp.invoke(target, a); // ⬅️非反射调用, 结合目标用
            //传入的是代理对象和方法参数----猜测和上面的----》proxy.sig1 = new Signature(saveSuper, desc);有关
            Object result = mp.invokeSuper(p, a);   // ⬅️非反射调用, 结合代理用

来看看MethodProxy的invoke方法和invokeSuper方法源码:

public Object invoke(Object obj, Object[] args) throws Throwable {
        try {
            //这里的init方法很重要
            this.init();
            //FastClassInfo封装的是啥?
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            //f1.invoke又是啥?
            return fci.f1.invoke(fci.i1, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        } catch (IllegalArgumentException var5) {
            if (this.fastClassInfo.i1 < 0) {
                throw new IllegalArgumentException("Protected method: " + this.sig1);
            } else {
                throw var5;
            }
        }
    }

    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
        //init方法干了(O_o)??
            this.init();
            //同样的疑问?
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }

问题很多,我们一个个解决,先来看一下FastClassInfo是什么:

private static class FastClassInfo {
    //这里解释一下: FastClass是cglib为目标对象调用反射避免反射创建和代理对象调用父类方法避免反射创建而规定的两个代理类
        FastClass f1;
        FastClass f2;
        int i1;
        int i2;

        private FastClassInfo() {
        }
    }

第一个疑惑揭开,下面看一下init方法的实现:

private void init() {
    //如果两个代理类已经生成好了,那么就直接返回
    //这里使用了单例模式的双重锁检测机制
        if (this.fastClassInfo == null) {
            synchronized(this.initLock) {
                if (this.fastClassInfo == null) {
                //CreateInfo包裹的是目标对象和代理对象的class类型
                    MethodProxy.CreateInfo ci = this.createInfo;
                    //包裹的是为目标对象和代理对象又分别生成的代理对象
                    MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                    //根据目标对象的class类型,生成其的代理对象(不要搞混,这个其实不算正统的代理对象,主要是为了避免反射调用方法,下面会讲具体实现)
                    fci.f1 = helper(ci, ci.c1);
                    //根据代理对象class类型,生成其代理对象
                    fci.f2 = helper(ci, ci.c2);
                    //如果是目标对象,那么对于目标对象来说,当前方法对应的索引
                    fci.i1 = fci.f1.getIndex(this.sig1);
                    //对应于代理对象来说,当前方法的索引
                    fci.i2 = fci.f2.getIndex(this.sig2);
                    this.fastClassInfo = fci;
                    this.createInfo = null;
                }
            }
        }

    }

helper方法源码:主要就是生成FastClass代理类

private static FastClass helper(MethodProxy.CreateInfo ci, Class type) {
        Generator g = new Generator();
        g.setType(type);
        g.setContextClass(type);
        g.setClassLoader(ci.c2.getClassLoader());
        g.setNamingPolicy(ci.namingPolicy);
        g.setStrategy(ci.strategy);
        g.setAttemptLoad(ci.attemptLoad);
        return g.create();
    }

init方法就是为了生成FastClass代理类

回到invoke方法:

//生成了FastClass代理类
            this.init();
           //拿到了当前方法对象的两个代理类
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            //执行对目标对象再进行包装的代理类的invoke方法--也就是说该方法是直接调用的方法,而不是反射调用的方法
             //注意这里第一个参数传入的是当前被调用方法的索引值,第二个参数是目标对象,第三个参数是参数值
            return fci.f1.invoke(fci.i1, obj, args);

回到invokeSuper方法:

//生成了FastClass代理类
            this.init();
               //拿到了当前方法对象的两个代理类
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            //执行对代理方法再进行包装的代理类的invoke方法---该方法也必须是直接调用的方法,而不是反射调用
            //注意这里第一个参数传入的是当前被调用方法的索引值,第二个参数是代理对象,第三个参数是参数值
            return fci.f2.invoke(fci.i2, obj, args);

但是这里FastClass的invoke方法都是抽象方法,我们也不方便查看其子类的具体实现,因此是动态生成的字节码,但是下面我可以给大家模拟一下这个实现的思路:

模拟生成的代理对象的代理对象类:
这里只重写了父类中两个最重要的方法

public class ProxyFastClass {
    static Signature s0 = new Signature("saveSuper", "()V");
    static Signature s1 = new Signature("saveSuper", "(I)V");
    static Signature s2 = new Signature("saveSuper", "(J)V");

    // 获取代理方法的编号
    /*
        Proxy
            saveSuper()              0
            saveSuper(int)           1
            saveSuper(long)          2
        signature 包括方法名字、参数返回值
     */
    public int getIndex(Signature signature) {
        if (s0.equals(signature)) {
            return 0;
        } else if (s1.equals(signature)) {
            return 1;
        } else if (s2.equals(signature)) {
            return 2;
        }
        return -1;
    }

    // 根据方法编号, 正常调用目标对象方法
    public Object invokeSuper(int index, Object proxy, Object[] args) {
        if (index == 0) {
            ((Proxy) proxy).saveSuper();
            return null;
        } else if (index == 1) {
            ((Proxy) proxy).saveSuper((int) args[0]);
            return null;
        } else if (index == 2) {
            ((Proxy) proxy).saveSuper((long) args[0]);
            return null;
        } else {
            throw new RuntimeException("无此方法");
        }
    }

    public static void main(String[] args) {
        ProxyFastClass fastClass = new ProxyFastClass();
        //这里的方法索引是在init方法中计算出来的
        int index = fastClass.getIndex(new Signature("saveSuper", "()V"));
        System.out.println(index);
        //调用代理类生成的代理类的invokeSuper方法
        fastClass.invokeSuper(index, new Proxy(), new Object[0]);
    }
}

模拟生成目标对象的代理类(不要搞混):

public class TargetFastClass {
    static Signature s0 = new Signature("save", "()V");
    static Signature s1 = new Signature("save", "(I)V");
    static Signature s2 = new Signature("save", "(J)V");

    // 获取目标方法的编号
    /*
        Target
            save()              0
            save(int)           1
            save(long)          2
        signature 包括方法名字、参数返回值
     */
    public int getIndex(Signature signature) {
        if (s0.equals(signature)) {
            return 0;
        } else if (s1.equals(signature)) {
            return 1;
        } else if (s2.equals(signature)) {
            return 2;
        }
        return -1;
    }

    // 根据方法编号, 正常调用目标对象方法
    public Object invoke(int index, Object target, Object[] args) {
        if (index == 0) {
            ((Target) target).save();
            return null;
        } else if (index == 1) {
            ((Target) target).save((int) args[0]);
            return null;
        } else if (index == 2) {
            ((Target) target).save((long) args[0]);
            return null;
        } else {
            throw new RuntimeException("无此方法");
        }
    }

    public static void main(String[] args) {
        TargetFastClass fastClass = new TargetFastClass();
        //init方法中计算出来当前被调用方法的索引
        int index = fastClass.getIndex(new Signature("save", "(I)V"));
        System.out.println(index);
        fastClass.invoke(index, new Target(), new Object[]{100});
    }
}

相信大家都已经看懂了,下面我再给大家做一个小小的汇总:

收获💡

  • 当调用 MethodProxy 的 invoke 或 invokeSuper 方法时, 会动态生成两个类

  • ProxyFastClass 配合代理对象一起使用, 避免反射

  • TargetFastClass 配合目标对象一起使用, 避免反射 (Spring 用的这种)

  • TargetFastClass 记录了 Target 中方法与编号的对应关系

  • save(long) 编号 2

  • save(int) 编号 1

  • save() 编号 0

  • 首先根据方法名和参数个数、类型, 用 switch 和 if 找到这些方法编号

  • 然后再根据编号去调用目标方法, 又用了一大堆 switch 和 if, 但避免了反射

  • ProxyFastClass 记录了 Proxy 中方法与编号的对应关系,不过 Proxy 额外提供了下面几个方法

  • saveSuper(long) 编号 2,不增强,仅是调用 super.save(long)

  • saveSuper(int) 编号 1,不增强, 仅是调用 super.save(int)

  • saveSuper() 编号 0,不增强, 仅是调用 super.save()

  • 查找方式与 TargetFastClass 类似

  • 为什么有这么麻烦的一套东西呢?

  • 避免反射, 提高性能, 代价是一个代理类配两个 FastClass 类, 代理类中还得增加仅调用 super 的一堆方法

  • 用编号处理方法对应关系比较省内存, 另外, 最初获得方法顺序是不确定的, 这个过程没法固定死

相关文章