静态代理、动态代理、CGLIB代理实现及原理

文章目录

  • 什么是代理
  • 为什么使用代理
    • 跨网络调用对象
    • 保证类的功能的单一性原则
  • 如何实现代理
    • 静态代理
      • 1. 基于继承的代理
      • 2. 基于接口的代理
    • 动态代理
      • 1. JDK动态代理
      • 2. cglib动态代理实现

什么是代理

    代理分为静态代理和动态代理,在未产生动态代理之前,代理只是为了给某一个类创建一个代理类来为这个类的对象动态添加一些职责和功能。而动态代理产生后,将这种在编译期实现的代理转到了运行时来实现,减少了代理对象和被代理对象之间的一个耦合程度。动态代理可以在运行时创建一个实现了一组给定接口的类,这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。


为什么使用代理

  • 跨网络调用对象

    在程序设计当中,经常会出现客户端无法直接调用实际对象,因为可能客户端需要调用的对象在另外一台机器上,而我们则需要跨网络调用,如果直接调用的话,我们需要处理网络链接、打包和解包等非常复杂的步骤。所以为了简化客户端的处理,采用代理模式,在客户端创建一个代理对象,由代理对象去和实际对象联系。

  • 保证类的功能的单一性原则

    在设计类时,需要尽量保证类的功能单一性。一个类功能的冗余可能会导致两方面的问题:

  1. 对类的单一功能进行改动较为复杂,破坏了面向对象程序设计的一个封装性;
  2. 功能过多造成类加载耗时。在设计程序时,我们需要尽可能保证打开一个程序时不去加载本地不应该加载的一些文件,例如我们想打开word,我们没有必要去加载word之前创建的一个文件或者是一张图片,我们可以为这个文件对象设计一个代理对象,让代理对象去执行文件的打开,而不影响应用程序的加载速度;

如何实现代理

  • 静态代理

    静态代理的实现有两种方式,最为常见也是我们平时最不经意的一种代理实现就是基于继承的代理实现,另一种则是基于接口的代理实现。

1. 基于继承的代理

public class Employee {

    Integer age;

    public Employee(Integer age){
        this.age = age;
    }

    public Integer getAge(){
        System.out.println("Employee getAge");
        return age;
    }

}

public class EmployeeProxy extends Employee {

    public EmployeeProxy(Integer age) {
        super(age);
    }

    @Override
    public Integer getAge() {
        System.out.println("增加功能!!!");
        return super.getAge();
    }
}

public class TestProxy {
    public static void main(String[] args) {
        Employee employee = new EmployeeProxy(23);
        employee.getAge();
    }
}

输出结果:
增加功能!!!
Employee getAge

    我们可以看到,EmployeeProxy类中的getAge()方法实际上最终调用的是Employee的getAge()方法,同时也可以动态为代理类增加功能。

2. 基于接口的代理

public interface FixEmployeeInfo {
    Integer FixAge(Integer age);
}

public class Employee implements FixEmployeeInfo {

    Integer age;

    public Employee(Integer age){
        this.age = age;
    }

    @Override
    public Integer FixAge(Integer age) {
        System.out.println("Employee FixAge");
        return this.age = age;
    }
}

public class EmployeeProxy implements FixEmployeeInfo {

    private FixEmployeeInfo employee;

    @Override
    public Integer FixAge(Integer age) {
        if(employee == null)
            employee = new Employee(age);
        System.out.println("记录修改日期");
        return employee.FixAge(age);
    }
}

public class TestProxy {
    public static void main(String[] args) {
        FixEmployeeInfo employee = new EmployeeProxy();
        employee.FixAge(23);
    }
}

