从静态代理,jdk动态代理到cglib动态代理-一文搞懂代理模式

代理模式是一种理论上很是简单,可是各类地方的实现每每却很是复杂。本文将从代理模式的基本概念出发,探讨代理模式在java领域的应用与实现。读完本文你将get到如下几点:java

  1. 为何须要代理模式,它一般用来解决什么问题,以及代理模式的设计与实现思路
  2. Java领域中代理模式3种不一样实现类型(静态代理,jdk动态代理,cglib)
  3. 代理模式的面试考点

为何要使用代理模式

在生活中咱们一般是去商场购买东西,而不是去工厂。最主要的缘由可能有如下几种:程序员

  1. 成本过高,去工厂路途遥远成本过高,而且可能从工厂进货要办理一些手续流程;
  2. 工厂不直接卖给你,毕竟可能设计到一些行业机密或者无良厂家有一些不想让你知道的东西;
  3. 商场能提供一些商品以外的服务,商场里有温馨的温度,整洁的洗手间,固然还有漂亮的小姐姐。

在面向对象的系统中也有一样的问题,有些对象因为某种缘由,好比对象建立开销很大,或者某些操做须要安全控制等,直接访问会给使用者或者系统结构带来不少麻烦,这时咱们就须要考虑使用代理模式面试

在应用中咱们可能会用代理模式解决如下问题:spring

  1. 权限控制与日志, 在客户端请求接口时咱们可能须要在调用以前对权限进行验证,或者经过记录接口调用先后时间,统计执行时长,又或者说咱们须要记录用户的一些操做日志信息等,咱们能够对原接口进行代理,而后根据需求在接口执行先后增长一些特定的操做。
  2. 重量级操做, 好比建立开销大的对象, 能够先由代理对象扮演对象的替身,在须要的使用再建立对象,而后代理再将请求委托给真实的对象。

什么是代理模式

代理模式:为其余对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。类图以下:编程

所谓控制,其实使用接口隔离其余对象与这个对象之间的交互;就是为client对象对RealSubject对象的访问一种隔离,本质上就是CLient→RealSuject的关系变成了Client→Subject, Proxy→RealSubject。 须要注意的时,代理类(Proxy)并不必定要求保持接口的完整的一致性(既也能够彻底不需实现Subject接口),只要可以实现间接控制便可。安全

代理模式代码演进

背景:假设已有一个订单系统,能够保存订单信息。cookie

需求:打印保存订单信息消耗时间。ide

/**
 * 订单服务
 *
 * @author cruder
 * @date 2019-11-23 15:42
 **/
public class OrderService2 {
    /**
     * 保存订单接口
     */
    public void saveOrder(String orderInfo) throws InterruptedException {
        // 随机休眠,模拟订单保存须要的时间
        Thread.sleep(System.currentTimeMillis() & 100);
        System.out.println("订单:" + orderInfo + "  保存成功");
    }
}

普通方式实现

直接修改源代码,这一般也是最简单和最容易想到的实现。函数

/**
  * 保存订单接口, 直接修改代码
  */
 public void saveOrder(String orderInfo) throws InterruptedException {
 
     long start = System.currentTimeMillis();
 
     // 随机休眠,模拟订单保存须要的时间
     Thread.sleep(System.currentTimeMillis() & 100);
     System.out.println("订单:" + orderInfo + "  保存成功");
 
     System.out.println("保存订单用时: " + (System.currentTimeMillis() - start) + "ms");
 }

面向对象设计原则中的“开闭原则”告诉咱们,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,可是对于修改是封闭的”,这意味着一个实体是容许在不改变它的源代码的前提下变动它的行为。工具

代理模式实现

/**
 * 1. 定义接口,为了使代理被代理对象看起来同样。固然这一步彻底能够省略
 *
 * @author cruder
 * @date 2019-11-23 15:58
 **/
public interface IOrderService {
    /**
     * 保存订单接口
     * @param orderInfo 订单信息
     */
    void saveOrder(String orderInfo) throws InterruptedException;
}
/**
 * 2. 原有订单服务,也实现这个接口。注意 此步骤也彻底能够省略。
 *
 * @author cruder
 * @date 2019-11-23 15:42
 **/
public class OrderService implements IOrderService{
    /**
     * 保存订单接口
     */
    @Override
    public void saveOrder(String orderInfo) throws InterruptedException {
        // 随机休眠,模拟订单保存须要的时间
        Thread.sleep(System.currentTimeMillis() & 100);
        System.out.println("订单:" + orderInfo + "  保存成功");
    }
}


/**
 * 3. 建立代理类,实现订单服务接口【这才是代理模式的实现】
 * 
 * @author cruder
 * @date 2019-11-23 16:01
 **/
public class OrderServiceProxy implements IOrderService{
    /**
     * 内部持有真实的订单服务对象,保存订单工做实际由它来完成
     */
    private IOrderService orderService;

