跳到主要内容

代理模式

  • 代理(Proxy)模式:是一种结构型设计模式,给某一个对象提供代理对象,并由代理对象控制对源对象的引用
    代理模式
  • 好处是可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能
    主要思想:不修改已有的代码或方法,如果需要修改,通过代理的方式来扩展该方法

结构

  • 角色

    • 抽象对象角色(AbstractObject)
      声明了目标对象和代理对象的共同接口,这样依赖在任何可以使用目标对象的地方都可以使用代理对象
    • 目标对象角色(RealObject)
      定义了代理对象所代表的目标对象,用来真正完成业务服务功能
    • 代理对象角色(ProxyObject)
      代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象
      代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象,自身的请求由目标对象实现,代理对象并不真正实现业务功能
      代理对象通常在客户端调用传递给目标对象之前或者之后执行某个操作,而不是单纯的将调用传递给目标对象

    UML
    UML

  • 代理模式有三种类型:静态代理、动态代理(JDK代理、接口代理)、CGLib 代理(在内存中动态创建目标对象的子类)

实现

静态代理

  • 静态代理需要先定义接口,被代理对象和代理对象一起实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
    静态代理

  • 代码示例

    // 公司接口  
    public interface TVCompany {
    /**
    * 生产电视机
    * @return 电视机
    */
    public TV produceTV();
    }

    // 公司的工厂生产电视机
    public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
    System.out.println("TV factory produce TV...");
    return new TV("小米电视机","合肥");
    }
    }

    // 代理商下单拿货(静态代理类)
    public class TVProxy implements TVCompany{
    private TVCompany tvCompany;
    public TVProxy(){
    }
    @Override
    public TV produceTV() {
    System.out.println("TV proxy get order .... ");
    System.out.println("TV proxy start produce .... ");
    if(Objects.isNull(tvCompany)){
    System.out.println("machine proxy find factory .... ");
    tvCompany = new TVFactory();
    }
    return tvCompany.produceTV();
    }
    }

    // 消费者通过代理商拿货(代理类的使用)
    public class TVConsumer {
    public static void main(String[] args) {
    TVProxy tvProxy = new TVProxy();
    TV tv = tvProxy.produceTV();
    System.out.println(tv);
    }
    }
  • 优点:在不改变目标对象的前提下,实现了对目标对象的功能扩展

  • 缺点:代理对象实现了目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本
    使用动态代理解决

动态代理

动态代理

  • 特点

    1. JDK 动态代理的代理对象不需要实现接口,只有目标对象需要实现接口
    2. 实现基于接口的动态代理需要利用 JDK 中的 API 在 JVM 内存中动态构建 Proxy 对象
    3. 需要使用 java.lang.reflect.Proxy 和其中的 newProxyInstance 方法,该方法需要接收三个参数
      newProxyInstance
       - **`ClassLoader loader`**:指定当前目标对象使用类加载器,获取类加载器的方法是固定的  
      - **`Class<?>[] interfaces`**:目标对象实现的接口的类型,使用泛型方式确定类型
      - **`InvocationHandler h`**:事件执行,执行目标对象的方法时,会触发事件处理器的方法,把当前执行目标对象的方法作为参数传入
  • 代码示例

    // 公司增加了维修业务
    public interface TVCompany {
    /**
    * 生产电视机
    * @return 电视机
    */
    public TV produceTV();

    /**
    * 维修电视机
    * @param tv 电视机
    * @return 电视机
    */
    public TV repair(TV tv);
    }

    // 工厂也需要增加维修业务
    public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
    System.out.println("TV factory produce TV...");
    return new TV("小米电视机","合肥");
    }

    @Override
    public TV repair(TV tv) {
    System.out.println("tv is repair finished...");
    return new TV("小米电视机","合肥");
    }
    }

    // B 代理商,全面代理公司业务。使用 Proxy.newInstance 方法生成代理对象,实现 InvocationHandler 中的 invoke 方法,在 invoke 方法中通过反射调用代理类的方法,并提供增强方法
    public class TVProxyFactory {
    private Object target;
    public TVProxyFactory(Object o){
    this.target = o;
    }
    public Object getProxy(){
    return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),
    new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("TV proxy find factory for tv.... ");
    Object invoke = method.invoke(target, args);
    return invoke;
    }
    });
    }
    }

    // 客户端调用
    public class TVConsumer {
    public static void main(String[] args) {
    TVCompany target = new TVFactory();
    TVCompany tvCompany = (TVCompany) new TVProxyFactory(target).getProxy();
    TV tv = tvCompany.produceTV();
    tvCompany.repair(tv);
    }
    }
  • 特点

    • 动态代理不需要实现接口,但是目标对象一定要实现接口,否则不能使用动态代理
    • 动态代理的方式中,所有的函数调用最终都会通过 invoke 函数的转发,因此可以在这里做一些其他操作,比如日志系统、事务、拦截器、权限控制
  • 缺点
    JDK 动态代理有一个最致命的问题就是它只能代理实现了某个接口的实现类,并且代理类也只能代理接口中实现的方法,要是实现类中存在私有方法,而接口中没有的话,该方法不能进行代理调用
    可以通过 CGLib 解决

