JDK动态代理与CGlib动态代理 众所周知,Spring会基于代理的类去动态的选择使用JDK创建代理对象还是CGLIB(当然也可以配置全都使用CGlib),这取决于被代理的对象是类还是接口。 - java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。 - CGlib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理(字节码增强)。 ## 为什么JDK动态代理只能代理接口,不能直接代理类? 1. **先定义一个接口(JDK动态代理只能代理接口)** ```java public interface UserService { /** * test * * @return */ String query(); } ``` 2. **接口的实现类** ```java public class UserServiceImpl implements UserService{ @Override public String query() { System.out.println("query"); return null; } } ``` 3. **再定义一个InvocationHandler接口的实现类:** ```java public class UserServiceInvocationHandler implements InvocationHandler { /** * 持有目标对象 */ private Object target; public UserServiceInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invocation handler"); // 通过反射调用目标对象的方法 return method.invoke(target, args); } } ``` 4. **通过Proxy.newInstance()创建代理对象** ```java public class MainApplication { public static void main(String[] args) { // 指明一个类加载器,要操作class文件,怎么少得了类加载器呢 ClassLoader classLoader = MainApplication.class.getClassLoader(); // 为代理对象指定要是实现哪些接口,这里我们要为UserServiceImpl这个目标对象创建动态代理,所以需要为代理对象指定实现UserService接口 Class[] classes = new Class[]{UserService.class}; // 初始化一个InvocationHandler,并初始化InvocationHandler中的目标对象 InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl()); // 创建动态代理 UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler); // 执行代理对象的方法,通过观察控制台的结果,判断我们是否对目标对象(UserServiceImpl)的方法进行了增强 userService.query(); } } ``` 5. 执行结果 ```json invocation handler query ``` > 阅读到这里,我们基本知道了JDK动态代理的写法,功能虽然实现了,但是对于喜欢研究源码的同学,可能会思考:为什么调用Proxy.newProxyInstance()方法后,就产生了代理对象,对目标对象的方法进行了增强?原理是什么呢? 那接下来,我们就想着能不能获取到代理对象的class文件。下面用几种方式来获取class文件 1. **手动写到磁盘** 在调用Proxy.newProxyInstance()方法时,最终会调用到ProxyGenerator.generateProxyClass()方法,该方法的作用就是生成代理对象的class文件,返回值是一个byte[]数组。所以我们可以将生成的byte[]数组通过输出流,将内容写出到磁盘。 ```java public static void main(String[] args) throws IOException { String proxyName = "com.sun.proxy.$Proxy0"; Class[] interfaces = new Class[]{UserService.class}; int accessFlags = Modifier.PUBLIC; byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); // 将字节数组写出到磁盘 File file = new File("C:\\Users\\admin\\Desktop\\$Proxy0.class"); OutputStream outputStream = new FileOutputStream(file); outputStream.write(proxyClassFile); } ``` 2. 自动写到磁盘 ```java public static void main(String[] args) { // 让代理对象的class文件写入到磁盘 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // 指明一个类加载器,要操作class文件,怎么少得了类加载器呢 ClassLoader classLoader = MainApplication.class.getClassLoader(); // 为代理对象指定要是实现哪些接口,这里我们要为UserServiceImpl这个目标对象创建动态代理,所以需要为代理对象指定实现UserService接口 Class[] classes = new Class[]{UserService.class}; // 初始化一个InvocationHandler,并初始化InvocationHandler中的目标对象 InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl()); // 创建动态代理 UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler); // 执行代理对象的方法,通过观察控制台的结果,判断我们是否对目标对象(UserServiceImpl)的方法进行了增强 userService.query(); } ``` 接着我们打开文件看一看 ```java // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.sun.proxy; import cn.bugstack.middleware.db.router.UserService; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public class $Proxy0 extends Proxy implements UserService { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String query() throws { try { return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("cn.bugstack.middleware.db.router.UserService").getMethod("query"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } } ``` - 通过源码我们发现,$Proxy0类继承了Proxy类,同时实现了UserService接口。到这里,我们的问题一就能解释了,为什么JDK的动态代理只能基于接口实现,不能基于继承来实现?因为Java中不支持多继承,而JDK的动态代理在创建代理对象时,默认让代理对象继承了Proxy类,所以JDK只能通过接口去实现动态代理。 - $ Proxy0 实现了UserService接口,所以重写了接口中的两个方法($ Proxy0 同时还重写了Object类中的几个方法)。所以当我们调用query()方法时,先是调用到$Proxy0.query()方法,在这个方法中,直接调用了super.h.invoke()方法,父类是Proxy,父类中的h就是我们定义的InvocationHandler,所以这儿会调用到UserServiceInvocationHandler.invoke()方法。因此当我们通过代理对象去执行目标对象的方法时,会先经过InvocationHandler的invoke()方法,然后在通过反射method.invoke()去调用目标对象的方法,因此每次都会先打印invocation handler这句话。 ## 三、CGlib为什么可以代理类? cglib实现动态代理的逻辑是使用子类继承代理类,就没有单继承的限制了。 ## 四、总结 1. JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。 2. JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。 3. JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。 文章转自为什么JDK动态代理只能代理接口,不能直接代理类?CGlib为什么可以代理类?