zoukankan      html  css  js  c++  java
  • 代理模式

    代理类的诞生背景?

    在实际开发中会经常会遇见这样的一种情况:

      原有类具有一些功能,但其具有的功能又不能满足我们的需求。在此情况下,我们可以对 原有类 进行增强处理。但是必须遵循《设计原则》中“开闭原则”的相关规定 —— “对扩展开放,修改关闭”。

    那么如何在不改变原有类的前提下,对原有类功能进行增强呢?

      解决思路:创建一个代理对象,使用代理对象来充当"中介"的角色。

           使用代理对象,是为了在不修改目标方法功能的基础上,在代理类中增强自己的功能代码 来实现增强目标类主业务逻辑。

      重点注意:最后代码执行时,真正使用的是代理类。

    代理类完成的功能:
      1. 调用目标方法,执行目标方法的功能
      2. 功能增强,在目标方法调用时,增加功能。


    代理模式

    Proxy Pattern

    什么是代理模式?

      代理模式是指,为其他对象提供一种代理 ,以控制对这个对象的访问。

      客户类真正的想要访问的对象是目标对象,但客户类实际可以访问的对象是代理对象。

      在实际开发中,当遇见 一个对象不适合 或者 不能直接引用另一个对象的情况时,可以创建代理类。通过使用代理对象来实现客户类对目标对象的访问。  

      由此可见,代理对象可以在客户端和目标对象之间起到中介的作用,同时也起到了保护了目标对象的作用。     

      注意事项:代理类需要实现和目标类一致的接口。

    三个角色:客户端类  、 代理类、 目标类                                  

                                                                           ---

    使用代理模式的作用?

    (1)职责清晰;
      真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

    (2)功能增强。增强目标类主业务逻辑;

      在你原有的功能上,增加了额外的功能。 新增加的功能,叫做功能增强。

    (3)控制访问;
        代理类不让你访问目标,例如商家不让用户访问厂家。

      在开发中也会有这样的情况, 你有A类, 本来是调用C类的方法, 完成某个功能。 但是C不让A调用。 那么只能在A和C之间 创建一个B作为C的代理让A访问。  
        B将访问C的结果,交给A。从而达到A间接访问到C的效果
          
      

    静态代理和动态代理的区别?

      静态代理是由程序员手动创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

      动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

      要从直观的代码中  鉴别 静态代理 和 动态代理,观察代码即可。 通常情况下,只要程序中 需要自己定义代理类,这就是静态代理。程序无需定义代理类, 但是 代理对象是通过  工厂 或 工具 在运行时 生成的,此时的代理 为 动态代理。

    静态代理:  指的是程序还没有运行,两个类之间就已经建立了代理关系。

    原理:

    要求:1) 代理类是自己手工实现的,自己创建一个java类,表示代理类。

          2)同时你所要代理的目标类是确定的。

    特点:1)实现简单 

          2)容易理解。

    缺点:1)当目标类增加了, 代理类可能也需要成倍的增加。 代理类数量过多。

          2) 当你的接口中功能增加了, 或者修改了,会影响众多的实现类,厂家类,代理都需要修改。影响比较多。

    动态代理:  指的是程序在运行过程中,才生成一个代理对象。而该代理对象 作用就是 为目标对象 作 代理。     

      当在静态代理中目标类很多时候,可以使用动态代理,来避免使用静态代理的缺点。
        动态代理中目标类即使很多, 1)代理类数量可以很少。 2)当你修改了接口中的方法时,并不会影响代理类。
        动态代理: 在程序执行过程中,使用jdk的反射机制,创建代理类对象, 并动态的指定要代理目标类。
        换句话说: 动态代理是一种创建java对象的能力,让你不用创建TaoBao类,就能创建代理类对象。

     


    回顾在java中,常规的创建对象方式步骤:    

      1)先创建类文件, java文件编译为class;    

      2)再使用构造方法,创建类的对象;

    动态代理的分类?

    动态代理类型的选择原则:

      目标类有实现接口     ——   使用JDK动态代理 或 CGLIB动态代理 
      目标类没有实现接口  ——   只能使用CGLIB动态代理。

    1)JDK的Proxy动态代理

      原理:(与静态代理的相同)

      要求: 目标类 必须要实现接口。

    JDK的反射机制,提供了在程序运行时创造对象的能力。于是可以使用java反射包中的类和接口来实现动态代理的功能:

    调用反射包中的相关方法,在程序执行时,创建代理类的对象。而无需再由程序员来手动创建代理类的java文件。

    需要使用到 java.lang.reflect 反射包中的三个类/接口:

    InvocationHandler接口

    jdk动态代理:
           1. 反射, Method类,表示方法。类中的方法。 通过Method可以执行某个方法。

    1)InvocationHandler 接口(调用处理器):就一个方法invoke()
                   invoke():表示代理对象要执行的功能代码。你的代理类要完成的功能就写在   invoke()方法中。
                                              
    方法原型:(3个参数)
    InvocationHandler “调用处理器”接口:表示你的代理要干什么。

      public Object invoke(Object proxy, Method method, Object[] args)
        Object proxy ——jdk创建的代理对象,无需赋值。
        Method method——代表目标类中的方法,jdk提供method对象的。无需赋值。
        Object[] args——目标类中方法的参数, jdk提供的。无需赋值。

           
    如何使用?
     1.创建类实现接口InvocationHandler。
     2.重写invoke()方法, 把原来静态代理中代理类要完成的功能,写在这。

    Method类 ————  表示方法的, 确切的说就是目标类中的方法。
                作用:通过Method可以执行某个目标类的方法,Method.invoke();
                        method.invoke(目标对象,方法的参数)
                          Object ret = method.invoke(service2, "李四");

                说明: method.invoke()就是用来执行目标方法的,等同于静态代理中的
                         //向厂家发送订单,告诉厂家,我买了u盘,厂家发货
                      float price = factory.sell(amount); //厂家的价格。

    Proxy类   ————  创建代理对象。

      常规的创建对象方式: new 类的构造方法()。缺点是代码固定,不灵活,代码扩展性差。

      反射方式创建对象:使用Proxy类的方法,代替new的使用,来创建对象。
                

    方法: 静态方法 newProxyInstance()
                作用是: 创建代理对象, 等同于静态代理中的TaoBao taoBao = new TaoBao()

     参数:
                 1. ClassLoader loader 类加载器,负责向内存中加载对象的。 使用反射获取对象的ClassLoader
                      类a , a.getCalss().getClassLoader(),  目标对象的类加载器
              2. Class<?>[] interfaces: 接口, 目标对象实现的接口,也是反射获取的。
              3. InvocationHandler h : 我们自己写的,代理类要完成的功能。
    
                返回值:就是代理对象
    
                public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h) 

     


    2)CGLIB动态代理

    原理:生成目标类的子类,而该子类是进行扩展增强过的,该子类对象就作为代理对象。

    要求:1)使用CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 修饰的类

       2)使用CGLIB,需要导入相应的JAR包。
      

      CGLIB(Code Generation Library):是一个开源项目,是一个强大的、高性能的、高质量的代码生成类库。它可以在运行期扩展和增强 Java 类。CGLIB是第三方的工具库, 用于创建代理对象。Hibernate 用它来实现持久对象的字节码的动态生成,Spring 用它来实现 AOP 编程。

      CGLIB的原理是继承, CGLIB通过继承目标类,创建它的子类。在子类中重写父类中同名的方法, 实现功能的修改。
    因为CGLIB是继承,重写方法,所以要求目标类不能是final修饰的, 方法也不能是final的。
      CGLIB的要求目标类比较宽松, 只要能继承就可以了。

      CGLIB在很多的框架中使用, 比如 mybatis ,spring框架中都有使用。     


    实现动态代理的步骤?

    1. 创建接口,定义目标类要完成的功能
    2. 创建目标类实现接口
    3. 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
        1)调用目标方法
        2)增强功能

    4.使用Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型。


    案例:

      需求:在不改动原有类代码的前提下,将用户输入的小写英文字符串 进行功能增强之后,以全大写字符串的形式输出


    1-静态代理模式 实现代码

    1)编写主业务接口
    package com.penguin1024.service;
    // 主业务接口
    public interface ISomeService {
        // 目标方法
        String doFirst(String str);
    }

    2) 编写实现业务接口的目标类  

    package com.penguin1024.service;
    // 目标类  
    public class SomeServiceImpl implements ISomeService {
        @Override
        public String doFirst(String str) {
            return str;   // 返回小写的英文字符串
        }
    }

    3)编写目标对象的代理类

    package com.penguin1024.proxy;
    import com.penguin1024.service.ISomeService;
    import com.penguin1024.service.SomeServiceImpl;
    /*
    代码思路:
        1.该代理类是为目标对象 SomeServiceImpl提供代理。因此代理方 和 目标类 处理的是同一种业务。
          体现在代码中则是 代理类 必须 去实现目标类实现的接口。
        2.该代理类是为目标对象 SomeServiceImpl提供代理。因此,在本代理类中需使用到目标对象。
          于是可以在本类的无参构造方法中创建目标对象。让程序在创建代理对象的同时,创建好目标对象。
     */
    // 为目标对象创建代理类
    public class SomeServiceImplProxy implements ISomeService {
        // 声明目标变量
        private ISomeService target;
        public SomeServiceImplProxy() {
            // 创建目标对象
           target  = new SomeServiceImpl();
        }
    
        @Override
        // 代理类中的业务方法doFirst(String str)
        public String doFirst(String str) {
            // 使用目标对象,调用目标类中自己的业务方法doFirst
            String result = target.doFirst(str);
    
            // 对目标对象调用方法得到的返回值,做【功能增强操作】:小写的字符串转成大写。 (这一步才体现代理类的作用)
            return  result.toUpperCase();
        }
    }

    4)编写测试类

    package com.penguin1024.test;
    import com.penguin1024.proxy.SomeServiceImplProxy;
    import com.penguin1024.service.ISomeService;
    public class MyTest {
        public static void main(String[] args) {
            // 1-创建代理对象  (”代理律师“ 代替 委托方,出庭)
            ISomeService proxy = new SomeServiceImplProxy();
    
            // 2-调用代理对象中的业务方法doFirst     (被进行功能增强之后的同名目标类业务方法doFirst)
            String str = proxy.doFirst("abcde");
            System.out.println("打印结果:"+str);
        }
    }

    5)测试结果


    2- JDK的Proxy动态代理 模式实现代码

         不需要创建代理类,而是 通过 工具 或 工厂 来生成代理对象。无需手动创建代理类

     1)主业务接口:ISomeService.java

    package com.penguin1024.service;
    // 主业务接口
    public interface ISomeService {
        // 目标方法
        String doFirst(String str);
    }

     2)目标类:SomeServiceImpl.java

    package com.penguin1024.service;
    // 目标类
    public class SomeServiceImpl implements ISomeService {
        @Override
        public String doFirst(String str) {
            return str;
        }
    }

    3)测试类 MyTest.java

    package com.penguin1024.test;
    import com.penguin1024.service.ISomeService;
    import com.penguin1024.service.SomeServiceImpl;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    /*
        JDK的Proxy动态代理
            需要使用到java.lang.reflect包下的三个类/接口
            1)InvocationHandler接口
            2)Method类
            3)Proxy类
     */
    public class MyTest {
        public static void main(String[] args) {
            // 1-创建目标对象
            final ISomeService target = new SomeServiceImpl();
    
            // 2-使用JDK中的Proxy类动态地创建代理对象
            ISomeService proxy = (ISomeService) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(), // 目标对象的类加载器
                    target.getClass().getInterfaces(), // 目标对象所实现的所有接口
                    new InvocationHandler() {  // InvocationHandler接口的匿名内部类
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("打印观察下method参数的信息:" + method);
    
                            // 目标对象target,调用method实例所代表的业务方法,并传递实参。接收返回结果。
                            Object result = method.invoke(target, args);// 相当于静态代理模式案例中的代码:Object result = target.doFirst("abcde")
    
                            // 对返回结果做增强操作。
                            if (result != null) {  // 进行类型转换之前,先进行空指针判断
                                // 进行了增强:将字符串转为全大写字符串
                                result = ((String) result).toUpperCase();
                            }
                            // 返回增强之后的结果给方法调用者
                            return result;
                        }
                    }
            );
            // 3-使用代理对象调用业务方法doFirst(String str)
            String result = proxy.doFirst("abcde");
            System.out.println("打印结果:" + result);
        }
    }

    4)测试结果:

     


     2-CGLIB动态代理 模式实现代码

    1)编写目标类

    package com.penguin1024.service;
    // 目标类
    public class SomeService {
        public String doFirst(String str) {
            return str;
        }
    }

    2)编写CGLIB动态代理对象生成类

    package com.penguin1024.factory;
    import com.penguin1024.service.SomeService;
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    import java.lang.reflect.Method;
    /*
       本类作为一个工厂类,定义方法,提供为目标类创造代理对象的功能。
       注意事项:
            1-本类需要实现MethodInterceptor接口,因此还需要添加CGLIB相关的jar包。
            2-难点:本程序设计《方法回调设计模式》的概念知识点
    
     */
    public class MyCglibFactory implements MethodInterceptor {
    
        private SomeService target;
    
        public MyCglibFactory() {
            // 无参构造创建目标对象。(为了实现:在创建该类的同时,创建出 目标对象。)
            target = new SomeService();
        }
        // 定义该方法用于创建CGLIB动态代理(注意该方法的返回值,必须是目标类型(即父类类型))
        public SomeService myCglibCreator() {
    
            // 1-创建增强器对象
            Enhancer enhancer = new Enhancer();
    
            // 2-指定目标类,即父类  (依据:CGLIB代理的原理是继承)
            enhancer.setSuperclass(SomeService.class);
    
            /*
              《回调对象》?
                若一个类 实现了Callback接口,这个类就是回调对象。
    
                思考:本类MyCglibFactory的对象是回调对象吗?
                    查看源码发现  :MethodInterceptor extends Callback 这个接口
                    而我们类 MyCglibFactory implements MethodInterceptor,就相当于MyCglibFactory 也间接实现了Callback接口
                    也就是说 ,当前类的对象 ,就是一个回调对象
                    因此 在 下面的 enhancer.setCallback(Callback回调接口对象); 的括号中填写 this即可。
                    =====》  enhancer.setCallback(this);  // 把当前MyCglibFactory类的对象 作为实参传递 给了Enhancer类。(涉及了方法的回调)
    
              《方法回调设计模式》?
                定义:
                    在Java中,类A调用类B中的某个方法b,然后类B又在某个时候反过来调用类A中的某个方法a。
                    对于A来说,这个a方法便叫做回调方法。
                    Java 的接口提供了一种很好的方式来实现方法回调。
                    这个方式就是定义一个简单的接口,在接口之中定义一个我们希望回调的方法。这个接口称为回调接口。
    
                在本类代码中该模式的体现:
                    已知:MyCglibFactory类的对象this就是一个回调对象。
                    在MyCglibFactory类(类A) 调用了 Enhancer类(类B)中的setCallback(this)方法,并把A类的回调对象this对象作为实参传递给了Enhancer类(类B)。
                    当Enhancer类(B类)中生成的代理对象【在测试类中执行目标方法时】会 反过来调用 MyCglibFactory类(类A)中的intercept方法。
                    因此,intercept方法就叫做回调方法。
             */
    
            // 3-设置回调接口对象
            enhancer.setCallback(this);
    
            // 4-返回指定类的子类。  即 目标类的代理对象
            return (SomeService) enhancer.create();    // 生成代理对象
        }
    
        @Override
        // 重写 【回调方法】  intercept “拦截”之意
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                // 1-调用目标方法,获取到结果
                Object result = method.invoke(target, args);
    
                // 2-对获取到的结果,做增强操作
                if (result !=null ) {   // 先做判断, 避免 空指针异常
                        result = ((String) result).toUpperCase(); //增强操作:转成大写
                    }
                return result;
        }
    }

     3)编写测试类MyTest.java

    package com.penguin1024.test;
    import com.penguin1024.factory.MyCglibFactory;
    import com.penguin1024.service.SomeService;
    public class MyTest {
        public static void main(String[] args) {
            // 1-通过 MyCglibFactory工厂中的myCglibCreator()方法得到目标类的 代理对象
            SomeService service = new MyCglibFactory().myCglibCreator();
    
            // 2-使用代理对象调用doFirst()方法
            // 这行代码 反过来会去执行MyCglibFactory类中的回调方法intercept
            String result = service.doFirst("abcde");
            System.out.println("打印结果:"+result);
        }
    }

    以上代码  涉及一个知识点:  方法回调设计模式!

      在 Java 中,就是类 A 调用类 B 中的某个方法 b,然后类 B 又在某个时候反过来调用类 A中的某个方法 a,对于 A 来说,这个 a 方法便叫做回调方法。

      Java 的接口提供了一种很好的方式来实现方法回调。这个方式就是定义一个简单的接口,在接口之中定义一个我们希望回调的方法。这个接口称为回调接口。

      在前面的例子中,我们定义的 AccountServiceCglibProxyFactory 类就相当于前面所说的 A类,而 Enhancer 类则是 B 类。

                    A 类中调用了 Enhancer 类的 setCallback(this) 方法,并将回调对象 this 作为实参传递给了 Enhancer 类。

                    Enhancer 类在后续执行过程中,会调用 A 类中的intercept()方法,这个 intercept()方法就是回调方法。

    总结: 

        CGlib动态代理 和 JDK的Proxy  应用场景不同在于:

               JDK的Proxy  要求  目标对象 必须 实现接口  ,而cglib动态代理没有这个要求。

       思考: 要是 目标类有接口的情况下,能使用cglib动态代理吗?  

           能!!!  一个类有接口,那么它能够有子类。这就说明能用cglib。


    代码模拟一个用户购买U盘的系列行为。          

    用户是客户端类
                商家:代理,代理某个品牌的u盘。
                厂家:目标类。

                三者的关系: 用户(客户端)---商家(代理)---厂家(目标)
                商家和厂家都是卖u盘的,他们完成的功能是一致的,都是卖u盘。


          实现步骤:
             1. 创建一个接口,定义卖u盘的方法, 表示你的厂家和商家做的事情。
             2. 创建厂家类,实现1步骤的接口
             3. 创建商家,就是代理,也需要实现1步骤中的接口。
             4. 创建客户端类,调用商家的方法买一个u盘。

                                    

    功能增强

  • 相关阅读:
    C语言中条件表达式求最大值和最小值
    面向对象编程:Java的简单数据类型
    JAVA学习经验谈
    JAVA的入门基础一些精典
    面向对象编程:Java复杂数据类型用法
    面向对象编程:Java的简单数据类型
    面向对象编程:Java collection更有效管理elements
    从C++到Java 理解面向对象是关键所在
    JAVA学习经验谈
    JAVA的入门基础一些精典
  • 原文地址:https://www.cnblogs.com/penguin1024/p/12144466.html
Copyright © 2011-2022 走看看