CGLib 代理

  • 静态代理和 JDK 动态代理都需要目标对象实现接口,有时目标对象只是一个单独对象,此时可以使用 CGLib 代理
    CGLib 代理
    CGLib 可以称为子类代理,是在内存中构建一个子类对象,从而实现对目标对象功能的扩展

  • CGLib 通过 Enhancer 来生成代理类,通过实现 MethodInterceptor 接口,并实现其中的 intercept 方法,在此方法中可以添加增强方法,并可以利用反射 Method 或者 MethodProxy 继承类来调用原方法

  • 代码示例

    // C 代理商不仅想代理公司,而且还想代理多个工厂的产品  
    public class TVProxyCglib implements MethodInterceptor {
    //给目标对象创建一个代理对象
    public Object getProxyInstance(Class c){
    //1.工具类
    Enhancer enhancer = new Enhancer();
    //2.设置父类
    enhancer.setSuperclass(c);
    //3.设置回调函数
    enhancer.setCallback(this);
    //4.创建子类(代理对象)
    return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("TVProxyFactory enhancement.....");
    Object object = methodProxy.invokeSuper(o, objects);
    return object;
    }
    }

    // 新代理的 B 工厂
    public class TVFactoryB {
    public TV produceTVB() {
    System.out.println("tv factory B producing tv.... ");
    return new TV("华为电视机", "南京");
    }
    public TV repairB(TV tv) {
    System.out.println("tv B is repair finished.... ");
    return tv;
    }
    }

    // C 代理商可以直接跟公司合作,也可以跟工厂打交道,并且可以代理任何工厂的产品
    public class TVConsumer {
    public static void main(String[] args) {
    TVCompany tvCompany = (TVCompany) new TVProxyCglib().getProxyInstance(TVFactory.class);
    TV tv = tvCompany.produceTV();
    tvCompany.repair(tv);
    System.out.println("==============================");

    TVFactoryB tvFactoryB = (TVFactoryB) new TVProxyCglib().getProxyInstance(TVFactoryB.class);
    TV tv = tvFactoryB.produceTVB();
    tvFactoryB.repairB(tv);
    }
    }

扩展

  • Spring 中的 AOP 实现有 JDK 和 CGLib 两种
    AOP
    如果目标对象需要实现接口,使用 JDK 代理
    如果目标对象不需要实现接口,则使用 CGLib 代理

总结

  1. 静态代理:需要代理类和目标类都实现接口的方法,从而达到代理增强其功能
  2. JDK 动态代理:需要目标类实现某个接口,使用 Proxy.newProxyInstance 方法生成代理类,并实现 InvocationHandler 中的 Invoke 方法,实现增强功能
  3. CGLib 代理:无需目标类实现接口,使用 CGLib 中的 Enhancer 来生成目标类的子类,并实现 MethodInterceptor 中的 intercept 方法,在此方法中实现增强功能