设计模式 - 代理模式
代理模式
代理模式是一种设计模式,提供了对目标对象的访问方式,通过代理对象调用目标对象的方法,可以在实现目标对象的基础上,增强额外的功能,即扩展了目标对象。 在使用过程中调用者不直接调用目标对象,而是通过代理对象访问目标对象。
首先讲一个费玉清嘿嘿嘿的段子(大家应该都知道)来引入代理模式
费玉清想减肥于是就去了一家减肥中心,前台小姐说:有五百的有一千的还有两千的,功效看价钱,你要哪种?他首先选了五百的想试试,前台小姐说,拿着这个钥匙,去第一扇门,男人拿着钥匙去了第一扇门。板凳上坐着一个穿着比基尼的美女,美女说:你追我,如果你追到我,我就让你嘿嘿嘿~他就追啊追,终于追到了,然后美女就让他嘿嘿嘿了。
费玉清回到家一看,果然瘦了五斤。过了几天,他又去那家店,前台小姐准备问,费玉清说,别问了,我是老顾客,接着选了一千的,前台小姐给了他一把钥匙,让他打开第二扇门。一个美女坐在板凳上,这次什么都没穿,说:你追我,如果你追上我,我就让你嘿嘿嘿。费玉清没有追上美女,可他们还是嘿嘿嘿了。
回去一看,又瘦了五斤。过了几天,他又去了那家店,点了两千的,前台小姐给了他一把钥匙,让他打开第三扇门。第三扇门里坐着一个母猩猩,说:我追你,如果我追到你,你就让我嘿嘿嘿。
在这个段子里面,我们可以把前台小姐当成代理对象,而比基尼辣妹、什么都没穿的辣妹和母猩猩就是被代理对象,前台小姐给钥匙的动作就是确定目标对象,前台小姐也知道房间里面的辣妹都会做哪些事情,所以前台小姐可以作为房间里面辣妹的代理对象。
三种代理模式
代理模式的实现主要有三种:
- 静态代理
- 动态代理(JDK 代理)
- Cglib代理
静态代理
代理对象和目标对象实现相同的接口或者继承相同的父类,代理对象是目标对象的扩展,持有目标对象的引用,会调用目标对象的方法。
示例
把嘿嘿嘿段子用静态代理模式翻译成代码
- 首先订单一个接口 IWomen 表示减肥中心里面的女性,她们会 run 和 heiheihei 同时她们也有自己的名字
1 2 3 4 5 6 7 8
public interface IWomen { void run(); void heiheihei(); String getName(); }
定义 500、1000、2000 的目标对象实现 IWomen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
/** * 比基尼辣妹 500 */ public class BikiniWoman implements IWomen { @Override public void run() { System.out.println("比基尼辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿"); } @Override public void heiheihei() { System.out.println("比基尼辣妹开始嘿嘿嘿"); } @Override public String getName() { return "比基尼辣妹"; } } /** * 没穿衣服的辣妹 1000 */ public class NakedWoman implements IWomen { @Override public void run() { System.out.println("没穿衣服的辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿"); } @Override public void heiheihei() { System.out.println("没穿衣服的辣妹开始嘿嘿嘿"); } @Override public String getName() { return "没穿衣服的辣妹"; } } /** * Gorilla 2000 */ public class GorillaWoman implements IWomen { @Override public void run() { System.out.println("Gorilla 说:我追你,如果我追到你,你就让我嘿嘿嘿"); } @Override public void heiheihei() { System.out.println("费玉清被开始嘿嘿嘿"); } @Override public String getName() { return "Gorilla"; } }
定义一个代理对象——前台小姐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
/** * 减肥中心前台接待小姐 */ public class Receptionist implements IWomen { private IWomen iWomen; public Receptionist() { // 默认是 500 比基尼辣妹的代理 this.iWomen = new BikiniWoman(); } public Receptionist(IWomen iWomen) { this.iWomen = iWomen; } public void setWomen(IWomen iWomen) { this.iWomen = iWomen; } @Override public void run() { System.out.println("前台小姐叫 " + iWomen.getName() + " 起跑了"); iWomen.run(); if ("Gorilla".equals(iWomen.getName())) { System.out.println("前台小姐看到 " + iWomen.getName() + " 追到费玉清了"); } else { System.out.println("前台小姐看到 " + iWomen.getName() + " 被追到了"); } } @Override public void heiheihei() { System.out.println("前台小姐安排 " + iWomen.getName() + " 开始嘿嘿嘿了"); iWomen.heiheihei(); } @Override public String getName() { return "前台小姐"; } }
费玉清登场
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
public class FeiYuQing { public static void main(String[] args) { // 费玉清来到减肥中心找到前台接待小姐 Receptionist receptionist = new Receptionist(); // 询问价格后,先选个 500 的试试 IWomen iWomen500 = new BikiniWoman(); receptionist.setWomen(iWomen500); receptionist.run(); receptionist.heiheihei(); System.out.println(); // 费玉清回家之后一看,瘦了5斤 // 过了几天又一次来到了减肥中心,这次来个 1000 的 IWomen iWomen1000 = new NakedWoman(); receptionist.setWomen(iWomen1000); receptionist.run(); receptionist.heiheihei(); System.out.println(); // 这次又瘦了5斤 // 效果这么明显,费玉清决定加大力度,这次直接上 2000 IWomen iWomen2000 = new GorillaWoman(); receptionist.setWomen(iWomen2000); receptionist.run(); receptionist.heiheihei(); } }
代码运行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
前台小姐叫 比基尼辣妹 起跑了
比基尼辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿
前台小姐看到 比基尼辣妹 被追到了
前台小姐安排 比基尼辣妹 开始嘿嘿嘿了
比基尼辣妹开始嘿嘿嘿
前台小姐叫 没穿衣服的辣妹 起跑了
没穿衣服的辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿
前台小姐看到 没穿衣服的辣妹 被追到了
前台小姐安排 没穿衣服的辣妹 开始嘿嘿嘿了
没穿衣服的辣妹开始嘿嘿嘿
前台小姐叫 Gorilla 起跑了
Gorilla 说:我追你,如果我追到你,你就让我嘿嘿嘿
前台小姐看到 Gorilla 追到费玉清了
前台小姐安排 Gorilla 开始嘿嘿嘿了
费玉清被开始嘿嘿嘿
在上面的代码中,费玉清每次到减肥中心都是先跟前台小姐接触,通过前台小姐安排辣妹进行“减肥活动”,而前台小姐根据价格为费玉清安排不同的辣妹,并且可以监控安排减肥过程,在这个过程中干活的是辣妹(被代理类),前台小姐(代理类)主要是接活安排工作,那怎么知道被代理类能不能干活呢?知根知底就行,大家都是 IWomen,你能做啥,我能做啥都很清楚。
小结
- 静态代理模式主要使用了 Java 的多态
- 可以做到对目标对象进行功能扩展。
- 代理对象需要与目标对象实现一样的接口,所以会产生很多代理类,同时,一旦接口增加方法,目标对象与代理对象都要维护。
动态代理(JDK 代理)
动态代理可以用来解决静态代理存在的缺点:产生大量代理类并且接口改动时需要同时维护目标对象和代理对象。
动态代理过程中我们不需要通过实现同个接口来手动创建代理类,代理对象的生成是利用 JDK 的 API 动态的在内存中创建的(需要我们指定创建代理对象/目标对象实现的接口的类型)。
动态代理使用到的类是 java.lang.reflect.Proxy
,使用到类中的方法是 newProxyInstance
Proxy.newProxyInstance() 接收三个参数:
- ClassLoader loader 用于定义代理类的类加载器
- Class<?>[] interfaces 要实现的代理类的接口列表
- InvocationHandler h 指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法(是一个接口,需要编写自己的实现类)
示例
把嘿嘿嘿段子用动态代理模式翻译成代码
IWomen 接口以及实现 IWomen 的各个辣妹实现同静态代理一样,这里就不赘述了,直接从 InvocationHandler
开始
InvocationHandler 实现类,通过 invoke 实现了对目标对象方法的扩展,把静态代理中代理对象的增强功能实现放在这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
public class WomenHandler implements InvocationHandler { private Object object; public WomenHandler(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); String simpleName = object.getClass().getSimpleName(); Object returnValue; if ("run".equals(methodName)) { System.out.println("动态生成的前台小姐叫 " + simpleName + " 起跑了"); returnValue = method.invoke(object, args); if ("GorillaWoman".equals(simpleName)) { System.out.println("动态生成的前台小姐看到 " + simpleName + " 追到费玉清了"); } else { System.out.println("动态生成的前台小姐看到 " + simpleName + " 被追到了"); } } else if ("heiheihei".equals(methodName)) { System.out.println("动态生成的前台小姐安排 " + simpleName + " 开始嘿嘿嘿了"); returnValue = method.invoke(object, args); } else { returnValue = method.invoke(object, args); } return returnValue; } }
费玉清来减肥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
public class FeiYuQing { public static void main(String[] args) { // 第一次 500 被代理对象 比基尼辣妹 IWomen bikiniWoman = new BikiniWoman(); Object proxyInstance = Proxy.newProxyInstance(bikiniWoman.getClass().getClassLoader(), bikiniWoman.getClass().getInterfaces(), new WomenHandler(bikiniWoman)); IWomen iWomen = (IWomen)proxyInstance; iWomen.run(); iWomen.heiheihei(); System.out.println(); // 第二次 1000 被代理对象 没穿衣服的辣妹 IWomen nakedWoman = new NakedWoman(); proxyInstance = Proxy.newProxyInstance(bikiniWoman.getClass().getClassLoader(), bikiniWoman.getClass().getInterfaces(), new WomenHandler(nakedWoman)); iWomen = (IWomen)proxyInstance; iWomen.run(); iWomen.heiheihei(); System.out.println(); // 第三次 2000 被代理对象直接上 Gorilla IWomen gorillaWoman = new GorillaWoman(); proxyInstance = Proxy.newProxyInstance(bikiniWoman.getClass().getClassLoader(), bikiniWoman.getClass().getInterfaces(), new WomenHandler(gorillaWoman)); iWomen = (IWomen)proxyInstance; iWomen.run(); iWomen.heiheihei(); } }
代码运行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
动态生成的前台小姐叫 BikiniWoman 起跑了
比基尼辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿
动态生成的前台小姐看到 BikiniWoman 被追到了
动态生成的前台小姐安排 BikiniWoman 开始嘿嘿嘿了
比基尼辣妹开始嘿嘿嘿
动态生成的前台小姐叫 NakedWoman 起跑了
没穿衣服的辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿
动态生成的前台小姐看到 NakedWoman 被追到了
动态生成的前台小姐安排 NakedWoman 开始嘿嘿嘿了
没穿衣服的辣妹开始嘿嘿嘿
动态生成的前台小姐叫 GorillaWoman 起跑了
Gorilla 说:我追你,如果我追到你,你就让我嘿嘿嘿
动态生成的前台小姐看到 GorillaWoman 追到费玉清了
动态生成的前台小姐安排 GorillaWoman 开始嘿嘿嘿了
费玉清被开始嘿嘿嘿
和静态代理代码不同的是,费玉清每次来到减肥中心见到的前台小姐都是动态创建出来的。
小结
- 动态代理仅支持接口代理,不支持抽象类代理
- 不需要编写代理类,但是目标对象一定要实现接口
Cglib 代理(子类代理)
静态代理和动态代理都需要目标对象实现一个接口,依赖接口来实现代理模式,但是有些情况下目标对象只是单独的一个类不需要实现接口,这时候就无法使用动态代理或者静态代理实现代理模式了,需要使用第三种代理模式 Cglib 代理
Cglib 代理,也叫作子类代理,它是在内存中构建一个子类对象,并在子类中采用方法拦截的技术拦截所有父类方法的调用,从而实现对目标对象功能的扩展。但因为采用的是继承,所以不能对 final 修饰的类进行代理,同理,如果目标对象的方法为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。JDK 动态代理与 CGLib 动态代理均是实现 Spring AOP 的基础。
Cglib 代理的实现步骤如下:
- 创建一个方法拦截器实现 MethodInterceptor 接口,在 intercept 方法中增强对目标对象的实现
- 创建 Enhancer 类设置父类以及拦截方法回调,调用 create() 方法创建子类对象(代理对象)
- 使用 Cglib 生成的代理对象
示例
还是以嘿嘿嘿为例,找比基尼辣妹进行“减肥活动”
创建一个方法拦截器实现 MethodInterceptor 接口,在 intercept 方法中增强对目标对象的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
public class CglibMethodInterceptor implements MethodInterceptor { private Object target; public CglibMethodInterceptor(Object target) { this.target = target; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { String methodName = method.getName(); String simpleName = target.getClass().getSimpleName(); Object returnValue; if ("run".equals(methodName)) { System.out.println("Cglib 动态生成的子类前台小姐叫 " + simpleName + " 起跑了"); // 两种方式调用都可以 // returnValue = method.invoke(target, objects); returnValue = methodProxy.invokeSuper(o, objects); if ("GorillaWoman".equals(simpleName)) { System.out.println("Cglib 动态生成的子类前台小姐看到 " + simpleName + " 追到费玉清了"); } else { System.out.println("Cglib 动态生成的子类前台小姐看到 " + simpleName + " 被追到了"); } } else if ("heiheihei".equals(methodName)) { System.out.println("Cglib 动态生成的子类前台小姐安排 " + simpleName + " 开始嘿嘿嘿了"); // returnValue = method.invoke(target, objects); returnValue = methodProxy.invokeSuper(o, objects); } else { // returnValue = method.invoke(target, objects); returnValue = methodProxy.invokeSuper(o, objects); } return returnValue; } }
创建 CglibProxy 类封装 Enhancer 生成子类的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public class CglibProxy { private Object target; public CglibProxy(Object target) { this.target = target; } //给目标对象创建一个代理对象 public Object getProxyInstance() { //1.工具类 Enhancer en = new Enhancer(); //2.设置父类 en.setSuperclass(target.getClass()); //3.设置回调函数 en.setCallback(new CglibMethodInterceptor(target)); //4.创建子类(代理对象) return en.create(); } }
使用 Cglib 生成的代理对象
1 2 3 4 5 6 7 8 9 10
public class FeiYuQing { public static void main(String[] args) { BikiniWoman bikiniWoman = new BikiniWoman(); Object proxyInstance = new CglibProxy(bikiniWoman).getProxyInstance(); BikiniWoman cglibBikiniWoman = (BikiniWoman) proxyInstance; cglibBikiniWoman.run(); cglibBikiniWoman.heiheihei(); } }
代码运行结果:
1
2
3
4
5
Cglib 动态生成的子类前台小姐叫 BikiniWoman 起跑了
比基尼辣妹说:你追我,如果你追到我,我就让你嘿嘿嘿
Cglib 动态生成的子类前台小姐看到 BikiniWoman 被追到了
Cglib 动态生成的子类前台小姐安排 BikiniWoman 开始嘿嘿嘿了
比基尼辣妹开始嘿嘿嘿
小结
- Cglib 代理是通过继承目标对象创建子类作为代理对象实现的,可以用与代理一些不需要实现接口的类
- 由于是通过继承实现的,所以无法代理用 final 修饰的类和方法
总结
- 代理模式中两个重要的对象就是代理对象和目标对象,代理对象可以对目标对象进行扩展增强,调用者可以通过代理对象访问目标对象。
- 代理模式的实现有三种:静态代理、动态代理、Cglib 代理,静态代理需要手动创建和维护代理类,动态代理是通过 JDK 的 API 实现动态创建代理对象的,这两种都是依赖接口实现,即目标类都必须实现某个接口,而 Cglib 代理是通过继承目标类创建子类作为代理类实现的,目标类不一定需要实现接口,可以是单独的一个类。