静态代理
我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?
电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。
现在用代码来进行模拟。
首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。
package com.frank.test;
public interface Movie {
void play();
}
然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。
public class RealMovie implements Movie {
@Override
public void play() {
System.out.println("您正在观看电影 《肖申克的救赎》");
}
}
这个表示真正的影片。它实现了 Movie 接口,play() 方法调用时,影片就开始播放。那么 Proxy 代理呢?
public class Cinema implements Movie {
RealMovie movie;
public Cinema(RealMovie movie) {
super();
this.movie = movie;
}
@Override
public void play() {
guanggao(true);
movie.play();
guanggao(false);
}
public void guanggao(boolean isStart){
if ( isStart ) {
System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
} else {
System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
}
}
}
Cinema 就是 Proxy 代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。现在,我们编写测试代码。
public class ProxyTest {
public static void main(String[] args) {
RealMovie realmovie = new RealMovie();
Movie movie = new Cinema(realmovie);
movie.play();
}
}
然后观察结果:
电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!
您正在观看电影 《肖申克的救赎》
电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!
现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。下面要介绍的内容就是动态代理。
动态代理
既然是代理,那么它与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。
那么在动态代理的中这个动态体现在什么地方?
上一节代码中 Cinema 类是代理,我们需要手动编写代码让 Cinema 实现 Movie 接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 Movie 接口的代理,而不需要去定义 Cinema 这个类。这就是它被称为动态的原因。
也许概念比较抽象。现在实例说明一下情况。
假设有一个大商场,商场有很多的柜台,有一个柜台卖茅台酒。我们进行代码的模拟。
public interface SellWine {
void mainJiu();
}
SellWine 是一个接口,你可以理解它为卖酒的许可证。
public class MaotaiJiu implements SellWine {
@Override
public void mainJiu() {
System.out.println("我卖得是茅台酒。");
}
}
然后创建一个类 MaotaiJiu,对的,就是茅台酒的意思。
我们还需要一个柜台来卖酒:
InvocationHandler : Proxy.newProxyInstance生成动态代理类时会传入 InvocationHandler 对象,之后代理类调用方法后会自动调用这个invoke方法利用反射( method.invoke(pingpai, args)),从而在调用方法前后增加其他的代码。
注意在 GuitaiA的构造方法要传入一个对象(类型为Object,因为 Proxy.newProxyInstance 可以根据前两个参数生成不同类型的代理类()),这个对象在invoke时传入说明是哪个对象调用的这个方法。
public class GuitaiA implements InvocationHandler {
private Object pingpai;
public GuitaiA(Object pingpai) {
this.pingpai = pingpai;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("销售开始 柜台是: "+this.getClass().getSimpleName());
method.invoke(pingpai, args);
System.out.println("销售结束");
return null;
}
}
GuitaiA 实现了 InvocationHandler 这个类,这个类是什么意思呢?大家不要慌张,待会我会解释。
然后,我们就可以卖酒了。
public class Test {
public static void main(String[] args) {
MaotaiJiu maotaijiu = new MaotaiJiu();
// GuitaiA ()中可以传入其他类型,此时要保证 Proxy.newProxyInstance 时传的参数类型对应。所以可以生成很多类型的代理类,调用这些类的不同方法,但是因为使用的是同一个处理类( InvocationHandler ),所以调用方法的前后逻辑都是该处理类( InvocationHandler )中的invoke方法。
InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
MaotaiJiu.class.getInterfaces(), jingxiao1);
dynamicProxy.mainJiu();
}
}
这里,我们又接触到了一个新的概念,没有关系,先别管,先看结果。
销售开始 柜台是: GuitaiA
我卖得是茅台酒。
销售结束
看到没有,我并没有像静态代理那样为 SellWine 接口实现一个代理类,但最终它仍然实现了相同的功能,这其中的差别,就是之前讨论的动态代理所谓“动态”的原因。