首先从实现上来讲,jdk动态代理是使用jdk自带的字节码技术去生成一个类,而cglib则基于asm,他两在使用上最直观的感受是,cglib不需要接口,而jdk需要接口,在性能上,下面的例子,jdk循环一万次执行耗时大概在150-170ms之间,而cglib则是300ms以上,所以jdk的性能是略优于asm的.
我们举两个例子,实践下二者的区别,一个是玩游戏的例子,在'游戏'两个字输出之前,用jdk代理输出'挂机'二字,另外一个例子是用的cglib,在用户类输出'买车'之前,我们用中间商那个类输出'赚差价'
jdk
public interface IGame {
void play();
}
public class Agent implements InvocationHandler {
public IGame game;
public Agent(IGame game) {
this.game = game;
}
public void agent(){
System.out.println("挂机");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
agent();
return method.invoke(game);
}
}
public class Game implements IGame {
@Override
public void play() {
System.out.println("游戏");
}
}
cglib
public class Customer {
public void buyCar(){
System.out.println("买车");
}
}
public class MiddleBussiness implements MethodInterceptor {
public void makeMoney(){
System.out.println("中间商赚差价");
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
makeMoney();
return methodProxy.invokeSuper(o, objects);
}
}
测试类
public class Main {
public static void main(String[] args) {
jdk();
System.out.println("---------------");
cglib();
}
public static void jdk(){
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
Game g = new Game();
Agent agent = new Agent(g);
IGame game = (IGame) Proxy.newProxyInstance(Game.class.getClassLoader(), new Class[]{IGame.class}, agent);
game.play();
}
public static void cglib(){
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "d://cglib");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Customer.class);
enhancer.setCallback(new MiddleBussiness());
Customer customer = (Customer) enhancer.create();
customer.buyCar();
}
}
二者的使用上除了对目标类要求是否有接口外还有一个区别就是对于目标对象的创建上,jdk需要手动创建目标对象,而cglib并不需要.
看一下二者生成的代理类是什么样的
Jdk
public final class $Proxy0 extends Proxy implements IGame {
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void play() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
Cglib
public class Customer$$EnhancerByCGLIB$$7bd11a1d extends Customer implements Factory {
final void CGLIB$buyCar$0() {
super.buyCar();
}
public final void buyCar() {
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$buyCar$0$Method, CGLIB$emptyArgs, CGLIB$buyCar$0$Proxy);
} else {
super.buyCar();
}
}
可以从代码中看出新生成的类都是继承于原来的类,都是属于原来类的子类,甚至在接口调用中,二者都是讲将调用传递给了handler,不同点事jdk的处理是直接将当前代理类,方法和参数传递了过去,注意此处并没有传递目标对象,,而cglib则除了传递当前代理,方法以及参数,还多传递了个methodProxy,这个对象里面封装了参数签名的信息,并且提供了调用当前代理类父类的方法,那为啥jdk没有实现直接调用父类的方法呢,因为cglib调用父类的方法依赖于FastClass
,而这个类的实现也是由cglib
动态代理实现,调用如下:
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();
}
}
FastClass
的代理类实现,主要是根据fci.i2这一个int值来找对应的方法,我们的buyCar
方法对应的值为16
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
7bd11a1d var10000 = (7bd11a1d)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
case 16:
var10000.CGLIB$buyCar$0();
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
看到这里有没有一种恍然大悟的感觉,这个methodProxy
又去调用了代理类的CGLIB$buyCar$0
这个方法,这个方法也是生成的里面只有一句话就是super.buyCar()
从而调用了父类.