代理模式

代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。【注-1】

使用场景

第一个使用场景就是在不改动源代码的前提下,对原有功能进行增强。比如可以在原方法调用之前和调用之后 增加额外的业务逻辑,比如在插入数据到数据库之前进行日志记录。第二个场景就是如果有多个对象有相同类 型的逻辑,但是每个方法各自的具体实现不同,那么也可以使用动态代理模式进行处理。 Spring框架的IOC和AOP,主要依赖的就是动态代理技术。

源码分析

静态代理模式

有一个售票窗口(TicketSeller),具有售票功能(sell()方法):

public class TicketSeller {
  public void sell(int price) {
    System.out.println("TicketSeller 以价格 $" + price + " 卖了一张票 ...");
  }
}

如果票价是 $30,正常情况下是这样运行的:

public class Test {
  public static void main(String[] args) {
    TicketSeller ticketSeller = new TicketSeller();
    ticketSeller.sell(30);
  }
}

运行结果:

TicketSeller 以价格 $30 卖了一张票 ...

有时候去不了车站售票口,可以去代理窗口买票,但是代理窗口要收取 $5 的手续费,实现如下:

public class ProxySeller {
  private TicketSeller seller;
  public ProxySeller(TicketSeller seller) {
    this.seller = seller;
  }
  public void sell(int price) {
    before();
    // 代理以扣除手续费之后的价格从原渠道购物
    seller.sell(price - 5);
    after();
  }
  private void before() {
    System.out.println("ProxySeller 代理收取手续费 $5");
  }
  private void after() {
    System.out.println("ProxySeller 代理完成");
  }
}

因为代理自己没有出票的能力,所以购票还是调用的原售票方法,但是调用之前进行了 扣除手续费的操作(before()方法),购票之后如果需要也可以进行别的操作( after()方法)。

此时就如果票价是 $30,那么就要加上 $5 的手续费,共 $35,运行如下:

public class Test {
  public static void main(String[] args) {
    // 需要传入被代理对象的实例
    ProxySeller tickeProxy = new ProxySeller(new TicketSeller());
    tickeProxy.sell(35);
  }
}

运行结果:

ProxySeller 代理收取手续费 $5
TicketSeller 以价格 $30 卖了一张票 ...
ProxySeller 代理完成

这样就在不改变原代码的前提下增加(更改)了业务逻辑,那么如果这个代理门店不仅提供 代购车票服务,还提供代购盒饭服务呢,最原始的做法就是在实现一个外卖服务类和外卖服务代理类。 但是因为代购车票和代购别的东西都是一样的逻辑,那么久可以在现有代码的基础上通过改造, 实现代理类复用。如果不希望代理类中使用if……else……语句进行被代理类的类型判断,那么需要 改造当前TicketSeller类,提取出Seller类,sell()接口作为公共接口,表示被代理类的售卖能力:

public interface Seller {
  void sell(int price);
}

接着改造原有TicketSeller类:

public class TicketSeller implements Seller {
  public void sell(int price) {
    System.out.println("TicketSeller 以价格 $" + price + " 卖了一张票 ...");
  }
}

参考TicketSeller类,实现外卖服务类:

public class FoodSeller implements Seller {
  public void sell(int price) {
    System.out.println("FoodSeller 以价格 $" + price + " 卖了一盒饭 ...");
  }
}

再改造代理类,使代理类具有代购外卖的服务:

public class ProxySeller implements Seller {
  private Seller seller;
  public ProxySeller(Seller seller) {
    this.seller = seller;
  }
  public void sell(int price) {
    before();
    // 代理以扣除手续费之后的价格从原渠道购物,如果只是单纯增加前后的逻辑,此参数无需修改
    seller.sell(price - 5);
    after();
  }
  private void before() {
    System.out.println("ProxySeller 代理收取手续费 $5");
  }
  private void after() {
    System.out.println("ProxySeller 代理完成");
  }
}

测试代理的买票和外卖服务:

public class Test {
  public static void main(String[] args) {
    // 买票服务
    ProxySeller tickeProxy = new ProxySeller(new TicketSeller());
    tickeProxy.sell(35);
    // 外卖服务
    ProxySeller foodProxy = new ProxySeller(new FoodSeller());
    foodProxy.sell(20);
  }
}

运行结果:

