Java动态代理

代理模式

  1. 定义: 给目标对象提供一个代理对象, 并由代理对象控制对目标对象的引用.
  2. 目的
    • 通过引入代理对象的方式来间接访问目标对象, 防止直接访问目标对象给系统带来的不必要的复杂性.
    • 通过代理对象对原有的业务增强.
  3. 举例说明, 我们想买进口牛奶喝, 不使用代理模式, 就相当于我们自己坐飞机到国外购买牛奶带回来喝. 而使用代理模式就相当于找了代购帮我们买.
    • 这里真实类和代理类都必须实现公共接口.
    • 同时, 需要在代理类中包含真实类.

静态代理

  1. 只适用于简单的情况
    • 抽象接口
      //抽象接口, 描述了服务提供者的行为
      public interface ToolsFactory {
      
          void saleMilk(Integer num);
      
      }
    • 代理类
      //代理对象, 包含真实的对象, 为真实对象的服务进行增强, 和真实对象继承同一个接口
      public class ProxyMan implements ToolsFactory {
      
          //被包含的真实对象
          private ToolsFactory factory;
          private WomanToolsFactory wFactory;
      
          public ProxyMan(ToolsFactory factory) {
              super();
              this.factory = factory;
          }
      
          @Override
          public void saleMilk(Integer num) {
              dosomeThingBefore();
              factory.saleMilk(num);
              dosomeThingEnd();
          }
      
          private void dosomeThingBefore() {
              System.out.println("根据您的需求, 进行市场调研和产品分析");
          }
      
          private void dosomeThingEnd() {
              System.out.println("为您免费发货");
          }
      }
    • 真实类
      public class AFactory implements ToolsFactory {
      
          @Override
          public void saleMilk(Integer num) {
              System.out.println("购买了" + num + "盒牛奶");
          }
      }
    • 测试
      public static void main(String[] args) {
              ToolsFactory factory = new AFactory();
              ProxyMan proxy = new ProxyMan(factory);
              proxy.saleMilk(5);
          }
  2. 此时, 业务拓宽了, 不只代购牛奶了, 又开始代购奶粉, 马桶, 手表...., 此时就会引发出一个非常致命的缺陷.
  3. 即静态代理违反了开闭原则
    • 开闭原则: 程序对外扩展开发, 对修改关闭, 换句话说, 当需求发生变化时, 我们可以通过添加新模块来满足新需求, 而不是通过修改原有代码满足新需求.
    • 违反了开闭原则
      • 扩展性差: 要在原有代码上添加代购奶粉, 手表的代码.
      • 可维护性差: 现在不按盒买了, 按斤买, 又要修改代码.

动态代理

  1. 由于业务的扩大, 我们开了一家外贸公司, 这时, 你想买什么东西, 我们就会派出专门的代购员帮你采购.
  2. 动态代理中的Proxy, 就相当于代购员, 而我们的外贸公司需要有优质的代购服务, 即需要实现InvocationHandler接口中的invoke()方法.
  3. 代码
    • 要实现的业务
      //A业务
      public interface ToolsFactory {
      
          void saleMilk(Integer num);
      
      }
      
      public class AFactory implements ToolsFactory {
      
          @Override
          public void saleMilk(Integer num) {
              System.out.println("购买了" + num + "盒牛奶");
          }
      }
      
      //B业务
      public interface BToolsFactory {
      
          void saleWatch(String brand);
      }
      
      public class BFactory implements BToolsFactory {
      
          @Override
          public void saleWatch(String brand) {
              System.out.println("为您选购" + brand + "品牌的手表");
          }
      }
    • 动态代理类
      public class Company implements InvocationHandler {
      
          //被代理的对象
          private Object factory;
      
          public Object getFactory() {
              return factory;
          }
      
          public void setFactory(Object factory) {
              this.factory = factory;
          }
      
          //通过Proxy获取动态代理的对象
          public Object getProxyInstance() {
              //类加载器, 接口, 当前对象
              return Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), this);
          }
      
          @Override
          //通过动态代理对象对方法进行增强.
          /**
           * proxy: 代理对象
           * method: 要优化, 拦截的方法
           */
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              dosomeThingBefore();
              Object ret = method.invoke(factory, args);
              dosomeThingAfter();
              return ret;
          }
      
          private void dosomeThingBefore() {
              System.out.println("根据您的需求, 进行市场调研和产品分析");
          }
      
          private void dosomeThingAfter() {
              System.out.println("免费配送");
          }
      }
    • 测试
      public static void main(String[] args) {
      
              ToolsFactory aFactory = new AFactory();
              BToolsFactory bFactory = new BFactory();
              Company company = new Company();
              company.setFactory(aFactory);
              ToolsFactory one = (ToolsFactory) company.getProxyInstance();
              one.saleMilk(5);
              System.out.println("====================================================");
      
              company.setFactory(bFactory);
              BToolsFactory two = (BToolsFactory) company.getProxyInstance();
              two.saleWatch("Glashutte");
          }
  4. 这里我们可以看到动态代理的一个好处, 即符合单一职责原则
    • 单一职责原则: 一个类或接口只负责一项职责, 尽量设计出功能单一的接口.