    @Override
    public void saveOrder(String orderInfo) throws InterruptedException {
        /**
         * 延迟初始化,也能够建立代理对象时就建立,或者做为构造参数传进来
         * 仅做为代码实例,不考虑线程安全问题
         */
        if (orderService == null) {
            orderService = new OrderService();
        }

        long start = System.currentTimeMillis();
        orderService.saveOrder(orderInfo);
        System.out.println("保存订单用时: " + (System.currentTimeMillis() - start) + "ms");
    }
}执行程序

执行程序

代理模式的优缺点

优势: 一、职责清晰。 二、高扩展性。 三、智能化。

缺点:

一、因为在客户端和真实主题之间增长了代理对象,所以有些类型的代理模式可能会形成请求的处理速度变慢。 二、实现代理模式须要额外的工做,有些代理模式的实现很是复杂。

Java中代理模式的实现

在java中代理模式能够按照代理类的建立时机分两类,即静态代理和动态代理,而动态代理又能够分为jdk动态代理和cglib动态代理。每种实现方式都各有千秋,接下来笔者将回针对不一样的实现方式进行演示和剖析。

静态代理

在上文代理模式代码演进中就使用了静态代理模式。所谓静态代理中的“静”字,无非就是代理类的建立时机不一样罢了。静态代理须要为每一个被代理的对象手动建立一个代理类;而动态代理则时在运行时经过某种机制来动态生成,不须要手动建立代理类。

动态代理 - jdk

jdk动态代理模式是利用java中的反射技术,在运行时动态建立代理类。接下来咱们仍借助上文中的订单服务的案例,使用jdk动态代理实现。

基于动态jdk涉及到两个核心的类Proxy类和一个 InvocationHandler接口。

/**
 * 基于JDK技术 动态代理类技术核心 Proxy类和一个 InvocationHandler 接口
 *
 * @author cruder
 * @date 2019-11-23 16:40
 **/
public class ProxyFactory implements InvocationHandler {

    /**
     * 委托对象,既被代理的对象
     */
    private Object target;

    public ProxyFactory (Object target) {
        this.target = target;
    }

    /**
     * 生成代理对象
     * 1. Classloader loader: 制定当前被代理对象使用的累加子啊其,获取加载器的方法固定
     * 2. Class<?>[] interfaces: 委托类的接口类型,使用泛型方法确认类型
     * 3. InvocationHandler handler: 事件处理,执行委托对象的方法时会触发事件处理器方法,
     * 会把当前执行的委托对象方法做为参数传入
     */
    public Object getProxyInstance() {
        Class clazz = target.getClass();

        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        method.invoke(target, args);
        System.out.println("保存订单用时: " + (System.currentTimeMillis() - start) + "ms");
        return null;
    }
}

/**
 * 经过动态代理方式来保存订单
 *
 * @author cruder
 * @date 2019-11-23 15:49
 **/
public class Client {
    public static void main(String[] args) throws InterruptedException {
        ProxyFactory proxyFactory= new ProxyFactory (new OrderService());
        IOrderService orderService = (IOrderService) proxyFactory.getProxyInstance();
        orderService.saveOrder(" cruder 新买的花裤衩 ");
    }
}

以上即是jdk动态代理的所有实现,有种只可意会不可言传的感受,笔者始终感受这种实现看起来很别扭。不过也要强行总结如下,jdk实现动态代理能够分为如下几个步骤:

  1. 先检查委托类是否实现了相应接口,保证被访问方法在接口中也要有定义
  2. 建立一个实现InvocationHandler接口的类
  3. 在类中定义一个被代理对象的成员属性,为了扩展方即可以直接使用Object类,也能够根据需求定义相应的接口
  4. 在invoke方法中实现对委托对象的调用,根据需求对方法进行加强
  5. 使用Proxy.newProxyInstance(...)方法建立代理对象,并提供要给获取代理对象的方法

代理类源码阅读

上文中基于jdk动态代理的代码实现中对于可*的产品经理来讲已经彻底知足了需求,可是对于具备Geek精神的程序员来讲这远远不够,对于这种不知其因此然的东西每每让人感到不安。接下来咱们将经过自定义的一个小工具类将动态生成的代理类保存到本地来一看究竟。

/**
 * 将生成的代理类保存为.class文件的工具类
 *
 * @author cruder
 * @date 2019-08-15 0:27
 */
