likes
comments
collection
share

Spring底层原理分析-九(Cglib代理原理)

作者站长头像
站长
· 阅读数 27

Cglib代理简单实现

  根据上节JDK代理的思路接着创建Cglib的例子。

public class Target {
    public void save() {
        System.out.println("save()执行");
    }
    public void save(int i) {
        System.out.println("save(i)执行");
    }
    public void save(long j) {
        System.out.println("save(j)执行");
    }
}
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 {
        try {
            save0 = Target.class.getMethod("save");
            save1 = Target.class.getMethod("save", int.class);
            save2 = Target.class.getMethod("save", long.class);
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }
    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0], null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, save1, new Object[]{i}, null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
    @Override
    public void save(long j) {
        try {
            methodInterceptor.intercept(this, save2, new Object[]{j}, null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}
public static void main(String[] args) {
    Proxy proxy = new Proxy();
    proxy.setMethodInterceptor(new MethodInterceptor() {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("before.....");
            method.invoke(new Target(),objects);
            System.out.println("after......");
            return null;
        }
    });
    proxy.save();
    proxy.save(1);
    proxy.save(1L);
}

  有了之前JDK代理创建的思路,这里省略了一些步骤,比如MethodInterceptor的自定义实现在这里就直接使用了原生的MethodInterceptor,不再手动添加了。方法的获取这里就直接参考上节的方式编写。每个方法中的intercept方法调用,第四个参数是传入MethodProxy类型参数,这里后面详细介绍,先传null。从这个案例出发,我们接着探究Cglib代理的底层逻辑。

MethodProxy应用

  在描述AOP的实现方式中,我们发现Cglib和JDK代理有一个最大的区别就是,Cglib的逻辑实现接口中比JDK的逻辑实现接口多了一个叫MethodProxy的参数,也就是上面先置为null的那个参数,而且使用这个参数,不会触发反射调用,我们这里先添加上能够使用该调用方式的代码。

static MethodProxy saveProxy0;
static MethodProxy saveProxy1;
static MethodProxy saveProxy2;
static {
    try {
        saveProxy0 = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");
        saveProxy1 = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");
        saveProxy2 = 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);
}

  这里在Proxy类中添加methodProxy的创建与获取,以及目标类的调用方法,因为目标类既是代理类的父类,所以这里调用父类方法即可。   这里说一下methodProxy的获取方式以及参数,MethodProxy类提供了一个create方法用来创建,需要按顺序传入目标类、代理类、代理方法参数和返回值组个起来的字符类型参数(比如save方法,括号内是参数类型简写,括号外是返回类型简写,这里V就是void的意思),第四个参数是代理类的方法,第五个就是目标类的原始调用方法。

Object invoke = methodProxy.invoke(new Target(), objects);
Object invoke = methodProxy.invokeSuper(o, objects);

  然后我们就可以根据之前学习的Cglib代理的基本使用来通过methodProxy调用的两种方式来使用。

MethodProxy底层原理

  既然之前一直在说MethodProxy的调用不会经过反射,那么它一定有一种实现方式来实现该调用,先来看MethodProxy的create方法做了什么,这里翻看一下源码内容。

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
   MethodProxy proxy = new MethodProxy();
   proxy.sig1 = new Signature(name1, desc);
   proxy.sig2 = new Signature(name2, desc);
   proxy.createInfo = new CreateInfo(c1, c2);
   return proxy;
}

  第一行先创建了MethodProxy对象,第二行和第三行,根据最初的案例代码,会发现是将目标类和代理类的方法名,以及对应的参数返回值绑定到一个Signature对象中。最后一行CreateInfo用来实体记录了代理类和被代理类。   我们接着看MethodProxy的invoke或者invokeSuper方法做了什么。

public Object invoke(Object obj, Object[] args) throws Throwable {
   try {
      init();
      FastClassInfo fci = fastClassInfo;
      return fci.f1.invoke(fci.i1, obj, args);
   }
   catch (InvocationTargetException ex) {
      throw ex.getTargetException();
   }
   catch (IllegalArgumentException ex) {
      if (fastClassInfo.i1 < 0)
         throw new IllegalArgumentException("Protected method: " + sig1);
      throw ex;
   }
}
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
   try {
      init();
      FastClassInfo fci = fastClassInfo;
      return fci.f2.invoke(fci.i2, obj, args);
   }
   catch (InvocationTargetException e) {
      throw e.getTargetException();
   }
}

  有一个共同点,就是调用了init方法,继续追溯init源码做了什么。

private void init() {
   if (fastClassInfo == null) {
      synchronized (initLock) {
         if (fastClassInfo == null) {
            CreateInfo ci = createInfo;
            FastClassInfo fci = new FastClassInfo();
            fci.f1 = helper(ci, ci.c1);
            fci.f2 = helper(ci, ci.c2);
            fci.i1 = fci.f1.getIndex(sig1);
            fci.i2 = fci.f2.getIndex(sig2);
            fastClassInfo = fci;
            createInfo = null;
         }
      }
   }
}
private static class FastClassInfo {
   FastClass f1;
   FastClass f2;
   int i1;
   int i2;
}

  先对一个叫fastClassInfo的成员变量判空,如果为空,就会做一些赋值操作,上面把该成员变量的类本身也粘贴了过来,里面是两个FastClass类型的变量。FastClass是个接口类型,那也就是说这里要放两个该接口的实现类。这里我们先说结论,Cglib代理除了会产生上面模拟的Proxy类之外,还会产生两个FastClass类型的类,分别是属于目标类和代理类的FastClass实现类,而FastClassInfo里面放的就是这两个实现类。产生这两个类做什么后面会进行分析,这里先有个概念。继续看上面的代码,为空后的一些赋值操作,也涉及到了之前提过的ASM字节码生成技术,就是靠这个技术来生成的这两个实现类的字节码。

