zoukankan      html  css  js  c++  java
  • 设计模式之 ==> 代理设计模式

    一、什么是代理设计模式

      所谓代理,用生活中的事务来做解释的话可以理解为中介,比如:买房、租房,我们都可以找中介,可以省去很多的时间和麻烦。

      在程序编码中的代理,就是用一个代理类将目标类封装起来,我们通过调用代理类中的方法,就可以执行目标类当中的方法,同时,我们在代理类中的方法还能增加一些额外的功能,这样可以实现给目标类的方法扩展新功能而不用修改目标类的代码。代理类和目标类的方法名最好设置得一样,这样的好处是我们使用代理类就和使用目标类是一样的。

    二、代理模式的使用场景

    远程代理,为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。

    虚拟代理,根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。

    安全代理,用来控制真是对象访问时的权限。

    智能指引,当调用真实对象时,代理处理另外一些事。

    三、静态代理

      在 Java 中的代理分为两种,静态代理和动态代理。静态代理就是在源码阶段我们手动的写个代理类将目标类给包装起来,这种方式比较水,因为要自己写代码,最好可以自动生成这个代理类就最好了,于是就有了动态代理,动态代理就是在运行阶段由 JVM 自动生成这个代理类,我们直接用就好。下面我们来演示一下静态代理的写法:

    先来一个接口,描述目标类需要实现的方法

    public interface Animal {
      void eat();
      void run();
    }

    再来是我们的目标类

    public class Dog implements Animal {
    
      @Override
      public void eat() {
        System.out.println("Dog...eat");
      }
    
      @Override
      public void run() {
        System.out.println("Dog...run");
      }
    }

    最后是代理类

    public class DogProxy implements Animal {
    
      private Dog dog;
    
      public DogProxy(Dog dog) {
        this.dog = dog;
      }
    
      @Override
      public void eat() {
        System.out.println("狗开始吃东西了");
        dog.eat();
      }
    
      @Override
      public void run() {
        System.out.println("狗开始跑步了");
        dog.run();
      }
    }

    最后来看一下客户端调用和结果

    public class DogProxy implements Animal {
    
      private Dog dog;
    
      public DogProxy(Dog dog) {
        this.dog = dog;
      }
    
      @Override
      public void eat() {
        System.out.println("狗开始吃东西了");
        dog.eat();
      }
    
      @Override
      public void run() {
        System.out.println("狗开始跑步了");
        dog.run();
      }
    }

    这样,我们如果需要对目标类中的一些方法扩展起来就很容易,甚至不需要知道目标类中的代码实现。但是,这个代理类需要我们自己来写,如果需要对很多目标类来进行扩展,比如说:打印某个包下所有类中方法的执行时间,这样我们就需要写很多结构一致的代理类,这个时候我们可以考虑使用动态代理来实现。

    四、动态代理

      由于静态代理存在着一定的缺陷,使用起来并不方便和友好,所以就有了动态代理,它可以自动生成代理类,不需要我们再自己手写代理类了。Java 中,动态代理有两种:一种是 JDK 的动态代理,另外一种是 CGLIB 动态代理。

    JDK动态代理

    JDK 动态代理除了有目标类和代理类以外,还有一个中间类,这个中间类起什么作用呢?我们先来看一下下面这个图:

      从上图可以看出,代理类中的所有方法实际上都是通过调用中间类(也就是自定义的 handler类)的 invoke() 方法来执行的,而在 invoke() 方法中才是真正去调用目标类的方法,我们对目标类进行的功能扩展就算在中间类的 invoke() 方法中来实现。

      JDK 提供给我们的代理类是 java.lang.reflect.Proxy,我们可以看看这个类中主要的东西:有参构造是传递进去一个 InvocationHandler 类型的参数然后复制给属性h,然后就是 newProxyInstance() 方法,这个方法最主要的是其中的三个参数,第一个参数是类加载器,任意类加载器都行,通常用目标类的类加载器即可。第二个参数是目标类实现的接口,跟静态代理差不多,这里是为了让代理类和目标类的方法名一样;第三个参数是一个 InvocationHandler 类型的参数,注意,这个 h 是我们要自己写代码实现的,而不是上面属性中的那个h。

    下面我们用来实现一下:

    首先,是一个接口

    public interface IInfoService {
    
      String addInfo(String info);
    
      String updateInfo(String info);
    
      String delInfo(String info);
    
      String queryInfo(String info);
    }
    IInfoService

    然后是接口的实现类,也就是被代理的目标类

    public class InfoServiceImpl implements IInfoService {
    
      @Override
      public String addInfo(String info) {
        System.out.println("InfoServiceImpl.addInfo");
        return "InfoServiceImpl.addInfo";
      }
    
      @Override
      public String updateInfo(String info) {
        System.out.println("InfoServiceImpl.updateInfo");
        return "InfoServiceImpl.updateInfo";
      }
    
      @Override
      public String delInfo(String info) {
        System.out.println("InfoServiceImpl.delInfo");
        return "InfoServiceImpl.delInfo";
      }
    
      @Override
      public String queryInfo(String info) {
        System.out.println("InfoServiceImpl.queryInfo");
        return "InfoServiceImpl.queryInfo";
      }
    }
    InfoServiceImpl

    再来是中间类,实现接口 InvocationHandler,给目标类中的方法增加某些功能

    public class CostTimeHandler implements InvocationHandler {
    
      private Object proxyObj;
    
      public CostTimeHandler(Object infoService) {
        this.proxyObj = infoService;
      }
    
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
        long start = System.currentTimeMillis();
        Object response = method.invoke(proxyObj, args);
        long end = System.currentTimeMillis();
        System.out.println(String.format("方法 %s 执行时间为:%s", method.getName(), (end - start)));
        return response;
      }
    }
    CostTimeHandler

    很明显,上面这个中间类的作用是计算目标类方法的执行时间

    最后,我们来增加一个类,通过调用代理类 Proxy 的 newProxyInstance() 方法来生成代理类对象,其中 InvocationHandler 类型的参数由中间类 CostTimeHandler 来替代。

    public class ServiceProxy {
    
      public static <T> T proxyService(T service) {
    
        return (T) Proxy.newProxyInstance(
                service.getClass().getClassLoader(),
                service.getClass().getInterfaces(),
                new CostTimeHandler(service)
        );
      }
    }
    ServiceProxy

    最后来看一下调用和结果:

    public class App {
      public static void main(String[] args) {
        //生成$Proxy0的class文件,也就是代理类的字节码文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        
        IInfoService infoService = ServiceProxy.proxyService(new InfoServiceImpl());
        infoService.addInfo("");
        infoService.delInfo("");
        infoService.updateInfo("");
        infoService.queryInfo("");
      }
    }

    最后,我们来分析一下生成的代理类字节码文件

    package com.sun.proxy;
    
    import com.jack.course.interview.spring.log.service.IInfoService;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    //注意这个代理类的名字$Proxy0,它实现了IInfoService接口,而且还继承Proxy这个类
    public final class $Proxy0 extends Proxy implements IInfoService {
    
      //保存一下目标类中的方法
      private static Method m1;
      private static Method m6;
      private static Method m5;
      private static Method m2;
      private static Method m3;
      private static Method m4;
      private static Method m0;
    
      //此处的静态代码块中就是我们熟悉的反射了,获取方法的Method对象。除了目标类中的四个方法之外,还有equals、toString、hashCode这三个方法
      static {
        try {
          m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
          m6 = Class.forName("com.jack.course.interview.spring.log.service.IInfoService").getMethod("delInfo", Class.forName("java.lang.String"));
          m5 = Class.forName("com.jack.course.interview.spring.log.service.IInfoService").getMethod("updateInfo", Class.forName("java.lang.String"));
          m2 = Class.forName("java.lang.Object").getMethod("toString");
          m3 = Class.forName("com.jack.course.interview.spring.log.service.IInfoService").getMethod("queryInfo", Class.forName("java.lang.String"));
          m4 = Class.forName("com.jack.course.interview.spring.log.service.IInfoService").getMethod("addInfo", Class.forName("java.lang.String"));
          m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
          throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
          throw new NoClassDefFoundError(var3.getMessage());
        }
      }
      
      //这个有参构造方法将InvocationHandler参数传给父类保存起来,也就是那个父类树属性h,方便后面使用这个h
      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 String addInfo(String var1) throws  {
        try {
        /**
         * 注意,此处的invoke方法可不是反射,是调用父类中保存的属性h,其实就是中间类,调用中间类的invoke方法
         * this代表当前代理类的对象,m4表示addInfo()方法对象,new Object[]{var1}其实就是addInfo()方法的参数
         * 现在知道为什么在中间类的invoke方法中可以直接用反射了吧,因为目标类的方法的Method对象,目标类和方法参数都准备好了,不就可以用反射了么....
         * 后面的delInfo、updateInfo和queryInfo这几个方法都差不多
         */
          return (String)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
          throw var3;
        } catch (Throwable var4) {
          throw new UndeclaredThrowableException(var4);
        }
      }
      
      public final String delInfo(String var1) throws  {
        try {
          return (String)super.h.invoke(this, m6, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
          throw var3;
        } catch (Throwable var4) {
          throw new UndeclaredThrowableException(var4);
        }
      }
      
      public final String updateInfo(String var1) throws  {
        try {
          return (String)super.h.invoke(this, m5, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
          throw var3;
        } catch (Throwable var4) {
          throw new UndeclaredThrowableException(var4);
        }
      }
      
      public final String queryInfo(String var1) throws  {
        try {
          return (String)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);
        }
      }
    }

    五、小结

      代理可以避免我们直接和目标类接触、实现解耦,并且有利于目标类的扩展。代理类用起来方式和目标类一样,所以我们在很多框架中即使用了代理,但是我们通常是感觉不出来的。

      还有个问题,上面的JDK动态代理,目标类必须要实现一个或几个接口,假如目标类没有实现接口,就不能使用 JDK 的动态代理了,这时候我们就需要使用到 CGLIB 动态代理。

      CGLib动态代理,这种代理方式刚好弥补了JDK动态代理的缺陷,其实就是生成一个目标类的子类,这个子类就是我们需要的代理类,重写一下父类的所有方法,那么代理类所有方法的名字就和目标类一样了,然后就是反射调用父类的方法。

  • 相关阅读:
    苹果mac shell 终端 命令行快捷键——行首行尾
    mac 编译ffmpeg真简单!
    (2)小彩灯接收数据解析
    JSON数据解析(自写)
    ESP-手机--双向通信模式
    史上最全脉搏心率传感器PulseSensor资料(电路图+中文说明书+最全源代码)
    OpenSCAD 大白
    用OpenSCAD設計特製的遊戲骰子
    如何使用openscad绘制一个简单的键帽.
    OpenSCAD(1)基础教程
  • 原文地址:https://www.cnblogs.com/L-Test/p/13225598.html
Copyright © 2011-2022 走看看