输出结果:
记录修改日期
Employee FixAge

    基于接口的代理实现需要代理对象和被代理对象实现同一个接口,代理类在调用被代理对象的方法的同时还可以动态的增加或者是修改被代理对象的功能,很大程度上增加了程序设计的灵活性。

    静态代理也存在很大的不足。首先他的代理对象只能服务于一种类型的对象,例如Emlpoyee下可能会有Manager或者是staff等,我们要为这两个类型的对象生成代理对象时,只能逐一生成,在代理规模比较大的情况下,很明显静态代理模式就已经不适用了。其次,基于接口实现的代理,由于需要代理类和类实现同一个接口,所以一旦接口方法增加,不仅要改变普通的实现类改变,连代理类也会随之而改变。

  • 动态代理

    动态代理与静态代理不同的是,在实现阶段不用去关心代理类,将编译期间的代理转移到了运行期间。那么此时我们就会想,在运行时JVM是怎样定位代理对象以及怎样定位代理类的方法呢?实际上还是依赖于反射机制。Java中的动态代理分为两种,一种是JDK动态代理,另一种是cglib动态代理。

1. JDK动态代理

    JDK动态代理的实现,主要依赖的是Proxy类的newProxyInstance方法和InvocationHandler类的invoke方法。此时看到invoke()方法后,我们首先会想到反射中对任意方法调用的实现,这也是动态代理的一个核心。JDK动态代理实现如下:

1. 定义代理目标接口(JDk动态代理不能代理类,只能基于接口实现)

public interface FixEmloyeeInfo {
    Integer FixEmployeeInfo(Integer age);
}

2. 定义代理目标类

public class Employee implements FixEmloyeeInfo {

    @Override
    public Integer FixEmployeeInfo(Integer age) {
        System.out.println("执行  FixEmployeeInfo()");
        return age + 1;
    }
}

3. 定义扩展处理类

public class EmployeeHandler implements InvocationHandler {
    private Object target;
    public EmployeeHandler(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        return method.invoke(target , args);
    }
}


4. 测试JDK动态代理

public class TestProxy {
    public static void main(String[] args) {
        FixEmloyeeInfo employee = (FixEmloyeeInfo) Proxy.newProxyInstance(Employee.class.getClassLoader(), Employee.class.getInterfaces(), new EmployeeHandler(new Employee()));
        employee.FixEmployeeInfo(23);
        System.out.println("代理类名:  " + employee.getClass().getName());
        Method[] methods = employee.getClass().getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}
输出结果:
执行  FixEmployeeInfo()
代理类名:  com.sun.proxy.$Proxy0
public final boolean com.sun.proxy.$Proxy0.equals(java.lang.Object)
public final java.lang.String com.sun.proxy.$Proxy0.toString()
public final int com.sun.proxy.$Proxy0.hashCode()
public final java.lang.Integer com.sun.proxy.$Proxy0.FixEmployeeInfo(java.lang.Integer)

    我们从上述的测试可以看出来,JDK动态代理为代理接口生成了一个名为$Proxy0的类,不仅代理了FixEmployeeInfo()方法,还重写了Object类中的一些方法。JDk动态代理的核心和最难点就在于生成代理类,它依赖于Proxy类的newProxyInstance()方法。该方法接受参数为目标类、目标接口类和处理器InvocationHandler实现类:

   public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {};

    在学习动态代理的时候,我们常常遇到了两个难点,分别是代理类是如何生成的和invoke方法是谁调用的,这也是代理实现原理的关键两部分:

    a. 查找或者生成指定的代理类

    生成指定的代理类主要依赖的传入的被代理类的类加载器和被代理类所实现的接口,虽然一个类可以同时实现多个接口,但在生成代理类时首先就会判断实现的接口个数是否超过了65535这个限额,如果超过这个限额则直接会抛出异常。在proxy类中会存在一个loaderToCache缓存变量,JVM所加载的类都会存入这个缓存之中,它的数据结构实际上是一个以ClassLoader为key的map,这也就是我们为什么需要传入被代理类的ClassLoader对象的原因,而在getProxyClass()方法中也会声明一个以interfaceName为key的map结构,value值则存储的是根据ClassLoader从JVM缓存中拿出来的代理类的class对象,具体的缓存结构如下:
静态代理、动态代理、CGLIB代理实现及原理_第1张图片

    代理类对象也并不是与生俱来的,在getProxyClass()方法中,我们会发现在获取代理类对象时,会进行几次判断如下:

    if (proxyClass != null) {  
        return proxyClass;  
    } else if (value == pendingGenerationMarker) {  
        try {  
            cache.wait();  
        } catch (InterruptedException e) {  
        }  
            continue;  
        } else {  
            cache.put(key, pendingGenerationMarker);  
            break;  
        }  
    } while (true);  