public class ProxyUtils {
    /**
     * 将代理类保存到指定路径
     *
     * @param path           保存到的路径
     * @param proxyClassName 代理类的Class名称
     * @param interfaces     代理类接口
     * @return
     */
    public static boolean saveProxyClass(String path, String proxyClassName, Class[] interfaces){
        if (proxyClassName == null || path == null) {
            return false;
        }
        // 获取文件字节码,而后输出到目标文件中
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyClassName, interfaces);
        try (FileOutputStream out = new FileOutputStream(path)) {
            out.write(classFile);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

// 此处是重点, 生成的代理类实现了IOrderService,而且继承了Proxy
public final class $Proxy0 extends Proxy implements IOrderService {
    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 void saveOrder(Order var1) throws  {
        try {
            super.h.invoke(this, m3, 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 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 {
           // 经过反射获取Method对象
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("cn.mycookies.test08proxy.IOrderService").getMethod("saveOrder", Class.forName("cn.mycookies.test08proxy.Order"));
            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());
        }
    }
}

ps: 实习转正面试中被问到为何jdk动态代理被代理的类为何要实现接口?

cglib动态代理

对于cglib我想大多数人应该都很陌生,或者是在学习Spring中AOP(面向切面编程)时据说了它使用jdk和cglib两种方式实现了动态代理。接下来笔者将针对cglib进行简要介绍。

cglib动态代理和jdk动态代理相似,也是采用操做字节码机制,在运行时生成代理类。cglib 动态代理采起的是建立目标类的子类的方式,由于是子类化,咱们能够达到近似使用被调用者自己的效果。

字节码处理机制-指得是ASM来转换字节码并生成新的类

注:spring中有完整的cglib相关的依赖,因此如下代码基于spring官方下载的demo中直接进行编写的

/**
 * 1. 订单服务-委托类,不须要再实现接口
 *
 * @author cruder
 * @date 2019-11-23 15:42
 **/
public class OrderService {
    /**
     * 保存订单接口
     */
    public void saveOrder(String orderInfo) throws InterruptedException {
        // 随机休眠,模拟订单保存须要的时间
        Thread.sleep(System.currentTimeMillis() & 100);
        System.out.println("订单:" + orderInfo + "  保存成功");
    }
}

/**
 * cglib动态代理工厂
 *
 * @author cruder
 * @date 2019-11-23 18:36
 **/
public class ProxyFactory implements MethodInterceptor {

    /**
     * 委托对象, 即被代理对象
      */
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 返回一个代理对象
     * @return
     */
    public Object getProxyInstance(){
        // 1. 建立一个工具类
        Enhancer enhancer = new Enhancer();
        // 2. 设置父类
        enhancer.setSuperclass(target.getClass());
        // 3. 设置回调函数
        enhancer.setCallback(this);
        // 4.建立子类对象,即代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        long start = System.currentTimeMillis();

        Object result = method.invoke(target, args);

        System.out.println("cglib代理:保存订单用时: " + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}

/**
 * 使用cglib代理类来保存订单
 *
 * @author cruder
 * @date 2019-11-23 15:49
 **/
public class Client {
    public static void main(String[] args) throws InterruptedException {
        // 1. 建立委托对象
        OrderService orderService = new OrderService();
        // 2. 获取代理对象
        OrderService orderServiceProxy = (OrderService) new ProxyFactory(orderService).getProxyInstance();
        String saveFileName = "CglibOrderServiceDynamicProxy.class";
        ProxyUtils.saveProxyClass(saveFileName, orderService.getClass().getSimpleName(), new Class[]{IOrderService.class});
        orderServiceProxy.saveOrder(" cruder 新买的花裤衩 ");
    }
}

cglib动态代理实现步骤和jdk及其类似,能够分为如下几个步骤:

  1. 建立一个实现MethodInterceptor接口的类
  2. 在类中定义一个被代理对象的成员属性,为了扩展方即可以直接使用Object类,也能够根据需求定义相应的接口
  3. 在invoke方法中实现对委托对象的调用,根据需求对方法进行加强
  4. 使用Enhancer建立生成代理对象,并提供要给获取代理对象的方法

cglib动态代理生成的代理类和jdk动态代理代码格式上几乎没有什么区别,惟一的区别在于cglib生成的代理类继承了仅仅Proxy类,而jdk动态代理生成的代理类继承了Proxy类的同时也实现了一个接口。代码以下:

// 生成一个Proxy的子类
public final class OrderService extends Proxy {
    private static Method m1;
    private static Method m2;
    private static Method m0;

    public OrderService(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 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");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

jdk动态代理 VS cglib

JDK Proxy 的优点:

  • 最小化依赖关系,减小依赖意味着简化开发和维护,JDK 自己的支持,可能比 cglib 更加可靠。
  • 平滑进行 JDK 版本升级,而字节码类库一般须要进行更新以保证在新版 Java 上可以使用。
  • 代码实现简单。

cglib 优点:

  • 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,相似 cglib 动态代理就没有这种限制。
  • 只操做咱们关心的类,而没必要为其余相关类增长工做量。

总结

  1. 代理模式: 为其余对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。
  2. jdk动态代理生成的代理类继承了Proxy类并实现了被代理的接口;而cglib生成的代理类则仅继承了Proxy类。
  3. jdk动态代理最大缺点:只能代理接口,既委托类必须实现相应的接口
  4. cglib缺点:因为是经过“子类化”的方式, 因此不能代理final的委托类或者普通委托类的final修饰的方法。

Q&A

  1. 为何jdk动态代理只能代理接口?
  2. Spring中AOP的实现采用那种代理方式?
  3. 都说jdk动态代理性能远比cglib要差,若是是,依据是什么?
相关文章
相关标签/搜索