FastClass实现方式

  由于使用ASM动态生成的字节码,不使用相关工具也看不到生成的代理类。我们这里手动编写目标类和代理类的FastClass实现。

public class TargetFastClass extends FastClass {
    static Signature s0 = new Signature("save", "()V");
    static Signature s1 = new Signature("save", "(I)V");
    static Signature s2 = new Signature("save", "(J)V");
    @Override
    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;
    }
    @Override
    public Object invoke(int i, Object o, Object[] objects) throws InvocationTargetException {
        if (i == 0) {
            ((Target) o).save();
        } else if (i == 1) {
            ((Target) o).save((int) objects[0]);
        } else if (i == 2) {
            ((Target) o).save((long) objects[0]);
        }else {
            throw new RuntimeException("error");
        }
        return null;
    }
}

  我们定义了一个叫TargetFastClass来实现FastClass,这里做了一定的删减,因为FastClass里面不止两个方法,不过在使用过程中,上面这两个比较重要。我们下面逐一分析作用,以及里面代码的含义。   getIndex,正如名字一样,就是获取一个索引的意思,里面需要传入一个Signature类型参数,这里也正是上面create方法里面创建的类。不过在执行new MethodProxy();的时候就已经生成了该类。create过程只是执行后续的比较操作罢了,也正是getIndex方法的逻辑。由三个static参数可以看出,这里是把三个方法,根据方法名和参数返回值组合值创建了三个Signature对象。在getIndex方法中,根据不同的Signature对象,返回不同的索引,但看该方法没有任何意义,这里的索引命名也有任何要求和意义,主要要和下面的incoke方法达成一致。   invoke方法正是方法具体执行的逻辑,根据代码也不难看出要做什么,参数i就是方法的索引,根据不同的索引来调用约定好的方法并传入参数。我们回看最初的例子执行用到的方法invoke和这里有什么关联。

public Object invoke(Object obj, Object[] args) throws Throwable {
   try {
      init();
      FastClassInfo fci = fastClassInfo;
      return fci.f1.invoke(fci.i1, obj, args);
   }
   catch (InvocationTargetException ex) {
      throw ex.getTargetException();
   }
   catch (IllegalArgumentException ex) {
      if (fastClassInfo.i1 < 0)
         throw new IllegalArgumentException("Protected method: " + sig1);
      throw ex;
   }
}

  两个方法的作用以及代码并不复杂,我们这里看一下哪里用到了这两个方法,上面贴过的源码里面其实就已经包含了两个方法的调用,只是上面没有细说。回看MethodProxy的init方法的这两行代码。

fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);

  在这两行代码中将FastClassInfo类中的i1和i2参数补齐了。正是传入的方法对应的,目标FastClass实现类索引,代理FastClass类索引。   再回看上面贴出的MethodProxy的invoke和invokeSuper源码内容,各自取出关键的一行如下。

return fci.f1.invoke(fci.i1, obj, args);
return fci.f2.invoke(fci.i2, obj, args);

  这里的invoke调用的就是我们上面模拟写出的invoke方法,传入了上面绑定好的i1和i2两个索引值。以及对应的类,调用invoke传入的就是目标类,调用invokeSuper传入的就是代理类。最后就是方法的参数集合。

public static void main(String[] args) throws InvocationTargetException {
    TargetFastClass targetFastClass = new TargetFastClass();
    int index = targetFastClass.getIndex(new Signature("save", "()V"));
    System.out.println(index);
    targetFastClass.invoke(index,new Target(),new Object[0]);
}

  我们这时候就可以通过上面的方法来调用方法,不过TargetFastClass继承了FastClass类,该类里面有个禁止调用无参构造的要求,因为我们方法的逻辑已经实现好了,所以可以先把继承取消掉来调用测试。   我们继续根据该逻辑创建代理FastClass实现类。

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");
    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 i, Object o, Object[] objects) throws InvocationTargetException {
        if (i == 0) {
            ((Proxy) o).saveSuper();
        } else if (i == 1) {
            ((Proxy) o).saveSuper((int) objects[0]);
        } else if (i == 2) {
            ((Proxy) o).saveSuper((long) objects[0]);
        } else {
            throw new RuntimeException("error");
        }
        return null;
    }
}

  这里除了调用的方法由增强方法改为了原始方法外,没有任何区别。

与JDK代理区别

  这里最直观的区别就是,一个是对接口的增强代理,一个是对父类的增强代理。但经过我们两篇文章对两者原理的分析后,我们还会发现,他们生成代理类的方式,数目都是不一样的。Cglib需要生成三个代理类,从时间成本上比JDK多的多,但是上篇提到JDK对反射的优化中提到了反射是一个比较慢的过程。如果仔细回想上面说的FastClass的作用后会发现,新生成的这两个类,相当于把方法和调用之间建立了某种联系,不需要通过一个方法名来去反射调用。这样就从另一个方面提高了运行效率。 Spring底层原理分析-九(Cglib代理原理)