代理模式

97 阅读5分钟

代理模式

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。

这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

Java中的代理按照代理对象类生成时机不同,又分为两种:

  • 静态代理——在编译器就生成

  • 动态代理——在运行时动态生成,其中动态代理又包含两类:

    • JDK代理
    • CGLib代理

结构

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

⭐静态代理

为方便理解,我们举个例子🌰

火车站卖票:

火车站为方便购票,省去排队,在多个地方开设了代售点,这个代售点就是火车站的代理对象,而 火车站就是目标对象,我们看类图:

StaticUML.png

我们可以看到代售点聚合了火车站的类,所以代售点实际上执行的卖票方法实际就是火车站的卖票方法。

具体实现:

//SellTickets接口
void sell();

接口定义好规范后,我们就可以开始建立火车站和代售点类了

//火车站TranStation类,实现SellTickets接口
public void sell() {
    System.out.println("火车站卖票")
}
//代售点ProxyPoint类,同样实现SellTickets接口
//声明火车站类对象
private TrainStation trainStation = new TrainStation();
​
public void sell() {
    System.out.println("代售点收取服务费");
    trainStation.sell();
}

测试类中买票:

//client.clss
//创建代售点类对象
ProxyPoint proxyPoint = new ProxyPoint();
//调用方法进行买票
proxyPoint.sell();

这样我们就成功通过售票点进行购票!

通过代理对象,我们不仅能实现卖票的方法,还可以对原先的sell方法进行增强(例如收服务费)

⭐JDK动态代理

Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance)来获取代理对象

下面我们再通过动态代理实现上面卖票的案例🌰

接口和火车站类均不变:

//SellTickets接口
void sell();
//火车站TranStation类,实现SellTickets接口
public void sell() {
    System.out.println("火车站卖票")
}

我们现在创建代理对象的工厂类:

//ProxyFactory.class//声明目标对象,也就是火车站对象
private TrainStation station = new TrainStation();
​
//获取代理对象的方法
public SellTickets getProxyObject() {
    //通过newProxyInstance方法返回代理对象,我们要用接口类型来接收代理对象
    SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
        station.getClassLoader(),
        station.getInterfaces(),
         new InvocationHandler() {
             //通过代理对象调用方法时,其根本就是调用invoke方法
             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                 System.out.println("代售点收取服务费用");
                 //通过反射的方式调用方法
                 Object obj = method.invoke(station,args);
                 return obj;
             }
         }
    );
    return proxyObject;
}

newProxyInstance方法需要传的参数:

  • ClassLoader Loader——类加载器,用于加载代理类,可以通过目标对象获取类加载器
  • Class<?>[] interfaces——代理类实现的接口的字节码对象
  • InvocationHandler h——代理对象的调用处理程序

其中invoke方法需要传入的参数及返回值:

  • Object proxy——代理对象,在这里也就是我们的proxyObject,在invoke方法中基本不用
  • Method method——对接口中的方法进行封装的method对象,比如这里执行时,method对应的就是sell方法,通俗来说,执行接口中哪个方法时,传入的就是哪个方法
  • Object[] args——调用方法的实际参数
  • 方法返回值——我们要执行方法的返回值

我们来测试类中买票:

//client.class//1、创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
​
//2、使用factory对象的方法获取代理对象
SellTickets proxyObject = factory.getProxyObject();
​
//3、调用卖票方法,这里代理对象实际调用的是invoke方法
proxyObject.sell();

⭐CGLIB动态代理

如果我们没有定义SellTickets接口,只定义了TrainStation接口呢?显然我们就不能使用JDK代理了,因为JDK动态代理必须要求有接口,对接口进行代理。

这里我们就提出了CGLIB代理,它是第三方提供包,为没有实现接口的类提供代理,为JDK的动态代理提供了一个补充

使用:

先导包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

再去类中使用

//TrainStation类不变,还是老样子
public void sell() {
    System.out.println("火车站卖票")
}

创建代理工厂类

//ProxyFactory.class,实现MethodIntercepor接口//声明火车对象
private TrainSation station = 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;
    
    //MethodIntercepor接口中的方法,当通过代理对象调用方法时,这个方法就会被调用,即回调函数
    public Object Intercepter(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代售点收取服务费");
        //通过反射调用目标对象的方法
        Object obj = method.invoke(station, objects);
        return obj
    }
}

我们来测试类中买票:

//client.class//1、创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
​
//2、使用factory对象的方法获取代理对象
TrainStation proxyObject = factory.getProxyObject();
​
//3、调用卖票方法,这里代理对象实际调用的是invoke方法
proxyObject.sell();

这样我们就通过CGLIB的方式实现了代理卖票~

⭐优缺点

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
  • 代理对象可以扩展目标对象的功能
  • 代理模式能将客户端和目标对象分离,降低耦合度

缺点:

  • 增加了系统的复杂度