深入分析动态代理

  1. 我们debug上面的测试类, 用company.getProxyInstance()创建的one, two,  它们的类名竟然是$Proxy0, $Proxy1, 但我们并没有创建这些类啊! 是不是动态生成的呢?
  2. 抱着这个疑问, 我们首先看一下一个类的完整生命周期
    • javac命令编译成 .class文件
    • 类加载器加载这些字节码文件生成Class对象
      • Class对象保存在JVM内存模型的方法区(元空间)中.
    • 之后通过new的方式就能生成实例对象(在堆中)了.
    • 当某个实例对象不会被引用到的时候, 就会被gc卸载了.
  3. 实际上, 动态代理忽略了第一步, 即没有Java源文件, 直接生成了字节码文件.
    • 字节码的来源有两个
      • 硬盘(Java源文件编译)
      • JVM在内存中直接生成字节码文件.
  • 怎么在内存中生成的?
    • 我们从newProxyInstance()方法为始, 读一下源码
    • 下述代码在JVM中生成了字节码, 把这个字节码加载完后再生成class对象.
      /*
               * Look up or generate the designated proxy class.
               */
              Class<?> cl = getProxyClass0(loader, intfs);
    • 这行代码之后就是利用反射new出实例了.
    • 我们再来看一下getProxyClass0()这个方法
      private static Class<?> getProxyClass0(ClassLoader loader,
                                                 Class<?>... interfaces) {
              if (interfaces.length > 65535) {
                  throw new IllegalArgumentException("interface limit exceeded");
              }
      
              // If the proxy class defined by the given loader implementing
              // the given interfaces exists, this will simply return the cached copy;
              // otherwise, it will create the proxy class via the ProxyClassFactory
              return proxyClassCache.get(loader, interfaces);
          }
      
      首先看看接口超没超过65535, 之后进入proxyClassCache.get()方法
    • 再看看 proxyClassCache.get()
      public V get(K key, P parameter) {
              Objects.requireNonNull(parameter);
      
              expungeStaleEntries();
      
              Object cacheKey = CacheKey.valueOf(key, refQueue);
      
              // lazily install the 2nd level valuesMap for the particular cacheKey
              ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
              if (valuesMap == null) {
                  ConcurrentMap<Object, Supplier<V>> oldValuesMap
                      = map.putIfAbsent(cacheKey,
                                        valuesMap = new ConcurrentHashMap<>());
                  if (oldValuesMap != null) {
                      valuesMap = oldValuesMap;
                  }
              }
      
      前面这些代码是从缓冲中加载代理类的.
    • 要是没拿到的话, 就要自己生成代理类了
      Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

      看一下这个apply方法 在Proxy类的私有类ProxyClassFactory中
      @Override
      public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    • 首先要拿到接口的对象, 判断是不是真的接口, 有没有冲突, 加没加public修饰符.
    • 之后从一个计数器中拿到一个数字, 拼接出proxyName, 
      /*
      * Choose a name for the proxy class to generate.
      */
      long num = nextUniqueNumber.getAndIncrement();
      String proxyName = proxyPkg + proxyClassNamePrefix + num;
      
      我们看一下前缀proxyClassNamePrefix, 原来我们的$Proxy0, $Proxy1就是这样来的.
      private static final String proxyClassNamePrefix = "$Proxy";
    • 之后把类名, 接口传进ProxyGenerator.gerateProxyClalss()方法生成一个byte[]数组, 这个byte数组其实就是前面所说的在内存中生成的字节码文件.
      byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
    • 接着通过该字节码文件生成Class对象(defineClass0是一个native本地方法..)
      return defineClass0(loader, proxyName,
                  proxyClassFile, 0, proxyClassFile.length);
  • 生成的Class文件结构是什么?
  1. 我们先编写工具类
    public class ProxyUtils {
    
        /**
         * 将根据类信息动态生成的二进制字节码保存到硬盘中
         *  clazz: 需要生成动态代理类的类
         * @param proxyName 为动态生成的代理类的名字
         */
        public static void generateClassFile(Class clazz, String proxyName) {
    
            //根据类信息和提供的代理类名, 生成字节码
            byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, new Class[]{clazz});
    
            String paths = clazz.getResource(".").getPath();
            System.out.println(paths);
            FileOutputStream out = null;
    
            try {
                //保存到硬盘中
                out = new FileOutputStream(paths + proxyName + ".class");
                out.write(classFile);
                out.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
  2. 生成$Proxy0字节码文件
        public static void main(String[] args) {
    
            ToolsFactory aFactory = new AFactory();
            BToolsFactory bFactory = new BFactory();
            Company company = new Company();
            company.setFactory(aFactory);
            ToolsFactory one = (ToolsFactory) company.getProxyInstance();
            one.saleMilk(5);
    
            ProxyUtils.generateClassFile(aFactory.getClass(), one.getClass().getSimpleName());
        }
  3. 用反编译工具打开$Proxy0.class, 我们发现如下代码
      public final void saleMilk(Integer paramInteger)
      {
        try
        {
          this.h.invoke(this, m3, new Object[] { paramInteger });
          return;
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
      }
  4. 这里的h是: protected InvocationHandler h;
    • 也正是在这里, 调用了我们Company里的invoke方法, 完成对原有方法的增强
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              dosomeThingBefore();
              Object ret = method.invoke(factory, args);
              dosomeThingAfter();
              return ret;
          }

#####

 

posted @ 2020-06-02 16:17  yellowstreak  阅读(168)  评论(0编辑  收藏  举报