代理机制
本文会从生成的代理对象的字节码角度浅析jdkProxy和cglib代理的区别。以及cglib如何做到嵌套代理
JDKProxy
JDKProxy是基于接口实现的。
利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类$Proxy.
对于一个接口A的对象a来说,只要这个对象a获取到了该接口实现类B的代理,则对于接口的实例对象来说,是可以调用到实现类B所实现的接口的方法。而在接口中没定义的方法是不能调用的.
Class<?> klass = object.getClass();
ClassLoader classLoader = klass.getClassLoader();
Class<?>[] interfaces = klass.getInterfaces();
return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在执行方法之前可以做一些事情 例如对参数的检查 从而决定方法是否真正执行
Object result = method.invoke(object, args);
//在执行方法之后可以做一些事情
return result;
}
});
CGLibPRoxy
public static <T> T getProxy(Object object) {
Class<?> aClass = object.getClass();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(aClass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//切面
Object result = method.invoke(object, objects);
//切面
return result;
}
});
return (T) enhancer.create();
}
对于CGLib代理模式,是无需要基于接口来实现的。
代理类相当于继承于被代理类
CGlib是对类进行代理,而jdk是对接口的实现类进行代理。
对于一个类A的对象a来说,只要a取得了自己的代理,则a执行自身的方法的时候就可以面向切面编程。a可以执行A的除了private ,static 的所有方法
案例
有这样一个接口
public interface IDao {
public void select();
public void insert();
}
和这样一个实现类
public class Dao implements IDao{
@Override
public void select() {
System.out.println("select 1 from dual:");
insert();
}
@Override
public void insert() {
System.out.println("insert into ...");
}
public final void delete() {
System.out.println("delete from ...");
}
}
- 采用jdk proxy
public static void main(String[] args) {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
IDao dao = (IDao) Proxy.newProxyInstance(
Dao.class.getClassLoader(), // 代理类加载器
Dao.class.getInterfaces(), // 代理的接口
new DaoJdkProxy(new Dao()));
dao.select();
}
运行一下代码,会生成一个匿名的字节码文件,长这样
也就是说采用jdkProxy获取到的代理对象就是它,执行jdk代理对象的方法就是执行该匿名类的方法,而该方法里具体执行h.invoke()即invocationhandler方法,该方法就是创建代理对象时传入的方法,调用到业务代码实际上是在invocationhandler的invoke()方法里通过反射进行调用
所以,在执行jdk代理下该接口的select方法只会增强一次,而不会传递增强到select()方法里调用的insert()方法。
而cglib动态代理则可以增强两次
- cglib
代理逻辑如下
public class Dao$$EnhancerByCGLIB$$ef1ad419 extends Dao implements Factory {
生成的一个类继承Dao类
//部分代码如下
public final void insert() {
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$insert$0$Method, CGLIB$emptyArgs, CGLIB$insert$0$Proxy);
} else {
super.insert();
}
}
final void CGLIB$select$1() {
super.select();
}
public final void select() {
//cglib代理对象执行select方法会调用到这里
//获得methodInterceptor和invocationhandler类似都是传入进来的
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
//执行Intercept方法 即自己传入的内部类的方法
var10000.intercept(this, CGLIB$select$1$Method, CGLIB$emptyArgs, CGLIB$select$1$Proxy);
} else {
super.select();
}
}
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
System.out.println("begin intercept");
//invokeSuper方法调用目标类的方法
proxy.invokeSuper(o, objects);
System.out.println("end intercept");
return o;
}
});
上述的proxy.invokesuper()方法会调到生成的一个fastclass类的该方法。以一种路由的模式,执行代理对象所执行的方法,也就是说代理对象执行的一个方法里嵌套了另外一个可以代理的方法,则两个方法均会被代理。
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
Dao var10000 = (Dao)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
var10000.delete();
return null;
case 1:
var10000.insert();
return null;
case 2:
var10000.select();
return null;
case 3:
var10000.wait();
return null;
case 4:
var10000.wait(((Number)var3[0]).longValue(), ((Number)var3[1]).intValue());
return null;
case 5:
var10000.wait(((Number)var3[0]).longValue());
return null;
总结:
jdkproxy会创建一个代理对象类的文件,而cglib会创建多个文件组合使用,例如代理对象类和一个fastclass路由。所以jdkproxy会比cglib效率高,但是生成代理对象,其实是在spring启动的时候就创建了,所以性能影响不大。而且,jdkproxy和cglib执行代理方法的方式不一样,jdkproxy执行代理的方法其实是在我们写的invocationhandler里通过反射方式来选择性执行或不执行,而cglib可以不通过反射方式执行,而是通过直接调用的方法,通过fastclass路由,直接调用。所以jdkproxy启动快,调用效率低一点,而cglib启动慢,调用快.