• 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;
            }

    #####

  • 相关阅读:
    使用VS2005搭建典型高效的SharePoint开发环境,提高生产效率,包含远程调试,自动部署 无为而为
    该死的Windows 2003 Server DMA设置,然我刻录DVD这么慢,终于找到办法了 无为而为
    听说Team Foundation Server 繁體中文版於 2006.04.28 RTM ,不知道微软中国的工作做得如何? 无为而为
    [软件开发过程]反模式:简单的部分留在需求人员的脑海中,只描述最复杂的部分给我们听 无为而为
    软件过程改进(SPI)常见反模式:第22条军规 无为而为
    在WebPart中上传图片到SharePoint图片库,读取Exif信息到图片的自定义属性 无为而为
    SQL Server 2005 Service Pack 1正式发布了,我想起,有人说,微软的软件至少要等到出了SP1才能用,那么现在可以用SQL2005了 无为而为
    可以下载Microsoft ISA Server 2006 试用版了,网管需要关注 无为而为
    超女带给我们什么? 无为而为
    Asp.net Url改写方法——采用HttpModules(转)
  • 原文地址:https://www.cnblogs.com/binwenhome/p/13025480.html
Copyright © 2020-2023  润新知