前面的章节,已经分析了IoC容器的源码,接下来的章节来分析Spring的另一个核心功能AOP。为了更好的分析源码,需要先温习一下动态代理的知识,如果对java的动态代理无所了解的话,那么对AOP源码的分析就无从谈起。代理模式可分为静态代理和动态代理两种。而动态代理又有JDK、CGLIB动态代理。下面我们逐步分析这几种代理。
1.静态代理
- 被代理接口和实现类
package com.lyc.cn.v2.day04.proxy.proxy; /** * 账户接口 */ public interface Count { // 查询账户 void queryCount(); // 修改账户 void updateCount(); }
package com.lyc.cn.v2.day04.proxy.proxy; /** * @author: LiYanChao * @create: 2018-10-21 12:07 */ public class CountImpl implements Count { @Override public void queryCount() { System.out.println("==查询账户"); } @Override public void updateCount() { System.out.println("==更新账户"); } }
接口只模拟了两个接口,账户查询和账户更新,并在实现类里进行了打印。
- 代理类
package com.lyc.cn.v2.day04.proxy.proxy; /** * 代理类 * @author: LiYanChao * @create: 2018-10-21 12:08 */ public class CountProxy implements Count { private CountImpl countImpl; /** * 覆盖默认构造器 */ public CountProxy(CountImpl countImpl) { this.countImpl = countImpl; } @Override public void queryCount() { System.out.println("==查询账户开始"); // 调用真正的查询账户方法 countImpl.queryCount(); System.out.println("==查询账户结束"); } @Override public void updateCount() { System.out.println("==更新账户开始"); // 调用真正的修改账户操作 countImpl.updateCount(); System.out.println("==更新账户结束"); } }
- 测试及结果
@Test public void test1() { // 静态代理 CountImpl countImpl = new CountImpl(); CountProxy countProxy = new CountProxy(countImpl); countProxy.updateCount(); System.out.println("\n*******\n"); countProxy.queryCount(); }
==更新账户开始 ==更新账户 ==更新账户结束 ******* ==查询账户开始 ==查询账户 ==查询账户结束
该中模式比较简单,不再做过多的分析。
2.JDK动态代理
JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的业务实现类对象以及方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。JDK的动态代理需要实现InvocationHandler接口,并重写invoke方法。并且被代理的类必须有接口,来看具体的例子:
- 被代理接口和类
package com.lyc.cn.v2.day04.proxy.jdk; public interface JDKAnimal { void sayHello(); }
package com.lyc.cn.v2.day04.proxy.jdk; public class JDKDog implements JDKAnimal { @Override public void sayHello() { System.out.println("我是一只猫"); } }
- 自定义InvocationHandler
package com.lyc.cn.v2.day04.proxy.jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyInvocationHandler implements InvocationHandler { // 目标对象 private Object target; /** * 构造方法 * @param target 目标对象 */ public MyInvocationHandler(Object target) { super(); this.target = target; } /** * @param proxy JDK动态生成的最终代理对象 * @param method 调用真实对象的某个方法的Method对象 * @param args 调用真实对象某个方法时接受的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("==代理方法开始执行"); Object invoke = method.invoke(target, args); System.out.println("==代理方法结束执行"); return invoke; } /** * 获取目标对象的代理对象 * @return 代理对象 */ public Object getProxy() { /** * 参数: * 1、获取当前线程的类加载 * 2、获取接口 * 3、当前对象 */ return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this); } }
- 测试及结果
@Test public void test2() { // JDK动态代理 MyInvocationHandler handler = new MyInvocationHandler(new JDKDog()); JDKAnimal proxy = (JDKAnimal) handler.getProxy(); proxy.sayHello(); }
==代理方法开始执行 我是一只猫 ==代理方法结束执行
3.JDK动态代理原理分析
在MyInvocationHandler类中,通过实现InvocationHandler并重写invoke方法,实现了对JDKDog类的动态代理。但是这里大家一定会有一个疑问,在测试类中通过JDKAnimal proxy = (JDKAnimal) handler.getProxy();
获取了代理类的实例,但是当调用proxy.sayHello();
方法时,却会调用MyInvocationHandler的invoke方法,要解开这个谜题,就需要了解一下代理类是如何生成、生成的代理类是什么样子的、是如何被实例化的。
- Proxy.newProxyInstance方法简析
打开MyInvocationHandler类的getProxy方法,查看Proxy.newProxyInstance方法源码:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } // 创建生成代理类的实例 return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
如果大家对这段代码不感兴趣或者觉得很枯燥的话,没关系,大家只要知道在这个方法里生成了代理类的实例就行了,但是生成的代理类是什么样子的呢?可以通过ProxyGenerator来帮助我们。
- 生成代理类的.class文件
package com.lyc.cn.v2.day04.proxy.jdk; import org.junit.Test; import sun.misc.ProxyGenerator; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * @author: LiYanChao * @create: 2018-11-02 14:11 */ public class GenJdkProxyClass { /** * 生成代理类的名称 */ private static String DEFAULT_CLASS_NAME = "$Proxy"; /** * 默认生成的文件全路径 */ private static String DEFAULT_FILE_PATH = "/Users/liyanchao/Desktop/" + DEFAULT_CLASS_NAME + ".class"; /** * 使用ProxyGenerator生成代理类.class文件 * @param path 文件路径 */ public static void genProxyClass(String path) { byte[] classFile = ProxyGenerator.generateProxyClass(DEFAULT_CLASS_NAME, new Class[]{JDKAnimal.class}); FileOutputStream fos = null; try { fos = new FileOutputStream(path); fos.write(classFile); fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } @Test public void TestGenProxyClass() { GenJdkProxyClass.genProxyClass(DEFAULT_FILE_PATH); } }
可以根据自己的需要修改生成的类名和路径。运行测试方法生成.class文并进行反编译,如果没有反编译软件的话,可以下载JD-GUI 可以实现对.class文件的反编译,该软件支持windows、linux、macOS。用JD-GUI打开生成的$Proxy.class文件,并将反编译的类导入IDEA(直接在IDEA里新建同名文件将代码复制粘贴进去即可)。反编译后的源码如下:
package com.lyc.cn.v2.day04.proxy.jdk; /** * 反编译代理.class文件 * @author: LiYanChao * @create: 2018-11-02 15:01 */ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy extends Proxy implements JDKAnimal { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } public final boolean equals(Object paramObject) { try { return ((Boolean) this.h.invoke(this, m1, new Object[]{paramObject})).booleanValue(); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } /** * 代理类中实现了sayHello接口 */ public final void sayHello() { try { this.h.invoke(this, m3, null); return; } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() { try { return (String) this.h.invoke(this, m2, null); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() { try { return ((Integer) this.h.invoke(this, m0, null)).intValue(); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m3 = Class.forName("com.lyc.cn.v2.day04.proxy.jdk.JDKAnimal").getMethod("sayHello", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); // return 会报错,注释掉即可。 //return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
代理类$Proxy被声明为final类,继承Proxy类并实现了JDKAnimal接口,重写了equals、toString、hashCode等方法,当然最重要的还是实现了JDKAnimal接口中的sayHello方法,并通过静态代码块拿到了sayHello方法的信息,看到sayHello方法体,看到这里相信大家对前面提到的疑问已经恍然大悟了。再通过一个测试方法加深大家的印象:
@Test public void test3() { // JDK动态代理测试反编译后的生成代理类 MyInvocationHandler handler = new MyInvocationHandler(new JDKDog()); $Proxy $proxy = new $Proxy(handler); $proxy.sayHello(); }
new $Proxy(handler)
构造函数接受的就是我们自定义的MyInvocationHandler,所以当代码运行到$Proxy的sayHello方法时,this.h.invoke(this, m3, null);
this.h就是MyInvocationHandler的实例,所以自然就会调用到invoke方法了,因为JDKAnimal proxy = (JDKAnimal) handler.getProxy();
获取到的是代理类的实例,而不是JDKAnimal的实例。
4.CGLIB动态代理
CGLIB是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。 在使用的时候需要引入cglib和asm的jar包,并实现MethodInterceptor接口。
本例是在Spring源码下新建的Gradle模块,所以引入jar包的方式如下,如果是maven工程或者其他的工程的话,可自行导入jar包。
compile group: 'asm', name: 'asm', version: '3.3.1' compile group: 'cglib', name: 'cglib', version: '2.2.2'
- 被代理类
package com.lyc.cn.v2.day04.proxy.cglib; public class CglibCat { public void sayHello() { System.out.println("我是一只猫。。。"); } }
- 自定义MethodInterceptor
package com.lyc.cn.v2.day04.proxy.cglib; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class MyCglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); // 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理 public Object getInstance(Class clazz) { enhancer.setSuperclass(clazz); enhancer.setCallback(this); // 返回代理对象 return enhancer.create(); } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("==代理方法开始执行"); Object result = methodProxy.invokeSuper(proxy, args); System.out.println("==代理方法结束执行"); return result; } }
- 测试类及结果
@Test public void test4() { // CGLIB动态代理 CglibCat cat = (CglibCat) new MyCglibProxy().getInstance(CglibCat.class); cat.sayHello(); }
==代理方法开始执行 我是一只猫。。。 ==代理方法结束执行
关于CGLIB的原理,经过多种反编译软件测试,反编译出来的代码好像不是正确的,而且跟网上其他的文章反编译出来的有很大的不同,不知道是不是因为macOS或CGLIB版本的原因,这里就不在粘贴反编译源码了,如果感兴趣的同学可以参考下面这篇文章,cglib原理解析。
5.总结
总而言之,动态代理技术是AOP的基础也是核心,大家一定要先把JDK、CGLIB动态代理搞明白之后,再去看AOP的源码,才能达到事半功倍的效果。
6.JDK与CGlib对比
- CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高(1.6和1.7CGLib更快,1.8jdk更快)
- CGLib在创建对象的时候所花费的时间却比JDK动态代理多
- singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反之,则适合用JDK动态代理
- JDK动态代理是面向接口的,CGLib动态代理是通过字节码底层继承代理类来实现(如果被代理类被final关键字所修饰,那么会失败)
- JDK生成的代理类类型是Proxy(因为继承的是Proxy),CGLIB生成的代理类类型是Enhancer类型
- 如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);如果要被代理的对象不是实现类,那么Spring会强制使用CGLib来实现动态代理。