采用字节码技术,动态生成代理类,并在代理类中做回调处理完成方法增强。
假设业务类User,我们现在要对其进行处理。CGLIB动态代理会生成User的代理类UserProxy,其中UserProxy继承自User。既然是继承,那么必然User的方法他也有,除非用final修饰。
因此,我们可以在UserProxy中对User的方法做处理,实现代理的作用。
现在通过代码看下:
业务类User
public class User {
public final void getId(){
System.out.println("这是final方法");
}
public void getName(String id){
System.out.println("User方法-获取名称返回:"+id);
}
}
设置业务类的拦截器,用于后面的方法拦截,做个性化处理。
public class UserMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("拦截器拦截前:"+method.getName());
Object object=proxy.invokeSuper(obj,args); //invokeSuper而非invoke
System.out.println("拦截器拦截后:"+method.getName());
return object;
}
}
调用测试:
public class MainTest {
public static void main(String[] args) {
//输出cglib动态代理产生的代理类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/**/Documents/log/cglibProxyClass");
//以下用于设置动态代理类的参数
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(User.class);
enhancer.setCallback(new UserMethodInterceptor());
User user=(User)enhancer.create();
user.getName("a");
}
}
打印结果:
拦截器拦截前:getName
User方法-获取名称返回:a
拦截器拦截后:getName
通过下面代码,我们可以在该路径下生成代理类。
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/**/Documents/log/cglibProxyClass");
下面截取部分代码:
public class User$$EnhancerByCGLIB$$1e96607 extends User implements Factory {
final void CGLIB$getName$0(String var1) {
super.getName(var1);
}
public final void getName(String var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$getName$0$Method, new Object[]{var1}, CGLIB$getName$0$Proxy);
} else {
super.getName(var1);
}
}
}
其中:
a.代理类与被代理类的继承关系( extends)
b.final修饰的方法无法继承,生成的代理类中并没有getId()。
c.普通方法getName()有两个,一个是直接调用父类的方法,另一个先处理了拦截(intercept)。
在JDK1.8之前,CGLIB动态代理的效率要比JDK动态代理高很多。这是为什么呢?如何实现的呢?
JDK动态代理使用的是反射,那么CGLIB是如何处理的呢?
CGLIB使用FastClass机制来实现对被拦截方法的调用。对类的方法建立索引,通过索引来调用相应的方法,快速高效。
public class ClassA {
public void a1(){
System.out.println("这是ClassA的a1方法");
}
public void a2(){
System.out.println("这是ClassA的a2方法");
}
}
public class ClassB {
public int getIndex(String signature){
switch (signature.hashCode()){
case 3014151:
return 1;
case 3014521:
return 2;
}
return -1;
}
public void invoke(int index,Object object,Object[] objects){
ClassA classA=new ClassA();
switch (index){
case 1:
classA.a1();
return ;
case 2:
classA.a2();
return ;
}
}
}
通过在一个类中建立另一个类的方法的索引,就可以快速准确的定位到调用的方法。无需使用反射。但也可以发现,这种方式弊端就是两个类过于耦合,维护代价很大。
对应到源码中的FastClassInfo类
private static class FastClassInfo
{
FastClass f1;
FastClass f2;
int i1;
int i2;
}
f1,f2分别对应代理类、被代理类,按照上面说的类方法索引的方式,生成新的类,方便使用。在例子中看一下,如下图所示。
被代理类的FastClass:
public class User$$FastClassByCGLIB$$8b45c2e9 extends FastClass {
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case 1826985398:
if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
return 2;
}
break;
case 1871866270:
if (var10000.equals("getName(Ljava/lang/String;)V")) {
return 0;
}
break;
case 1913648695:
if (var10000.equals("toString()Ljava/lang/String;")) {
return 3;
}
break;
case 1955814084:
if (var10000.equals("getId()V")) {
return 1;
}
break;
case 1984935277:
if (var10000.equals("hashCode()I")) {
return 4;
}
}
return -1;
}
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
//实例化User对象,根据int值进行调用。
User var10000 = (User)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
var10000.getName((String)var3[0]);
return null;
case 1:
var10000.getId();
return null;
case 2:
return new Boolean(var10000.equals(var3[0]));
case 3:
return var10000.toString();
case 4:
return new Integer(var10000.hashCode());
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
}
代理类的FastClass:
(类似部分略过)
public class User$$EnhancerByCGLIB$$1e96607$$FastClassByCGLIB$$6706c1cf extends FastClass {
public int getIndex(Signature var1) {
//具体实现略
}
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
//“1e96607”即对应上面截图中的“User$$EnhancerByCGLIB$$1e96607”
1e96607 var10000 = (1e96607)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
return new Boolean(var10000.equals(var3[0]));
case 1:
return var10000.toString();
case 2:
return new Integer(var10000.hashCode());
case 3:
return var10000.clone();
case 4:
var10000.getName((String)var3[0]);
return null;
case 5:
return var10000.newInstance((Callback[])var3[0]);
case 6:
return var10000.newInstance((Callback)var3[0]);
case 7:
return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);
case 8:
return 1e96607.CGLIB$findMethodProxy((Signature)var3[0]);
case 9:
var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);
return null;
case 10:
var10000.setCallbacks((Callback[])var3[0]);
return null;
case 11:
1e96607.CGLIB$SET_STATIC_CALLBACKS((Callback[])var3[0]);
return null;
case 12:
1e96607.CGLIB$SET_THREAD_CALLBACKS((Callback[])var3[0]);
return null;
case 13:
return var10000.getCallback(((Number)var3[0]).intValue());
case 14:
return var10000.getCallbacks();
case 15:
1e96607.CGLIB$STATICHOOK1();
return null;
case 16:
var10000.CGLIB$getName$0((String)var3[0]);
return null;
case 17:
return new Boolean(var10000.CGLIB$equals$1(var3[0]));
case 18:
return var10000.CGLIB$toString$2();
case 19:
return new Integer(var10000.CGLIB$hashCode$3());
case 20:
return var10000.CGLIB$clone$4();
case 21:
var10000.getId();
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
}
字节码增强技术相当于是一把打开运行时JVM的钥匙,利用它可以动态地对运行中的程序做修改,也可以跟踪JVM运行中程序的状态。
上面我们介绍过,代理类是动态生成的,负责拦截调用的FastClass也是动态生成的。通过这些动态生成的类,我们实现了对原始业务类中方法的增强处理。
1.反射是个好东西,但效率一般不高。在选型时,要综合考虑,平衡把握,没有恒强的技术。
2.系统间的低耦合、可维护、可插拔都是我们所追求的。实现方式多种多样,各个方面各个层次的实现思路也是不同的。