202505|设计模式04|代理模式

202506|设计模式04|代理模式

概述

给某个对象提供一个代理对象来控制对该对象的访问。访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象的中介存在。

202505|设计模式04|代理模式_第1张图片

结构

  • 抽象主题类:通过抽象类或者接口实现真实主题类和代理对象实现的业务方法;
  • 真实主题类:实现了抽象主题中的具体业务,是代理对象所表示的真实对象,是最终要引用的对象;
  • 代理类:提供了与真实主题类相同的接口,内部含有对真实主题的引用,可以访问、控制和扩展真实主题的功能。代理类通常对真实主题类进行某些附加操作,比如访问控制、日志记录、延迟加载等。

分类

静态代理

火车站卖票案例背景

如果要买火车票,需要去火车站买票,坐车到火车站,排队等一系列操作,这样的操作比较麻烦。而火车站在多个地方都有代售点,去代售点买票就会方便很多。这个例子就是典型的代理模式,其中「火车站」就是「目标对象」,而「代售点」就是「代理对象」。

卖票接口(抽象主题类)
public interface SellTickets {
    // 售票接口(抽象主题类)
    void sell();
}
火车站(真实主题类)
public class TrainStation implements SellTickets {
    // 火车站(真实主题类)
    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}
代售点(代理类)
public class ProxyPoint implements SellTickets {
    // 代售点(代理类)
    private TrainStation trainStation = new TrainStation();

    @Override
    public void sell() {
        System.out.println("代售点收取一些服务费用");
        trainStation.sell();
    }
}
public class Client {
    public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        proxyPoint.sell();
    }
}

JDK动态代理

java中提供了一个动态代理类Proxy,Proxy并不是上述中的「代理对象」的类,而是提供了一个创建代理对象的静态方法newProxyInstance来获取代理对象。

代理工厂类
public class ProxyFactory {

    private TrainStation station = new TrainStation();

    public SellTickets getProxyObject() {
        //使用Proxy获取代理对象
        /*
            newProxyInstance()方法参数说明:
            ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
            Class[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
            InvocationHandler h : 代理对象的调用处理程序
         */

        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                        InvocationHandler中invoke方法参数说明:
                        proxy : 代理对象
                        method : 对应于在代理对象上调用的接口方法的 Method 实例
                        args : 代理对象调用接口方法时传递的实际参数
                     */
                    // 可以在此处对方法进行增强
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代理点收取一些费用(JDK动态代理方式)");
                        // 执行真实对象的方法
                        Object obj = method.invoke(station, args);
                        return obj;
                    }
                }
        );
        return sellTickets;
    }
}
拓展思考
  • ProxyFactory是代理类吗?

    • ProxyFactory不是代理模式中说的「代理类」,「代理类」是程序在运行过程中动态生成的在内存中的类。
  • $Proxy0的结构:

    • 代理类($Proxy0)实现了SellTickets,说明「真实类」和「代理类」会实现相同的接口;

    • 代理类($Proxy0)将提供的匿名内部类对象传递给了父类。

      package com.sun.proxy;
      
      import com.itheima.proxy.dynamic.jdk.SellTickets;
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      import java.lang.reflect.UndeclaredThrowableException;
      
      public final class $Proxy0 extends Proxy implements SellTickets {
          private static Method m1;
          private static Method m2;
          private static Method m3;
          private static Method m0;
      
          public $Proxy0(InvocationHandler invocationHandler) {
              super(invocationHandler);
          }
      
          static {
              try {
                  m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                  m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                  m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
                  m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                  return;
              }
              catch (NoSuchMethodException noSuchMethodException) {
                  throw new NoSuchMethodError(noSuchMethodException.getMessage());
              }
              catch (ClassNotFoundException classNotFoundException) {
                  throw new NoClassDefFoundError(classNotFoundException.getMessage());
              }
          }
      
          public final boolean equals(Object object) {
              try {
                  return (Boolean)this.h.invoke(this, m1, new Object[]{object});
              }
              catch (Error | RuntimeException throwable) {
                  throw throwable;
              }
              catch (Throwable throwable) {
                  throw new UndeclaredThrowableException(throwable);
              }
          }
      
          public final String toString() {
              try {
                  return (String)this.h.invoke(this, m2, null);
              }
              catch (Error | RuntimeException throwable) {
                  throw throwable;
              }
              catch (Throwable throwable) {
                  throw new UndeclaredThrowableException(throwable);
              }
          }
      
          public final int hashCode() {
              try {
                  return (Integer)this.h.invoke(this, m0, null);
              }
              catch (Error | RuntimeException throwable) {
                  throw throwable;
              }
              catch (Throwable throwable) {
                  throw new UndeclaredThrowableException(throwable);
              }
          }
      
          public final void sell() {
              try {
                  this.h.invoke(this, m3, null);
                  return;
              }
              catch (Error | RuntimeException throwable) {
                  throw throwable;
              }
              catch (Throwable throwable) {
                  throw new UndeclaredThrowableException(throwable);
              }
          }
      }
      
  • 动态代理的执行流程

    1. 在测试类中通过「代理对象」调用sell()方法
    2. 根据多态的特性,执行的是代理类$Proxy()中的sell()方法
    3. 代理类$Proxy()中的sell()方法又调用了InvocationHandler接口的子实现类对象的invoke方法
    4. invoke方法通过反射执行了真实对象所属类TrainStation中的sell()方法
    //程序运行过程中动态生成的代理类
    public final class $Proxy0 extends Proxy implements SellTickets {
        private static Method m3;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
        }
    
        public final void sell() {
            this.h.invoke(this, m3, null);
        }
    }
    
    //Java提供的动态代理相关类
    public class Proxy implements java.io.Serializable {
    	protected InvocationHandler h;
    	 
    	protected Proxy(InvocationHandler h) {
            this.h = h;
        }
    }
    
    //代理工厂类
    public class ProxyFactory {
    
        private TrainStation station = new TrainStation();
    
        public SellTickets getProxyObject() {
            SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
                    station.getClass().getInterfaces(),
                    new InvocationHandler() {
                        
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                            System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                            Object result = method.invoke(station, args);
                            return result;
                        }
                    });
            return sellTickets;
        }
    }
    
    
    //测试访问类
    public class Client {
        public static void main(String[] args) {
            //获取代理对象
            ProxyFactory factory = new ProxyFactory();
            SellTickets proxyObject = factory.getProxyObject();
            proxyObject.sell();
        }
    }
    