这三次判断是判断JVM中是否存在代理类的Class对象,如果存在代理类的Class对象,我们则不需要在创建了,直接从缓存中返回Class 对象即可,如果Class对象正在被创建,则我们需要等待Class对象被创建完成之后才可获取,如果两种可能都不是则直接加载Class对象。而代理类Class对象又是什么时候如何被加载到JVM缓存中的呢?

若要生成代理类class对象,我们只能先生成代理类的字节码文件然后再加载到JVM内存当中,字节码文件的生成很多都是调用了反射中的方法,然后根据接口和目标代理类类名来拼接字节码生成文件并写入本地硬盘。字节码文件生成之后则选取的类加载器就会将字节码文件存入JVM内存当中并生成代理类实例。

    b. 调用InvocationHandler的invoke()方法来拓展代理方法;

    在实现JDK动态代理时,我们发现InvocationHandler的实现类重写了invoke()方法,这个方法是拓展代理方法的关键,而在JDK实现中却没有一个类中调用了invoke()方法。实际上,invoke()方法的调用存在于JDK为我们产生的代理类中,只是在生成代理类时很多时候我们都没有去保存他的class文件,以文章中的JDK动态代理示例,我们可以反编译看一下代理的class文件的源码是怎样的:

public final class $proxy1 extends Proxy implements FixEmloyeeInfo {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $proxy1(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 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 Integer FixEmployeeInfo(Integer var1) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.actchen.demo.JDKProxy.FixEmloyeeInfo").getMethod("FixEmployeeInfo", Class.forName("java.lang.Integer"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

    我们可以清楚的看见,在代理类中存在一个static静态块,而其中就注定了在代理类被加载的时候必须调用四个方法,并且是通过forName来获取类名调用的。

    与此同时,我们也可以看到,代理类继承了Proxy类,这也就造成了JDK动态代理的一个致命缺陷,只能代理实现了接口的类而不能直接代理类,因为Java中不支持多继承,一旦一个类已经存在一个父类之后,只能通过实现接口来拓展代理方法。

2. cglib动态代理实现

    cglib的产生可以说是为了改进JDK动态代理必须实现接口的缺点,cglib是针对类的代理模式,实现cglib必须实现MethodInterceptor接口:

public class Employee  {
    private String name;
    private Integer age;

    final public String fixName(String name){
        this.name = name;
        System.out.println("Employee  fixName !");
        return name;
    }

    public Integer fixAge(Integer age){
        this.age = age;
        System.out.println("Employee fixAge !");
        return age;
    }

}

public class EmployeeInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("EmployeeInterceptor : "+method.getName());
        Object object = methodProxy.invokeSuper(o , objects);
        return object;
    }
}

public class TestCglibProxy {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Employee.class);
        enhancer.setCallback(new EmployeeInterceptor());
        Employee proxy = (Employee) enhancer.create();
        System.out.println(proxy.getClass().getName());
        proxy.fixAge(23);
        proxy.fixName("  ");
    }
}

输出结果:
com.actchen.demo.cglibProxy.Employee$$EnhancerByCGLIB$$ea016430
EmployeeInterceptor : fixAge
Employee fixAge !
Employee  fixName !

    cglib与jdk不同的是,cglib摆脱了对Proxy的依赖,而避免了创建的代理类必须继承Proxy的限制。但cglib也依赖着一个类Enhancer,这个类也是为了生成代理类,但它不仅生成了代理类,还让代理类继承了被代理类,代理类会为委托方法生成两个方法,一个是重写的父类的方法,也就是被代理类的方法,另一个是代理类自身的同名方法,重写方法使用的是super调用了父类的方法,而代理类的方法则是对其的拓展。
cglib是通过Enhancer类来获取代理对象的。在cglib中需要获取代理对象分以下四步:

    1)创建一个Enhancer对象;

    2)调用setSuperclass()方法将被代理的类设置为Enhancer类的父类;

    3)调用setCallback()方法设置Enhancer的回调对象为MethodInterceptor的实现类;

    4)调用create()方法来创建代理对象;

你可能感兴趣的:(JavaSE)