Spring底层原理分析-九(Cglib代理原理)
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的作用后会发现,新生成的这两个类,相当于把方法和调用之间建立了某种联系,不需要通过一个方法名来去反射调用。这样就从另一个方面提高了运行效率。
转载自:https://juejin.cn/post/7173269818806632462