Java动态代理和Cglib动态代理最强王者阵容

前言

上一篇讲解了反射的知识[],做为反射的入门级,而后这一篇主要也是讲解动态代理的实现机制。java

动态代理包括jdk的动态代理cglib 的动态代理,二者实现相同的功能,可是实现方式倒是有明显的区别。程序员

下面咱们就经过代码的方式层层的深刻这两种动态代理,了解他们的性能以、底层的实现原理及应用场景。web

代理模式

在详细介绍动态代理以前,先来讲说Java中的代理模式。代理模式分为两种:编程

  1. 静态代理:也就是23种设计模式中的代理模式,由程序员本身编写源代码并进行编译,在程序运行以前已经编译好了.class文件。
  2. 动态代理:包括jdk的动态代理和cglib的动态代理,运行时经过反射动态建立。

代理模式定义:个人我的理解就是给某一个对象提供一个代理对象,在代理对象中拥有被代理对象的引用,并在代理对象中调用被代理对象的方法以前和以后进行方法的加强。设计模式

我这里画了一张代理模式的类图,设计模式中的代理模式比较简单,代理类和委托类有公共的接口,最后由代理类去执行委托类的方法:编辑器

代理模式就好像生活中的中介,去帮你作事,而不用比本身去作事。举个例子,好比你要买车,可是买车以前你要处处找车源,找到车源给钱了还要办理一堆手续。ide

(1)下面咱们以买车这个案例进行代理模式的代码编写,首先要有一个公共的接口Person,Person接口里面定义公共的方法:函数

public interface Person{
    void buyCar();
}
复制代码

(2)而后定义一个委托类,也就是我本人Myself,并实现Person接口,具体代码以下:工具

性能

public class Myself implements Person {
@Override
public void buyCar() {
    System.out.println("我要买车了");
}
复制代码
复制代码@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("买车后办理手续");
}
复制代码
复制代码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,具体的实现图以下:

看完上面的图彷佛有那么一点点的理解,下面咱们就来详细的深刻动态代理。

jdk动态代理

上面讲解到动态代理是在运行时环境动态加载class文件,并建立对应的class对象,那么动态代理着静态代理的执行时机是在哪里呢?

我这边又画了一张原理图,感受我为画图操碎了心,每个点都会画一个想截图,是否是很暖。

这个是静态代理的运行原理图,静态代理在程序运行时就已经建立好了class文件,在程序启动后的某一个时机(用到class文件)就会加载class文件到内存中。

当在运行时期动态生成class文件并加载class文件的运行原理图以下:

在JVM运行期时遵循JVM字节码的结构和规范生成二进制文件,并加载到内存中生成对应的Class对象。这样,就完成了动态建立class文件和Class对象的功能了。

在jdk的动态代理中的Proxy类和委托类要求实现相同的功能,这里的相同是指他们均可以调用统一的逻辑业务方法。要实现这样的设计有如下三种方法:

  1. 实现同一个接口:接口里面定义公共的方法。
  2. 继承:Proxy继承委托类,这样Proxy就有了和委托类同样的功能,或者二者都继承同一个类,把公共实现业务逻辑的方法放在父类中,这样也能实现。
  3. 二者内部都有同一个类的引用:这个和继承有殊途同归之妙,均可以统一的调用统一的业务逻辑方法。

在jdk的动态代理中,是采用第一种方法进行实现,必须有公共的接口,下面咱们仍是经过静态代理的案例使用动态代理来实现。

(1)首先建立一个公共接口Person:

public interface Person{
    void buyCar();
}
复制代码

(2)而后建立接口的实现类Myself:

public class Myself implements Person {
@Override
public void buyCar() {
    System.out.println("我要买车了");
}
复制代码
复制代码@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;  
}    
复制代码
复制代码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();  
}  
复制代码
复制代码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个步骤:

  1. 获取委托类也就是Myself上的全部接口。
  2. 生成代理,生成的代理的名称也是有规律的,通常是在 com.sun.proxy.$ProxyXXX
  3. 动态建立代理类的字节码信息,也就是class文件。
  4. 根据class文件建立Class对象。
  5. 建立本身的InvocationHandler并实现InvocationHandler重写invoke方法,实现对委托类方法的调用和加强。
  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对象来进行调用。

仔细的总结能够看出上面反编译出来的代理类有如下特征:

  1. 继承 java.lang.reflect.Proxy类,并实现统一的接口Person。
  2. 全部的方法都是 final修饰的。
  3. 都是经过 InvocationHandler对象执行invoke方法的调用统一调用函数,invoke方法经过Method参数来区分是什么方法,进而相应的处理。

到这里我想你们应该对jdk的动态代理有一个清晰的认识了,包括他的底层实现的原理,下面咱们就来详细的了解cglib动态代理的是实现方式。

cglib动态代理

在实现jdk的动态代理的实现会发现,jdk动态代理必须实现一个接口,而且代理类也只能代理接口中实现的方法,要是实现类中有本身私有的方法,而接口中没有的话,该方法不能进行代理调用。

基于这种状况cglib便出现了,他也能够在运行期扩展Java类和Java接口。

cglib底层是采用字节码技术,其原理是经过字节码技术生成一个子类,并在子类中拦截父类的方法的调用,织入业务逻辑。

由于原理是采用继承的方式,因此被代理的类不能被final修饰,在Spring Aop中底层的实现是以这两种动态代理做为基础进行实现。

当使用cglib动态代理一个类demo时,JVM又作了哪些工做呢?

  1. 首先找到demo类中的全部非final的公共方法。
  2. 而后将这些方法转化为字节码。
  3. 经过这些字节码转化为Class对象。
  4. 最后由MethodInterceptor实现代理类中全部方法的调用。

(1)那么咱们经过代码也来实现cglib动态代理,仍是建立Myself类,可是此时不须要实现接口:

public class Myself {
@Override
public void buyCar() {
    System.out.println("I'm going to buy a house");
}
复制代码
复制代码@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动态代理的方式更加合适。

相关文章
相关标签/搜索