概述
所谓代理模式,实际上就是「将某一件事托管给第三方去做」,如我们买房子一般情况下并不是直接联系房主,而是通过中介去了解房子,中介做为我们买房子这件事的第三方。 我们不需要知道中介是如何联系客户的,以及其他的很多细节,我们只需要联系中介即可。这就叫「代理模式」。
有一件很重要的事情,在学习设计模式的时候,我认为第一步应该知道它能做什么,然后再看自己应不应该学,最后才是学。否则为了学而学,是没有意义的。
那么代理模式能给我们带来什么呢?
1.隐藏具体功能实现细节
2.对原先的方法进行加强,不影响原先的使用者
3.对一个类的所有(或自定)方法进行批量加强
注意,这里的「加强」的含义是指在原先代码的基础上增加其他代码。
而代理模式又分为:
- 静态代理模式
- 动态代理模式
且随文章逐个学习,便明悟。
整体
静态代理
根据概述中所阐述,想必已经知道它的具体概念了。那么我们在代码中可以将这个模式进行抽象一下。它分为三大块,分别是:
- 抽象主题(接口),定义事迹主题所需要的的方法
- 真实主题(实现类),定义抽象主题中的具体业务,是最终被代理的对象
- 代理类,也实现了抽象主题接口,在内部含有对真实主题的引用,它可以访问、控制、拓展真实主题的功能
当学习到这里,我的第一个想法就是——这不就是Spring中的AOP嘛?好吧,AOP实际上就是通过代理模式所实现的。
首先,我们现在联想一个「用户通过第三方购票APP购买火车票」的场景。它的抽象主题是什么?买票嘛,那么通过谁买票呢? 自然是车站买票啦。因此我们得知,一般情况下我们去车站买票还需要排队啊什么什么的,这是就出现了一个代理人——第三方购票APP。
好了,场景分析完毕下面直接开始代码。
购票接口
public interface SellTickets {
void sell();
}
车站(真实主题)
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("成功购买一张火车票");
}
}
第三方购物APP(代理类)
public class ProxyPoint implements SellTickets{
private TrainStation trainStation = new TrainStation();
/**
* 通过「代理点」的封装,可以实现对真实主题的加强
*/
@Override
public void sell() {
System.out.println("我是代理点,我收取了5%的费用");
trainStation.sell();
System.out.println("我是代理点,我抢到了票");
}
}
最终客户端
写到这里你应该明白了,我们通过一个代理类(中间人)对「购票」这件事进行了一层代理。很简单吧?这就是静态代理模式,我们可以通过这种模式去隐藏、拓展代码中的细节。
public static void main(String[] args) {
ProxyPoint proxyPoint = new ProxyPoint();
proxyPoint.sell();
}
动态代理
通过上述的代理模式,你应该已经明白它的主要作用了吧。不知道你有没有觉得这种方式是不是好麻烦呀,我们每增加一些代码,可能都需要我们去专门创建一个代理类。因此,就衍生了「动态代理」模式。它的好处在于所有增加的代码使用一个集中的方法进行管理。
而动态代理一共有两种实现方式,一种是JDK中自带的方式,它的原理是通过对应的接口去代理,而第二种是通过第三方工具库「cglib」去通过字节码技术创建子类代理。说到这里很绕吧,没事,我们只需要记住当要代理的「主题」是通过接口实现的,我们就可以直接通过JDK自带的方式,反之通过cglib库。
JDK动态代理模式
同样的,我们根据上述「静态代理」的段落所示,我们仍然需要「车站、第三方购票APP、买票接口」三个要素,这里就不再复制。
按照之前的思路,我们在动态代理形式中已经不需要重新创建代理类了,可以集中在一个方法之中。
如下所示,我们创建了一个名为「ProxyFactory」的代理工厂类,并在其中编写了「getProxyObject()」方法,很显然,我们通过这样一个方法能够获得一个「加强的买票方法」。
public class ProxyFactory {
private TrainStation trainStation = new TrainStation();
public SellTickets getProxyObject() {
return (SellTickets) Proxy.newProxyInstance(
trainStation.getClass().getClassLoader(),
trainStation.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代收点收取了一定的服务费用");
return method.invoke(trainStation, args);
}
}
);
}
}
具体是怎么实现的呢?主要代码在于「Proxy.newProxyInstance()」方法,它是JDK中提供创建动态代理的方法,一共拥有三个参数,即:
- 目标接口的类加载器
- 目标接口的类字节码
- 具体的实现方式
很显然,前两者都是要的接口类对应的信息,那么我们大胆的想象,「Proxy.newProxyInstance()」方法便是通过这两者进行构建的接口代理。而第三个参数呢,便是我们具体要执行(加强)那些代码,在其中编写。
此处的重中之重便是「InvocationHandler」中的「invoke」方法,它其中携带了三个参数,分别是:
- Object proxy 代理对象
- Method method 代理对象所调用的方法
- Object[] args 代理对象调用方法所传递的参数
当用户使用我们所构造的动态代理类的时候,会直接执行「InvocationHandler」中的「invoke」方法。我们可以直接在这个方法之中对其进行操作。
因此,我们在代码中可以直接编写「要加强的代码」和调用原先的代码。那么,我们该如何调用原先的方法呢?我们可以直接通过第二个参数(Method)的「invoke」方法去实现。需要将被代理的对象为参数(车站类)。这里要注意歧义,Method中的「invoke」方法与当前的「InvocationHandler」中的「invoke」方法毫无相关。 前者的作用是调用被代理对象(车站)与当前「method」所对应的方法。
之后,我们将对应方法的返回值返回出去,便完成了动态代理的操作。
public class Client {
public static void main(String[] args) {
SellTickets proxyObject = new ProxyFactory().getProxyObject();
proxyObject.sell();
}
}
cglib
首先,我们需要通过maven引入对应库的坐标。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
当我们将包导入成功之后,便可以直接进行操作。通过前面所讲述,cglib主要通过字节码技术对目标类子类代理,并且不需要接口。
首先,我们需要创建一个「Enhancer」类型的对象。它便是创建动态代理的构造器,我们需要告诉它,我们要对那个类进行加强,因此使用「setSuperclass」方法。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TrainStation.class);
那么,我们在哪里加强呢?这个时候我们就需要去创建一个回调函数了,即在其中编写加强代码。
class Proxy implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(11);
method.invoke(trainStation,args);
return null;
}
}
需要实现「MethodInterceptor」接口,并实现「intercept」方法,这个方法与上述JDK动态代理相似,便不再撰述。
其后通过以下代码完成创建。
enhancer.setCallback(new Proxy());
TrainStation t = (TrainStation) enhancer.create();
便完成。
总结
今天详细学习了设计模式中的代理模式,但是存在一个问题——知道如何使用JDK和cglib为我们提供的方法,但并不知道具体是如何实现的,因此导致本篇博文中可能产生很多歧义。后续应不断学习补充,以待将来对本篇博文查漏(错)补缺。
本页的评论功能已关闭