代理模式

1.定义

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

2.具体实现

2.1代码

  1. 静态代理:静态代理中,我们对目标对象的每个方法的增强都是手动完成的,十分不灵活;从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package 设计模式;

    public class ProxyPattern01 {
    public static void main(String[] args) {
    IphoneProxy iphoneProxy = new IphoneProxy(new IphoneFactoryImpl());
    iphoneProxy.create();
    }
    }
    //服务类接口
    interface IphoneFactory{
    void create();
    }
    //直接实现
    class IphoneFactoryImpl implements IphoneFactory{

    @Override
    public void create() {
    System.out.println("厂家直销");
    }
    }
    //代理实现
    class IphoneProxy implements IphoneFactory{
    private IphoneFactoryImpl impl;

    public IphoneProxy(IphoneFactoryImpl impl) {
    this.impl=impl;
    }

    @Override
    public void create() {
    System.out.println("我来代理");
    impl.create();
    System.out.println("有优惠");
    }
    }
  2. 动态代理:相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类;从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

  • 使用JDK动态代理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    public class ProxyPattern02 {
    public static void main(String[] args) {
    final Producer producer = new Producer();

    /**
    * 动态代理:
    * 特点:字节码随用随创建,随用随加载
    * 作用:不修改源码的基础上对方法增强
    * 分类:
    * 基于接口的动态代理
    * 基于子类的动态代理
    * 基于接口的动态代理:
    * 涉及的类:Proxy
    * 提供者:JDK官方
    * 如何创建代理对象:
    * 使用Proxy类中的newProxyInstance方法
    * 创建代理对象的要求:
    * 被代理类最少实现一个接口,如果没有则不能使用
    * newProxyInstance方法的参数:
    * ClassLoader:类加载器
    * 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
    * Class[]:字节码数组
    * 它是用于让代理对象和被代理对象有相同方法。固定写法。
    * InvocationHandler:用于提供增强的代码
    * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
    * 此接口的实现类都是谁用谁写。
    */
    IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
    producer.getClass().getInterfaces(),
    new InvocationHandler() {
    /**
    * 作用:执行被代理对象的任何接口方法都会经过该方法
    * 方法参数的含义
    * @param proxy 代理对象的引用
    * @param method 当前执行的方法
    * @param args 当前执行方法所需的参数
    * @return 和被代理对象方法有相同的返回值
    * @throws Throwable
    */
    @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);
    }
    }

    /**
    * 对生产厂家要求的接口
    */
    interface IProducer {

    /**
    * 销售
    * @param money
    */
    public void saleProduct(float money);

    /**
    * 售后
    * @param money
    */
    public void afterService(float money);
    }
    /**
    * 一个生产者
    */
    class Producer implements IProducer{

    /**
    * 销售
    * @param money
    */
    @Override
    public void saleProduct(float money){
    System.out.println("销售产品,并拿到钱:"+money);
    }

    /**
    * 售后
    * @param money
    */
    @Override
    public void afterService(float money){
    System.out.println("提供售后服务,并拿到钱:"+money);
    }
    }

    必须实现接口的原因:JDK动态代理会在程序运行期间,去动态生产一个代理类,叫$Proxy0 ,那么这个动态生成的代理类会去继承一个 java.lang.reflect.Proxy 这样一个类,同时还会去实现被代理类的接口,在java里面不支持多种继承的,而每个动态代理都继承一个Proxy,所以就导致的JDK里面的动态代理只能代理接口,而不能代理实现类。

  • CGLIB代理:CGLIB 是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    public class ProxyPattern03 {
    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);
    }
    }
    /**
    * 一个生产者
    */
    class Producer{

    /**
    * 销售
    * @param money
    */
    public void saleProduct(float money){
    System.out.println("销售产品,并拿到钱:"+money);
    }

    /**
    * 售后
    * @param money
    */
    public void afterService(float money){
    System.out.println("提供售后服务,并拿到钱:"+money);
    }
    }

    CGLIB代理相比JDK动态代理:

    1. 性能更高:CGLIB代理通常比JDK代理更快,因为它直接操作字节码,而不是反射调用。这在需要频繁调用代理方法的情况下可以提供显著的性能优势。
    2. 支持无接口的代理:CGLIB可以代理没有实现接口的类,而JDK代理要求目标对象必须实现接口。
    3. 更强大的代理功能:使用CGLIB代理更广泛地代理各种对象。
    4. CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

3.优点

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

4.缺点

  • 代理模式会造成系统设计中类的数量增加
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  • 增加了系统的复杂度;

5.使用场景

  • 当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。