• Java代理模式


    代理模式

    代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
    这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法

    举个例子:我们想在网上买一个商品,需要通过电商购物平台购买,而嗲是电商的货物来自于生产厂商;由此可以看出电商平台不生产商品,代理生产厂商销售商品。

    用图表示如下:

    代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象

     动态代理:

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

    分类

    基于接口的动态代理

    基于子类的动态代理

    基于接口的动态代理:

    * 涉及的类:Proxy
    * 提供者:JDK官方
    * 如何创建代理对象:
    * 使用Proxy类中的newProxyInstance方法
    * 创建代理对象的要求:
    * 被代理类最少实现一个接口,如果没有则不能使用
    * newProxyInstance方法的参数:
    * ClassLoader:类加载器
    * 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
    * Class[]:字节码数组
    * 它是用于让代理对象和被代理对象有相同方法。固定写法。
    * InvocationHandler:用于提供增强的代码
    * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
    * 此接口的实现类都是谁用谁写。

    首先准备一个生产者抽象类IProducer.java  和 Producer.java ,Producer.java实现IProducer

     1 public interface IProducer {
     2 
     3     /**
     4      * 销售
     5      * @param money
     6      */
     7     public void saleProduct(float money);
     8 
     9     /**
    10      * 售后
    11      * @param money
    12      */
    13     public void afterService(float money);
    14 }
    IProducer.java
     1 public class Producer implements IProducer {
     2     /**
     3      * 销售
     4      *
     5      * @param money
     6      */
     7     public void saleProduct(float money) {
     8         System.out.println("销售产品,并拿到钱:" + money);
     9     }
    10     /**
    11      * 售后
    12      *
    13      * @param money
    14      */
    15     public void afterService(float money) {
    16         System.out.println("提供售后服务,并拿到钱:" + money);
    17     }
    18 }
    Producer.java

    模拟一个用户类Client.java 

     1 public class Client {
     2 
     3     public static void main(String[] args) {
     4         final Producer producer = new Producer();
     5 
     6         /**
     7          * 动态代理:
     8          *  特点:字节码随用随创建,随用随加载
     9          *  作用:不修改源码的基础上对方法增强
    10          *  分类:
    11          *      基于接口的动态代理
    12          *      基于子类的动态代理
    13          *  基于接口的动态代理:
    14          *      涉及的类:Proxy
    15          *      提供者:JDK官方
    16          *  如何创建代理对象:
    17          *      使用Proxy类中的newProxyInstance方法
    18          *  创建代理对象的要求:
    19          *      被代理类最少实现一个接口,如果没有则不能使用
    20          *  newProxyInstance方法的参数:
    21          *      ClassLoader:类加载器
    22          *          它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
    23          *      Class[]:字节码数组
    24          *          它是用于让代理对象和被代理对象有相同方法。固定写法。
    25          *      InvocationHandler:用于提供增强的代码
    26          *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
    27          *          此接口的实现类都是谁用谁写。
    28          */
    29        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
    30                 producer.getClass().getInterfaces(),
    31                 new InvocationHandler() {
    32                     /**
    33                      * 作用:执行被代理对象的任何接口方法都会经过该方法
    34                      * 方法参数的含义
    35                      * @param proxy   代理对象的引用
    36                      * @param method  当前执行的方法
    37                      * @param args    当前执行方法所需的参数
    38                      * @return        和被代理对象方法有相同的返回值
    39                      * @throws Throwable
    40                      */
    41                     @Override
    42                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    43                         //提供增强的代码
    44                         Object returnValue = null;
    45 
    46                         //1.获取方法执行的参数
    47                         Float money = (Float)args[0];
    48                         //2.判断当前方法是不是销售
    49                         if("saleProduct".equals(method.getName())) {
    50                             returnValue = method.invoke(producer, money*0.8f);
    51                         }
    52                         return returnValue;
    53                     }
    54                 });
    55         proxyProducer.saleProduct(10000f);
    56     }
    57 }
    Client

    上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

    Cglib代理

    Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

    • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
    • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
    • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

    Cglib子类代理实现方法:
    1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
    2.引入功能包后,就可以在内存中动态构建子类
    3.代理的类不能为final,否则报错
    4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

    maven工程的实现

    在pom.xml引入

    <dependencies>
           <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>2.1_3</version>
            </dependency>
    </dependencies>

    模拟一个生产商Producer.java

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

    模拟一个消费对象Client.java

    public class Client {
    
        public static void main(String[] args) {
            final Producer producer = new Producer();
            /**
             * 动态代理:
             *  特点:字节码随用随创建,随用随加载
             *  作用:不修改源码的基础上对方法增强
             *  分类:
             *      基于接口的动态代理
             *      基于子类的动态代理
             *  基于子类的动态代理:
             *      涉及的类:Enhancer
             *      提供者:第三方cglib库
             *  如何创建代理对象:
             *      使用Enhancer类中的create方法
             *  创建代理对象的要求:
             *      被代理类不能是最终类
             *  create方法的参数:
             *      Class:字节码
             *          它是用于指定被代理对象的字节码。
             *
             *      Callback:用于提供增强的代码
             *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
             *          此接口的实现类都是谁用谁写。
             *          我们一般写的都是该接口的子接口实现类:MethodInterceptor
             */
            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);
        }
    }

    在Spring的AOP编程中:

    如果加入容器的目标对象有实现接口,用JDK代理
    如果目标对象没有实现接口,用Cglib代理

    最后:还有一个自己的实现简单代理模式,这里不在赘述。

  • 相关阅读:
    为什么处理有序数组比无序数组快?
    LeetCode:Longest Common Prefix
    LeetCode:Container With Most Water,Trapping Rain Water
    LeetCode:Substring with Concatenation of All Words (summarize)
    LeetCode:Pow(x, n)
    LeetCode:Combination Sum I II
    LeetCode:N-Queens I II(n皇后问题)
    LeetCode:Valid Sudoku,Sudoku Solver(数独游戏)
    LeetCode:Divide Two Integers
    LeetCode:Reverse Nodes in k-Group
  • 原文地址:https://www.cnblogs.com/hoganhome/p/14831645.html
Copyright © 2020-2023  润新知