代理模式的作用
代理模式可以说是 23 种设计模式中,非常有名的一种了。为什么这么说呢?当今使用 Java 作为后台语言的互联网大厂都离不开 SpringBoot,而 SpringBoot 中,代理模式可以说是满天飞,SpringBoot 最核心的功能面向切面编程(AOP)就是使用代理模式实现的。
代理模式怎么理解呢?
生活中,最接近代理模式的就是火车票的代售点了。
想想为什么会有火车票代售点?
火车站密度小,很多人住的地方离火车站很远;
如果所有人都去火车站买票,火车站将不堪重负,尤其是在春节期间。
所以,代理模式作用在这样一种情景:要访问的对象,直接访问起来开销很大,比如有些对象是通过远程过程调用的方式生成的,访问依次就会有网络开销。有些对象初始化的时候,是要从磁盘加载的,等等。
代理模式的功能和作用就是:给那些不建议直接访问的对象 A,提供一个代理对象 B。当需要访问 A 时,通过 B 去访问,B 会做一些优化工作,使得它访问 A 的开销要小得多。
下面看下火车票代售点的例子的实现。
车站和车票
车票接口如下:
/**
- 车票
*/
public interface Tickets {
//售卖车票
void sellTickets();
}
车站需要去实现上面的 sellTickets 方法,进行卖票:
/**
- 火车站
*/
public class RailwayStation implements Tickets{
//火车站构造函数
public RailwayStation() {
initStation();
}
//火车站售卖车票
@Override
public void sellTickets() {
System.out.println("火车站售卖车票");
}
//初始化火车站
private void initStation() {
System.out.println("初始化火车站,比如调配工作人员、设立售票窗口");
}
}
车站在初始化的时候,需要执行一些动作,比如调配工作人员、设立售票窗口等,这样,通过车站去买票的开销就很大。
火车票代售点
代售点也可以买票,代售点的票也是从车站来的,和上面不同的是,代售点从车站拿票的时候,是不需要每次都让车站初始化的,如下:
/**
- 火车票代售点
*/
public class ProxySellStation implements Tickets{
//火车站
private RailwayStation railwayStation;
//代售点售卖车票
@Override
public void sellTickets() {
//初始化火车站
if (null == railwayStation) {
railwayStation = new RailwayStation();
}
//售卖车票
railwayStation.sellTickets();
}
}
可以看到,代售点会去判断车站有没有初始化成功,有的话,就直接执行售卖车票的动作。
通过一个主类,看下程序的执行结果:
public class Main {
//主函数
public static void main(String[] args) {
//初始化代售点
Tickets tickets = new ProxySellStation();
//代售点售卖车票
tickets.sellTickets();
//代售点售卖车票
tickets.sellTickets();
}
}
看下运行结果:
图 1 运行结果
代售点卖了两次车票,只有第一次需要初始化车站。符合预期。
JDK 动态代理
上面的代理模式中,我们需要为每一个被代理的类新建一个代理类,这样很低效,这种成为静态代理。
与之相反的就是动态代理,即:我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口。
JDK 动态代理是一种常见的动态代理机制,它是 Java 原生的,原理是在运行时动态生成类字节码,并加载到 JVM 中的。
如果你不了解也没关系,这是比较难的知识,建议结合 Spring 或者 SpringBoot 去学习。
一步步看下 JDK 动态代理如何使用。
定义一个动态代理类
动态代理类的作用类比于上面的火车票代售点:
/**
- 定义一个 JDK 动态代理类
*/
public class MyInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* 执行被代理对象的方法
*/
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("开始执行方法之前:");
//执行被代理对象的方法
Object result = method.invoke(target, args);
//返回执行结果
return result;
}
}
和代售点一样,它也需要一个成员变量,存储被代理的对象。然后在 invoke 中,执行被代理对象的被代理方法。在这个方法执行前后,我们都可以加入自己的代码。
不同的是,这里的被代理对象 target 是通用的,可以使用任意类的对象。
获取被代理对象并使用
接下来通过一个工厂方法,获取被代理的对象:
//获取被代理对象的方法
public static Object getProxy(Object target) {
//返回被代理的对象
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new MyInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
这也是通用的,意味着我们不需要为不同的代理对象,编写不同的获取方法。
最后是调用被代理对象,执行方法:
//主函数
public static void main(String[] args) {
//通过代理获取对象
Tickets railwayProxy = (Tickets)getProxy(new RailwayStation());
//执行方法
railwayProxy.sellTickets();
}
看下执行结果:
运行结果
符合预期。