• java中的代理模式


    代理模式的原理

    使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

    简单例子:

    小强意外成为了一个网红明星,此时他请了一个专业的经纪人进行各种事务的管理,小强的演出活动啊都得经过经纪人的同意。此时小强是被代理对象,经纪人是代理对象。

    此时快乐大本营想请小强去参加节目,需要找到小强的经纪人,小强的经纪人然后进行排挡,找小强去完成唱歌,才艺展示等动作。

    静态代理

    代理对象和被代理对象(目标对象)实现同一接口。优点是不用修改目标对象的源码,扩展其功能。

    特点

    • 在编译期间,代理类和被代理对象(目标对象)的类都确定下来了,不利于程序的扩展
    • 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。

    举例

    创建一个接口,这个接口是代理工厂(代理类)和Nike工厂(被代理类)的公共接口,

    通过代理工厂的方法调用同时也调用了Nike工厂的同名方法,完成不修改被代理类的源码而扩展其功能。

    共同接口

    interface ClothFactory{
        void produceCloth();
    }
    

    代理类

    // 代理类
    class ProxyClothFactory implements ClothFactory {
        private ClothFactory factory;//用被代理类对象进行实例化
    
        public ProxyClothFactory(ClothFactory factory) {
            this.factory = factory;
        }
    
        @Override
        public void produceCloth() {
            System.out.println("代理工厂做一些准备工作");
            factory.produceCloth();
            System.out.println("代理工厂做一些后续的工作");
        }
    }
    

    被代理类

    // 被代理类
    class NikeClothFactory implements ClothFactory{
    
        @Override
        public void produceCloth() {
            System.out.println("nike工厂生产运动服");
        }
    }
    

    测试代码

    public class StaticProxyTest {
        public static void main(String[] args) {
    
            // 创建被代理类对象
            NikeClothFactory nikeClothFactory = new NikeClothFactory();
            // 创建代理类对象
            ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nikeClothFactory);
            proxyClothFactory.produceCloth();
        }
    }
    

    输出结果

    代理工厂做一些准备工作
    nike工厂生产运动服
    代理工厂做一些后续的工作

    动态代理

    动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

    特点

    代理对象不需要实现接口,但是要求被代理对象(目标对象)必须实现接口,否则不能使用动态代理。

    相关API

    Proxy:专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。

    static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) 直接创建一个动态代理对象

    参数详解:

    • ClassLoader loader:和被代理对象使用相同的类加载器
    • Class<?>[] interfaces:和被代理对象具有相同的行为,实现相同的接口
    • InvocationHandler h:得到InvocationHandler接口的实现类实例

    实现动态代理

    实现动态代理,要解决的问题

    1. 如何根据加载到内存中的被代理类,动态创建一个代理类及其对象;
    2. 当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法

    具体步骤如下:

    1. 创建被代理类和接口

      接口:Human接口

      //共同接口
      interface Human{
          String getBelief();
          void eat(String food);
      }
      

      被代理类:SuperMan类

      // 被代理类
      class SuperMan implements Human{
      
          @Override
          public String getBelief(){
              return "我能飞";
          }
          @Override
          public void eat(String food){
              System.out.println("我喜欢吃"+food);
          }
      }
      
    2. 创建一个用来生产代理类的工厂,该工厂有一个静态方法getProxyInstance可以返回一个代理类的对象,该方法需传入被代理类对象。

      class ProxyFactory{
          // 调用此方法,返回一个代理类的对象。需要传入被代理类对象
          public static Object getProxyInstance(Object obj){
      
              MyInvocationHandler handler = new MyInvocationHandler();
              handler.bind(obj);
              // 哪个类加载的?被代理类
              // 被代理类实现了哪个接口?得到被代理类实现的全部接口
              // 得到InvocationHandler接口的实现类实例
              return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
          }
      }
      
    3. 创建一个实现接口InvocationHandler的类,实现inoke方法,以完成代理的具体操作。当我们通过代理类的对象调用方法a时,就会自动的调用如下方法invoke(),将被代理类要执行的方法a的功能声明在invoke()中。

      class MyInvocationHandler implements InvocationHandler{
          private Object obj;//需要使用被代理类的对象进行赋值
          public void bind(Object obj){
              this.obj = obj;
          }
          
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              // obj:被代理类对象
              // method:即为代理类对象调用的方法,此方法作为被代理类对象要调用的方法
              // args: 方法调用时所需要的参数
              Object returnValue = method.invoke(obj,args);
              //该返回值作为当前类中的invoke()的返回值
              return returnValue;
          }
      }
      
    4. 测试类

      public class ProxyTest {
          public static void main(String[] args) {
              SuperMan superMan = new SuperMan();//创建一个被代理类对象
              // 动态创建代理类
              Human proxyInstance = (Human)ProxyFactory.getProxyInstance(superMan);
              
              String belief = proxyInstance.getBelief();
              System.out.println(belief);
              proxyInstance.eat("肉夹馍");
          }
      }
      
    5. 输出结果

    我们发现,只需要知道接口和被代理类,就能实现创建一个代理类。把上面静态代理部分的例子拿过来

    NikeClothFactory nike = new NikeClothFactory();
    ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nike);
    proxyInstance1.produceCloth(); //nike工厂生产运动服
    

    动态代理实现两种方式

    动态代理

    特点:字节码随用随创建,随用随加载;
    作用:不修改源码的基础上对方法增强。

    基于接口的动态代理

    提供者: JDK 官方的 Proxy 类。
    要求:被代理类最少实现一个接口。

    实现步骤

    1. 创建一个接口,该接口可以理解成对生产厂家的规范

      /**
       * 对生产厂家要求的接口
       */
      public interface IProducer {
      
          /**
           * 销售
           * @param money
           */
          public void saleProduct(float money);
      
          /**
           * 售后
           * @param money
           */
          public void afterService(float money);
      }
      

      代理类:是一个生产者,它实现了接口,具有接口中的方法,也就是说符合生产厂家的要求,产品销售和售后是ok的。

      /**
       * 一个生产者
       */
      public class Producer implements IProducer{
          /**
           * 销售
           * @param money
           */
          public void saleProduct(float money){
              System.out.println("销售产品,并拿到钱:"+money);
          }
          /**
           * 售后
           * @param money
           */
          public void afterService(float money){
              System.out.println("提供售后服务,并拿到钱:"+money);
          }
      }
      
    2. 模拟一个消费者,如果该消费者如果没有通过代理商去买电脑,而是直接去到厂家处购买。但现实中,很少有人这样购买。一般都是厂家把产品交给代理商进行销售以及提供售后服务,顾客只需跟代理商进行交易,而不直接与厂家进行联系。

      没有代理之前

      Producer producer1 = new Producer();
      producer.saleProduct(10000f);//销售产品,并拿到钱:10000.0
      

      有了代理,代理要从中提取20%的手续费。那么生产者只能得到80%的钱

      public class Client {
          public static void main(String[] args) {
              final Producer producer = new Producer();
              IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                      producer.getClass().getInterfaces(),
                      new InvocationHandler() {
                          @Override
                          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                              //提供增强的代码
                              Object returnValue = null;
      
                              //1.获取方法执行的参数
                              Float money = (Float)args[0];
                              //2.判断当前方法是不是销售
                              if("saleProduct".equals(method.getName())) {
                                  returnValue = method.invoke(producer, money*0.8f);
                              }
                              return returnValue;
                          }
                      });
              proxyProducer.saleProduct(10000f);
          }
      }
      

      涉及的参数:

      Proxy类

      public static Object newProxyInstance(ClassLoader loader,
                                                Class<?>[] interfaces,
                                                InvocationHandler h)
      ClassLoader:类加载器 代理谁写谁的类加载器,它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
      Class[]:字节码数组,它是用于让代理对象和被代理对象有相同方法。固定写法。
      InvocationHandler:用于提供增强的代码
      它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。
      

      InvocationHandler接口

      public Object invoke(Object proxy, Method method, Object[] args) 
      作用:执行被代理对象的任何接口方法都会经过该方法
      方法参数的含义
      proxy   代理对象的引用
      method  当前执行的方法
      args    当前执行方法所需的参数
      

    基于子类的动态代理

    提供者:第三方的 cglib。
    要求:被代理类不能是最终类(用 final 修饰的类)。

    涉及的类:Enhancer

    如何创建代理对象:使用Enhancer类中的create(Class,Callback)方法

    create方法的参数:

    Class:字节码,它是用于指定被代理对象的字节码。

    Callback:如何代理,即就是用于提供增强的代码。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。我们一般写的都是该接口的子接口实现类:MethodInterceptor

    还是消费者买产品的例子,只不过不让它实现接口。

    举例:

    生产者

    /**
     * 一个生产者
     */
    public class Producer {
    
        /**
         * 销售
         * @param money
         */
        public void saleProduct(float money){
            System.out.println("销售产品,并拿到钱:"+money);
        }
    
        /**
         * 售后
         * @param money
         */
        public void afterService(float money){
            System.out.println("提供售后服务,并拿到钱:"+money);
        }
    }
    

    消费者

    /**
     * 模拟一个消费者
     */
    public class Client {
    
        public static void main(String[] args) {
            final Producer producer = new Producer();
            Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
                /**
                 * 执行被代理对象的任何方法都会经过该方法
                 * @param proxy
                 * @param method
                 * @param args
                 *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
                 * @param methodProxy :当前执行方法的代理对象
                 * @return
                 * @throws Throwable
                 */
                @Override
                public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    //提供增强的代码
                    Object returnValue = null;
    
                    //1.获取方法执行的参数
                    Float money = (Float)args[0];
                    //2.判断当前方法是不是销售
                    if("saleProduct".equals(method.getName())) {
                        returnValue = method.invoke(producer, money*0.8f);
                    }
                    return returnValue;
                }
            });
            cglibProducer.saleProduct(12000f);
        }
    }
    

    参考链接:

    https://blog.csdn.net/briblue/article/details/73928350

  • 相关阅读:
    ubuntu 14.04 安装gvim 后报出warning
    ubuntu 如何搭建svn 服务器
    如何为wordpress 添加favicon
    如何为wordpress 的文章添加分页
    ubuntu 如何添加alias
    wordpress 如何防止盗链
    ubuntu 14.04 如何设置静态ip
    钉钉与金蝶ERP对接开发方式
    金蝶组件无法正常工作
    金蝶补丁安装失败
  • 原文地址:https://www.cnblogs.com/benjieqiang/p/11309733.html
Copyright © 2020-2023  润新知