上一篇讲解了反射的知识[],作为反射的入门级,然后这一篇主要也是讲解动态代理的实现机制。
动态代理包括「jdk的动态代理」和「cglib 的动态代理」,两者实现相同的功能,但是实现方式却是有明显的区别。
下面我们就通过代码的方式层层的深入这两种动态代理,了解他们的性能以、底层的实现原理及应用场景。
在详细介绍动态代理之前,先来说说Java中的代理模式。代理模式分为两种:
代理模式定义:我的个人理解就是给某一个对象提供一个代理对象,在代理对象中拥有被代理对象的引用,并在代理对象中调用被代理对象的方法之前和之后进行方法的增强。
我这里画了一张代理模式的类图,设计模式中的代理模式比较简单,代理类和委托类有公共的接口,最后由代理类去执行委托类的方法:
代理模式就好像生活中的中介,去帮你做事,而不用比自己去做事。举个例子,比如你要买车,但是买车之前你要到处找车源,找到车源给钱了还要办理一堆手续。
(1)下面我们以买车这个案例进行代理模式的代码编写,首先要有一个公共的接口Person,Person接口里面定义公共的方法:
public interface Person{ void buyCar(); } 复制代码
(2)然后定义一个委托类,也就是我本人Myself,并实现Person接口,具体代码如下:
public class Myself implements Person { @Override public void buyCar() { System.out.println("我要买车了"); } 复制代码 复制代码} 复制代码
(3)最后就是创建代理类CarProxy,同样也是实现Person接口,具体实现代码如下:
public class CarProxy implements Person{ private Myself myself ; public CarProxy(final Myself myself ) { this.myself = myself ; } @Override public void buyCar() { System.out.println("买车前去找车源"); myself .buyCar(); System.out.println("买车后办理手续"); } 复制代码 复制代码} 复制代码
这个代理的demo很简单,如上面的类图所示,代理类和委托类都实现公共的接口Person,在委托类中进行方法的具体业务逻辑的实现,而代理类中再次对这个方法进行增强。
「代理模式」的优点就是「能够对目标对象进行功能的扩展」,缺点是每一个业务类都要创建一个代理类,这样会「使我们系统内的类的规模变得很大,不利于维护」。
于是就出现了动态代理,仔细思考静态代理的缺点,就是一个委托类就会对象一个代理类,那么是否可以将代理类做成一个通用的呢?
我们仔细来看一下下面的这个图:
我们把静态代理所有的执行过程都可以抽象成这张图的执行过程,Proxy角色无非是在「调用委托类处理业务的方法之前或者之后做一些额外的操作」。
那么为了做一个通用性的处理,就把调用委托类的method的动作抽出来,看成一个通用性的处理类,于是就有了InvocationHandler
角色,抽象成一个处理类。
这样在Proxy和委托类之间就多了一个InvocationHandler
处理类的角色,这个角色主要是「将之前代理类调用委托类的方法的动作进行统一的调用,都由InvocationHandler来处理」。
于是之前上面的类图就有了这样的改变,在Proxy和委托类之间加入了InvocationHandler,具体的实现图如下:
看完上面的图似乎有那么一点点的理解,下面我们就来详细的深入动态代理。
上面讲解到动态代理是在运行时环境动态加载class文件,并创建对应的class对象,那么动态代理着静态代理的执行时机是在哪里呢?
我这边又画了一张原理图,感觉我为画图操碎了心,每一个点都会画一个想截图,是不是很暖。
这个是静态代理的运行原理图,静态代理在程序运行时就已经创建好了class文件,在程序启动后的某一个时机(用到class文件)就会加载class文件到内存中。
当在运行时期动态生成class文件并加载class文件的运行原理图如下:
在JVM运行期时遵循JVM字节码的结构和规范生成二进制文件,并加载到内存中生成对应的Class对象。这样,就完成了动态创建class文件和Class对象的功能了。
在jdk的动态代理中的「Proxy类和委托类要求实现相同的功能」,这里的相同是指「他们都可以调用统一的逻辑业务方法」。要实现这样的设计有以下三种方法:
在jdk的动态代理中,是采用第一种方法进行实现,必须有公共的接口,下面我们还是通过静态代理的案例使用动态代理来实现。
(1)首先创建一个公共接口Person:
public interface Person{ void buyCar(); } 复制代码
(2)然后创建接口的实现类Myself:
public class Myself implements Person { @Override public void buyCar() { System.out.println("我要买车了"); } 复制代码 复制代码} 复制代码
(3)这一步就是比较关键的,要创建一个类并实现InvocationHandler
:
public class InvocationHandlerImpl implements InvocationHandler { private Person person; public InvocationHandlerImpl(Person person){ this.person=person; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("买车前开始找车源。。。。"); method.invoke(person, args); System.out.println("买车后办理手续。。。。"); return null; } 复制代码 复制代码} 复制代码
(4)最后一步就是进行测试:
public class Test { public static void main(String[] args) { Myself myself= new Myself(); // 创建代理对象,这里有三个参数,第一个是类的ClassLoader,第二个是该类的接口集合,第三个就是InvocationHandler Object o = Proxy.newProxyInstance(myself.getClass().getClassLoader(), myself.getClass().getInterfaces(), new InvocationHandlerImpl(myself)); Person person= (Person) o; person.buyCar(); } 复制代码 复制代码} 复制代码
整体来说jdk动态代理的应用过程还是比较简单的,重要的实现理解他的底层实现过程,它的重要实现步骤就是InvocationHandler
中 的invoke
方法处理。
invoke方法才是实现方法的调用者,根据上面的参数最后才会创建代理对象newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
。
那么在实现jdk动态代理的过程都做了哪些工作呢?具体有以下6个步骤:
我们可以通过反编译工具来看看生成的代理类的源码是怎么样的,我这里使用的反编译工具是jd-gui
,推荐给大家。
public final class MyselfProxy extends Proxy implements Person { private static Method m1; private static Method m3; private static Method m0; private static Method m2; public MyselfProxy(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final boolean equals(Object paramObject) throws { try { // InvocationHandler 实现equals的调用 return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void buyCar() throws { try { // InvocationHandler实现buyCar的调用 this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() throws { try { // InvocationHandler实现hashCode方法的调用 return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() throws { try { // InvocationHandler实现toString的调用 return (String)this.h.invoke(this, m2, null); } 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.ldc.org.Person").getMethod("buyCar", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } } 复制代码
从上面反编译的源码中可以可以看出,在「静态块中直接通过反射的方式来生成Method对象」,对方法的调用则是通过InvocationHandler
对象来进行调用。
仔细的总结可以看出上面反编译出来的代理类有以下特征:
到这里我想大家应该对jdk的动态代理有一个清晰的认识了,包括他的底层实现的原理,下面我们就来详细的了解cglib动态代理的是实现方式。
在实现jdk的动态代理的实现会发现,「jdk动态代理必须实现一个接口」,并且代理类也「只能代理接口中实现的方法」,要是实现类中有自己私有的方法,而接口中没有的话,该方法不能进行代理调用。
基于这种情况cglib
便出现了,他也可以在运行期扩展Java类和Java接口。
cglib
底层是采用「字节码技术」,其原理是通过字节码技术生成一个子类,并在子类中拦截父类的方法的调用,织入业务逻辑。
因为原理是采用继承的方式,所以被代理的类不能被final修饰,在Spring Aop中底层的实现是以这两种动态代理作为基础进行实现。
当使用cglib动态代理一个类demo时,JVM又做了哪些工作呢?
(1)那么我们通过代码也来实现cglib动态代理,还是创建Myself类,但是此时不需要实现接口:
public class Myself { @Override public void buyCar() { System.out.println("I'm going to buy a house"); } 复制代码 复制代码} 复制代码
(2)然后是创建MyMethodInterceptor
类实现MethodInterceptor
接口,这个和动态代理实现InvocationHandler
方式一样,实现统一方法的调用。
public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("买车前开始找车源。。。。"); proxy.invokeSuper(obj, args); System.out.println("买车后办理手续。。。。"); return null; } 复制代码} 复制代码
(3)最后是进行测试
public class Test { public static void main(String[] args) { Myself myself= new Myself(); MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor (); //cglib 中加强器,用来创建动态代理 Enhancer enhancer = new Enhancer(); //设置要创建的代理类 enhancer.setSuperclass(myself.getClass()); // 设置回调,这里相当于是对于代理类上所有方法的调用 enhancer.setCallback(myMethodInterceptor ); // 创建代理类 Programmer proxy =(Myself)enhancer.create(); proxy.buyCar(); } } 复制代码
总结来说cglib是一个强大的、高性能的Code生产类库,在Spring中就是通过cglib方式继承要被代理的类,重写父类的方法,实现Aop编程。
cglib创建动态代理对象的性能时机要比jdk动态代理的方式高很多,但是创建对象所花的时间却要比jdk动态代理方式多很多。
在应用方面单例模式更适合用cglib,无需频繁的创建对象,相反,则使用jdk动态代理的方式更加合适。