CGLIB动态代理

  • 如果没有定义SellTickets()接口,只定义了TrainStation()火车站类,则无法使用JDK代理,因为JDK动态代理必须定义接口,对接口进行代理。

  • CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,补充了JDK代理。

  • CGLIB是第三方提供的包,需要引入Jar包的坐标

    <dependency>
        <groupId>cglib</groupId>
        <artifarufuctId>cglib</artifactId>
        <version>2.2.2</version>
    </dependency>
    
火车站(真实类)
public class TrainStation {
    // 火车站
    public void sell() {
        System.out.println("火车站卖票");
    }
}
代理工厂
public class ProxyFactory implements MethodInterceptor {
    private TrainStation target = new TrainStation();

    public TrainStation getProxyObject(){
        // 1 创建一个Enhancer对象
        Enhancer enhancer = new Enhancer();
        // 2 设置父类的字节码对象
        enhancer.setSuperclass(target.getClass());
        // 3 设置回调函数
        enhancer.setCallback(this);
        //4 创建代理对象
        TrainStation obj = (TrainStation) enhancer.create();
        return obj;
    }

    /*
        intercept方法参数说明:
        o : 代理对象
        method : 真实对象中的方法的Method实例
        args : 实际参数
        methodProxy :代理对象中的方法的method实例
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
        TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
        return result;
    }
}
public class Client {
    public static void main(String[] args) {
        // 1 创建代理工厂对象
        ProxyFactory proxyFactory = new ProxyFactory();
        // 2 创建代理对象
        TrainStation proxyObject = proxyFactory.getProxyObject();
        // 3 调用代理对象的方法
        proxyObject.sell();
    }
}

对比

动态代理 VS 静态代理
静态代理
  • 代理类在编译期生成:代理类必须实现与目标对象相同的接口,并且在编译时就需要写好代理类的代码。
  • 代码膨胀:每一个目标类对应一个静态代理类,如果有多个目标类,代码会变得非常臃肿。
  • 实现简单:由于是在编译期就确定的,代码结构明确且容易理解。
动态代理
  • 运行时生成代理类:不需要为每个目标类单独编写代理类,可以在运行时自动创建代理对象。
  • 代码复用性好:可以通过反射机制动态处理多个目标对象,减少了代码的重复。
  • 性能开销:由于涉及到反射机制,动态代理的运行效率通常会稍低于静态代理。

Java中的动态代理机制主要有两种:基于java.lang.reflect包的JDK动态代理,以及基于第三方库如CGLIB的动态代理。

特性 静态代理 动态代理
生成方式 编译时生成 运行时生成
实现复杂度 简单,易于理解 复杂,需要理解反射机制
代码复用性 低,需要为每个目标类编写代理类 高,可以动态处理多个目标对象
维护成本 高,代理类多 低,不需要手动编写多个代理类
性能 高,没有反射的性能开销 较低,通过反射机制调用方法

静态代理适用于目标对象较少,且代理逻辑简单明确的场景。动态代理适用于目标对象较多,希望提高代码复用性,且可以接受一些性能开销的场景。

JDK动态代理 VS CGLIB动态代理
JDK动态代理
特点
  1. 基于接口:JDK动态代理只能代理实现了接口的类或接口本身。
  2. 灵活性:可以在运行时动态生成代理类,且代理对象与被代理对象共享相同的接口。
  3. 实现简单:利用java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler类实现。
优点
  • 易于使用,Java核心库自带,无需额外依赖。
  • 直接操作接口,类型安全,避免了类的多重继承问题。
缺点
  • 只能代理接口,不能代理具体类,如果目标类没有实现接口就无法使用JDK动态代理。
CGLIB动态代理
特点
  1. 基于子类:CGLIB动态代理通过生成目标类的子类来代理,因此不需要代理类实现接口。
  2. 依赖第三方库:需要引入CGLIB库(如cglib-nodep-x.x.x.jar)才能使用。
  3. 动态生成字节码:使用ASM库生成代理类的字节码。
优点
  • 可以代理没有实现接口的类。
  • 一般情况下,性能优于JDK动态代理,尤其是在方法调用频繁的场景中。
缺点
  • 代理类不能为final类,因为动态代理的实现方式需要生成目标类的子类。
  • 目标方法不能为final方法,否则也无法被重写。
特性 JDK动态代理 CGLIB动态代理
代理机制 基于接口 基于生成子类
目标对象要求 必须实现至少一个接口 可以不实现接口,但不能为final类
依赖 无需额外依赖,Java自带 需要额外引入CGLIB库
性能 一般较低,尤其是在大量方法调用时性能欠佳 通常较高,处理大量方法调用时性能优于JDK动态代理
使用难度 较低,适合简单场景 较复杂,需了解第三方库及其配置
灵活性 只能代理实现了接口的目标对象 可代理任何普通类,使用范围更广

选择哪种代理方式要依据具体的应用场景来决定。如果项目中目标对象实现了接口,并且调用频率不是特别高,JDK动态代理是一个简便且高效的选择。如果需要代理的类没有实现接口或者需要处理大量的方法调用场景,CGLIB动态代理可能是更好的选择。

使用场景

  1. 远程代理(Remote Proxy)

    目的:为在不同地址空间中的对象提供本地代理,处理远程对象的方法调用和通信。

    应用场景

    • 分布式系统:客户端通过代理对象与远程服务器通信,代理对象负责执行远程过程调用(RPC)。

    • Web服务调用:客户端使用代理对象来调用远程的Web服务,代理对象负责处理底层的HTTP请求和响应。

  2. 虚拟代理(Virtual Proxy)

    目的:通过代理延迟创建资源消耗较高的对象,只有在需要时才进行对象的实际创建。

    应用场景

    • 大图片加载:在需要显示大图片时,使用虚拟代理来延迟图片的加载,只有当图片需要显示时才进行实际加载。
    • 大型对象加载:某些对象创建开销较大,可以使用虚拟代理在对象需要使用时才创建,避免不必要的资源消耗。
  3. 保护代理(Protection Proxy)

    目的:控制对原始对象的访问权限,进行权限检查或访问控制。

    应用场景

    • 文件访问控制:限制某些用户访问系统中的特定文件,代理对象在用户尝试访问文件时进行权限检查。
    • 用户权限管理:在系统中实现细粒度的权限控制,限制用户对操作的权限。

你可能感兴趣的:(【思想】设计模式,设计模式,代理模式,java)