结构型模式-代理模式

suaxi
2022-09-04 / 0 评论 / 96 阅读 / 正在检测是否收录...

结构型模式

描述如何将类或对象按某种布局组合成更大的结构,分为类结构型模式和对象结构型模式,前者采用继承来组织接口和类,后者采用组合或聚合来组合对象

分为:代理模式、适配器模式、装饰者模式、桥接模式、外观模式、组合模式、享元模式

1. 代理模式

(1)概述

由于某些原因需要给某对象提供一个代理以控制对该对象的访问,这时,访问对象不适合或不能直接引用目标对象,代理对象作为访问对象与目标对象之间的中介

Java中的代理按照类生成时机的不同分为静态代理动态代理,静态代理 代理类在编译时生成,动态代理 代理类在运行时动态生成(JDK动态代理、CGLib动态代理)

(2)结构
  • 抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法
  • 真实主题类:实现抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
  • 代理类:提供与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制以及扩展真实主题的功能
(3)静态代理

以火车站卖票为例

1.静态代理(火车站卖票案例).png

抽象主题类

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("代售点收取5元手续费");
        trainStation.sell();
    }
}

测试

public class Client {
    public static void main(String[] args) {
        //创建代售点
        ProxyPoint proxyPoint = new ProxyPoint();
        //顾客从代售点买票
        proxyPoint.sell();
    }
}
(4)JDk动态代理

抽象主题类

public interface SellTickets {

    /**
     * 卖票
     */
    void sell();
}

真实主题类

public class TrainStation implements SellTickets {

    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}

获取代理对象的工厂类

public class ProxyFactory {

    /**
     * 声明目标对象
     */
    private final TrainStation trainStation = new TrainStation();

    /**
     * 获取代理对象
     *
     * @return
     */
    public SellTickets getProxyInstance() {
        //返回代理对象
        /**
         * ClassLoader loader:类加载器(可以通过目标对象获取类加载器)
         * Class<?>[] interfaces:代理类实现的接口的字节码对象
         * InvocationHandler h:代理对象的调用处理程序
         */
        return (SellTickets) Proxy.newProxyInstance(
                trainStation.getClass().getClassLoader(),
                trainStation.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * Object proxy:代理对象,和proxyObject是同一个对象,在invoke方法中基本不用
                     * Method method:对接口中的方法进行封装的method对象
                     * Object[] args:调用方法的实际参数
                     * 返回值:方法的返回值
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代售点收取10元手续费(jdk动态代理)");
                        //执行目标对象的方法
                        return method.invoke(trainStation, args);
                    }
                });
    }
}

测试

public class Client {
    public static void main(String[] args) {
        //获取代理对象
        //1.创建代理工厂对象
        ProxyFactory proxyFactory = new ProxyFactory();
        //2.使用proxyFactory对象的方法获取代理对象
        SellTickets proxyObject = proxyFactory.getProxyInstance();
        //3.调用卖票的方法
        proxyObject.sell();
    }
}

Java中提供了一个动态代理类 Proxy ,即提供了一个创建代理对象的静态方法 newProxyInstance 来获取代理对象

使用Arthas (阿尔萨斯)查看代理类的结构:

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

从以上类结构可以看出:

  • 代理类 $Proxy0 实现了 SellTickets,即真实类和代理类都实现了同样的接口
  • 代理类 $Proxy0 将我们提供了的匿名内部类 invocationHandler 传递给了父类
  • 动态代理执行流程:

    • 在测试类(访问类)中通过代理对象调用 sell() 方法
    • 根据多态的特性,执行的代理类是 $Proxy0 中的 sell() 方法
    • 代理类 $Proxy0 中的 sell() 方法中又调用了 invocationHandler 接口的子实现类对象的 invoke() 方法
    • invoke 方法通过反射执行了真实对象所属类 TrainStation 中的 sell() 方法
(5)CGLib动态代理

如果没有定义 SellTickets 接口,只定义了 TrainStation 火车站类,此时无法使用JDK动态代理,因为它要求必须定义接口

真实主题类

public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }
}

代理对象工厂

public class ProxyFactory implements MethodInterceptor {

    /**
     * 声明火车站对象
     */
    private TrainStation trainStation = new TrainStation();

    public TrainStation getProxyObject() {
        //创建Enhancer对象(类似于JDK代理中的Proxy)
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(TrainStation.class);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TrainStation proxyObject = (TrainStation) enhancer.create();
        return proxyObject;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代售点收取15元手续费(CGLib代理)");
        //要调用目标方法的对象
        return method.invoke(trainStation, objects);
    }
}

测试

public class Client {
    public static void main(String[] args) {
        //代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        //获取代理对象
        TrainStation proxyObject = proxyFactory.getProxyObject();
        //调用sell
        proxyObject.sell();
    }
}
(6)三种代理模式的对比
  • 静态代理与动态代理:

    • 动态代理将接口中声明的所有方法都转移到调用处理器一个集中的方法中处理(JDK:InvocationHandler.invoke()),在接口方法较多的情况下可以进行灵活的处理;反之静态代理每增加一个方法就需要新增对应的代理类
  • JDK与CGLib动态代理:

    • CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,在 JDK1.6 之前比使用 Java反射效率要高,其中需注意:CGLib 不能对声明为 final 类型的类或方法进行代理,因为 CGLib 原理是动态生成被代理类的子类
    • JDK1.6、1.7、1.8逐步优化之后,在调用次数较少的情况下,其效率高于 CGLib 动态代理,在大量调用的情况下,CGLib 效率优于 JDK1.6、1.7JDK1.8 时效率高于 CGLib
    • 有接口时使用JDK动态代理,没有接口时使用CGLib动态代理
(7)优缺点
  • 代理模式在客户端与目标对象之间起到中介的作用和保护目标对象的作用
  • 可以扩展目标对象的功能
  • 将客户端与目标对象分离,降低了耦合度
  • 增加了系统的复杂度
(8)使用场景
  • 远程代理(Remote)

    本地服务通过网络请求远程服务,将通信部份隐藏起来,只暴露给本地服务一个接口,通过该接口访问远程服务所提供的功能,而不必过多关心通信细节的部分,如:RPC 远程调用

  • 防火墙代理

    在浏览器配置代理功能时,防火墙将请求转发给互联网,收到响应之后再转发给浏览器

  • 保护代理

    控制一个对象的访问,可以给不同的用户提供不同的使用权限

0

评论 (0)

取消