ProxySeller 代理收取手续费 $5
TicketSeller 以价格 $30 卖了一张票 ...
ProxySeller 代理完成

ProxySeller 代理收取手续费 $5
FoodSeller 以价格 $15 卖了一盒饭 ...
ProxySeller 代理完成

TicketSeller 和 代理类ProxySeller 都实现了相同的接口Seller,都具有相同的能力,但是代理类除了额外的逻辑之外, 具体功能实现是调用原实现类的方法,这就是静态代理。

动态代理

上述静态代理类中,如果增加了一个新的方法,比如退货接口,那么接口类、所有实现类以及代理类都要根据新接口进行修改:

// Seller接口增加方法:
void back(int price);
// TicketSeller类增加实现:
public void back(int price) {
  System.out.println("TicketSeller 退回了价值 $" + price + " 的车票 ...");
}
// FoodSeller类增加实现:
public void back(int price) {
  System.out.println("FoodSeller 退回了价值 $" + price + " 的盒饭 ...");
}
// ProxySeller代理类增加实现:
public void back(int price) {
  before();
  // 如果只是单纯增加前后的逻辑,此参数无需修改
  seller.back(price - 5);
  after();
}

这样的话就可以从代理类调用退货方法:

public class Test {
  public static void main(String[] args) {
    // 车票服务
    ProxySeller tickeProxy = new ProxySeller(new TicketSeller());
    tickeProxy.sell(35); // 买票
    tickeProxy.back(35); // 退票
    System.out.println();
    // 外卖服务
    ProxySeller foodProxy = new ProxySeller(new FoodSeller());
    foodProxy.sell(20); // 买外卖
    foodProxy.back(20); // 退外卖
  }
}

运行结果:

ProxySeller 代理收取手续费 $5
TicketSeller 以价格 $30 卖了一张票 ...
ProxySeller 代理完成
ProxySeller 代理收取手续费 $5
TicketSeller 退回了价值 $30 的车票 ...
ProxySeller 代理完成

ProxySeller 代理收取手续费 $5
FoodSeller 以价格 $15 卖了一盒饭 ...
ProxySeller 代理完成
ProxySeller 代理收取手续费 $5
FoodSeller 退回了价值 $15 的盒饭 ...
ProxySeller 代理完成

使用动态方法的话就可以在不修改代理类的前提下进行方法的扩充,而动态代理有JDK动态代理, CGLIB字节码增强、JAVAASISST等方式。还有另外一个好处,就是各个实现类的可以继承自不同的接口, 所以方法命名也可以不同了。

JDK动态代理方式

JDK动态代理的前提条件是实现类必须实现了某个接口(FoodSeller和TicketSeller必须继承某个接口),代码如下:

public class ProxySeller implements InvocationHandler {
  private Object target;
  public Object getInstance(Object target) {
    this.target = target;
    //Get the proxy object
    return Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        this
    );//To bind interface (this is a defect, cglib made up for this shortcoming)
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    before();
    int price = (int) args[0];
    // 一般情况下,如果只是单纯增加前后的逻辑,此参数无需修改
    Object result = method.invoke(target, price - 5);
    after();
    return result;
  }
  private void before() {
    System.out.println("ProxySeller 代理收取手续费 $5");
  }
  private void after() {
    System.out.println("ProxySeller 代理完成");
  }
}

CGLIB字节码增强方式

CGLIB没有必须实现接口的限制(FoodSeller和TicketSeller可以不继承接口),代码如下:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class ProxySeller implements MethodInterceptor {
  private Object target;
  public Object getInstance(Object target) {
    this.target = target;
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(this.target.getClass());
    // The callback method
    enhancer.setCallback(this);
    // Create a proxy object
    Object o = enhancer.create();
    return o;
  }
  @Override
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    before();
    int price = (int) args[0];
    // 一般情况下,如果只是单纯增加前后的逻辑,此参数无需修改
    Object result = proxy.invokeSuper(obj, new Object[]{price - 5});
    after();
    return result;
  }
  private void before() {
    System.out.println("ProxySeller 代理收取手续费 $5");
  }
  private void after() {
    System.out.println("ProxySeller 代理完成");
  }
}

代理效率

关于各个代理模式的效率如何,可以参考文章《Java代理性能比较》。

参考资料

  • 【注-1】《Java与模式——阎宏》