代理模式也被称为委托模式,它是结构型设计模式的一种。在现实生活中我们用到类似代理模式的场 景有很多,比如代理上网、打官司等。 定义:为其他对象提供一种代理以控制对这个对象的访问。 代理模式的结构图如图所示:
代理是什么?
代理也称“委托”,分为静态代理和动态代理,代理模式也是常用的设计模式之一,具有方法增强、高扩展性的设计优势。其设计就是限制直接访问真实对象,而是访问该对象的代理类。这样的话,我们就保护了内部对象,如果有一天内部对象因为某个原因换了个名或者换了个方法字段等等,那对访问者来说一点不影响,因为他拿到的只是代理类而已,从而使该访问对象具有高扩展性。然而,代理类可以实现拦截方法,修改原方法的参数和返回值,满足了代理自身需求和目的,也就是是代理的方法增强性。
在代理模式中有如下角色:
- Subject:抽象主题类,声明真实主题与代理的共同接口方法。
- RealSubject:真实主题类,是抽象主题的实现方法,代理类所代表的真实主题。客户端通过代理类间接地调用真实主题类的方法。
- Proxy:代理类,持有对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行,并且可以添加自己的方法执行。
- Client:客户类。
需要注意的有下面几点:
- 用户只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
- 接口真正实现者是上图的 RealSubject,但是它不与用户直接接触,而是通过代理。
- 代理就是上图中的 Proxy,由于它实现了 Subject 接口,所以它能够直接与用户接触。
- 用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。
下面我们用代码来帮助我们理解什么是静态和动态代理,当然这里的代理是分为静态和动态两种的,我们分开来写,并对比说明。
静态代理:
我们平常去电影院看电影的时候,在电影开始之前是不是经常会先去买一些零食、真正播放前会放广告呢?
电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告,用以维持电影院的正常营业。
现在用代码来进行模拟。
首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。
1.抽象主题类:
public interface Movie {
void play();
}
然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。
2.真实主题类:
/**
* 真正播放的电影
*/
public class RealMovie implements Movie{
@Override
public void play() {
System.out.println("幕后真正执行:正在为您播放《唐人街探案2》");
System.out.println("************************");
}
}
这个表示真正的影片。它实现了 Movie 接口,play() 方法调用时,影片就开始播放。那么 Proxy 代理呢?
3.代理类
在代理类里面,我们在真正执行play播放电影前,需要实现代理类的一些任务,例如:卖零食、播放片头广告等。
/**
* 用户可以接触到的幕后的代理类
*/
public class StaticCinema implements Movie {
RealMovie movie;
public StaticCinema(RealMovie movie) {
this.movie = movie;
}
@Override
public void play() {
//代理类的额外一些方法的插入
lingShi();
guangGao();
//真正的幕后方法
movie.play();
}
//静态代理需要为您做的额外工作
private void lingShi() {
System.out.println("静态代理:卖瓜子、饮料啦,挣点外快维持影院营业...");
}
//静态代理需要为您做的额外工作
private void guangGao() {
System.out.println("静态代理:正片前广告,挣点外快维持影院营业...");
}
}
Cinema 就是 Proxy 代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是卖零食、播放广告。现在,我们编写测试代码。
4.客户测试类
public void test() {
//静态代理测试
RealMovie realMovie = new RealMovie();
Movie cinema = new StaticCinema(realMovie);
cinema.play();
}
下面我们观察结果:
System.out: 静态代理:卖瓜子、饮料啦,挣点外快维持影院营业...
System.out: 静态代理:正片前广告,挣点外快维持影院营业...
System.out: 幕后真正执行:正在为您播放《唐人街探案2》
现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。下面要介绍的内容就是动态代理。
动态代理:
既然是代理,那么它与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。
那么在动态代理的中这个动态体现在什么地方?
上一节代码中 Cinema 类是代理,我们需要手动编写代码让 Cinema 实现 Movie 接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 Movie 接口的代理,而不需要去定义 Cinema 这个类。这就是它被称为动态的原因。
下面我们先来看看怎么创建出一个动态代理的,其实也没什么难的,第一点:就固定需要实现系统的InvocationHandler接口,里面重写的invoke方法可以拿到真实执行方法,返回即可;第二点:需要Proxy.newProxyInstance(),来反射调用我们刚创建的代理对象即可。看几遍熟悉就好了,下面我们将上面的静态代理写法改成动态代理的写法:
如上,1.抽象主题类 和 2.真实主题类是一样的,我们仅需要添加一个动态代理,
3.动态代理:
public class DynamicCinema implements InvocationHandler {
private Object obj;
public DynamicCinema(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理需要为您做的额外工作
if (method.getName().equals("play")) {
System.out.println("动态代理:卖瓜子、饮料啦,挣点外快维持影院营业...");
System.out.println("动态代理:正片前广告,挣点外快维持影院营业...");
}
Object result = method.invoke(obj, args);
return result;
}
}
这个和静态代理的作用是一样的,下面我们来写测试代码:
4.客户测试类:
public void test() {
//静态代理测试
RealMovie realMovie = new RealMovie();
Movie cinema = new StaticCinema(realMovie);
cinema.play();
//动态代理:电影院
DynamicCinema dynamicCinema = new DynamicCinema(realMovie);
Movie playing = (Movie) Proxy.newProxyInstance(RealMovie.class.getClassLoader(),
new Class[]{Movie.class}, dynamicCinema);
playing.play();
}
我们看下结果,其实和静态代理的实现结果是一样的:
System.out: 静态代理:卖瓜子、饮料啦,挣点外快维持影院营业...
System.out: 静态代理:正片前广告,挣点外快维持影院营业...
System.out: 幕后真正执行:正在为您播放《唐人街探案2》
System.out: ************************
System.out: 动态代理:卖瓜子、饮料啦,挣点外快维持影院营业...
System.out: 动态代理:正片前广告,挣点外快维持影院营业...
System.out: 幕后真正执行:正在为您播放《唐人街探案2》
System.out: ************************
现在我们有了和静态代理一样的一个动态代理,也就是说这个电影院的代理功能有卖零食、播放广告、播放电影,这三个功能,我们都用静态和动态代理实现了一遍,但是随着影院越做越大,发现相当多的客户他们看完电影有去大保健的需求,影院觉得这是个挣钱门路,所以呢,有一天影院又代理起来了大保健行业的买卖,那么我们做的就是再影院里面创建一个代理对象是大保健的就可以了,咱们来看看是怎么创建的呢?
1.抽象主题类:
public interface DaBaoJian {
void anMo();
}
大保健主题的代理创建出来了,但是需要有真实的技师提供服务吧?请上冰冰出场
2.真实主题类:
public class BingBingDaBaoJian implements DaBaoJian {
@Override
public void anMo() {
System.out.println("幕后真正执行:头牌按摩师冰冰正在服务");
}
}
3.动态代理的创建:由于我们还是在影院,所以和上面的DynamicCinema类是一样的,但是为了区分服务,在invoke里面,我们需要判断下,否则,给大保健的客户卖零食、播广告,那就业务混乱了:
public class DynamicCinema implements InvocationHandler {
private Object obj;
public DynamicCinema(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理需要为您做的额外工作
if (method.getName().equals("play")) {
System.out.println("动态代理:卖瓜子、饮料啦,挣点外快维持影院营业...");
System.out.println("动态代理:正片前广告,挣点外快维持影院营业...");
}
if (method.getName().equals("anMo")) {
System.out.println("动态代理:推销精油,以维持影院营业...");
System.out.println("动态代理:推销服务,以维持影院营业...");
}
Object result = method.invoke(obj, args);
return result;
}
}
其实是一样的,就是加了个if,来判断下我们的业务场景。
4.客户测试类:
public void test() {
//静态代理测试
RealMovie realMovie = new RealMovie();
Movie cinema = new StaticCinema(realMovie);
cinema.play();
//动态代理:电影院
DynamicCinema dynamicCinema = new DynamicCinema(realMovie);
Movie playing = (Movie) Proxy.newProxyInstance(RealMovie.class.getClassLoader(),
new Class[]{Movie.class}, dynamicCinema);
playing.play();
//动态代理:大保健
BingBingDaBaoJian bingBingDaBaoJian = new BingBingDaBaoJian();
DynamicCinema dynamicCinema1 = new DynamicCinema(bingBingDaBaoJian);
DaBaoJian daBaoJian = (DaBaoJian) Proxy.newProxyInstance(BingBingDaBaoJian.class.getClassLoader(),
new Class[]{DaBaoJian.class}, dynamicCinema1);
daBaoJian.anMo();
}
下面我们看下测试结果:
System.out: 静态代理:卖瓜子、饮料啦,挣点外快维持影院营业...
System.out: 静态代理:正片前广告,挣点外快维持影院营业...
System.out: 幕后真正执行:正在为您播放《唐人街探案2》
System.out: ************************
System.out: 动态代理:卖瓜子、饮料啦,挣点外快维持影院营业...
System.out: 动态代理:正片前广告,挣点外快维持影院营业...
System.out: 幕后真正执行:正在为您播放《唐人街探案2》
System.out: ************************
System.out: 动态代理:推销精油,以维持影院营业...
System.out: 动态代理:推销服务,以维持影院营业...
System.out: 幕后真正执行:头牌按摩师冰冰正在服务
代理的作用
可能有同学会问,已经学习了代理的知识,但是,它们有什么用呢?
主要作用,还是在不修改被代理对象的源码上,进行功能的增强。
这在 AOP 面向切面编程领域经常见。
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态
代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一
个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各
部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
至于,如何通过代理来进行日志记录功能、性能统计等等,这个大家可以参考 AOP 的相关源码,然后仔细琢磨。
同注解一样,很多同学可能会有疑惑,我什么时候用代理呢?
这取决于你自己想干什么。你已经学会了语法了,其他的看业务需求。对于实现日志记录功能的框架来说,正合适。
至此,静态代理和动态代理者讲完了。
总结
- 代理分为静态代理和动态代理两种。
- 静态代理,代理类需要自己编写代码写成。
- 动态代理,代理类通过 Proxy.newInstance() 方法生成。
- 不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
- 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
- 动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。
- 代理模式本质上的目的是为了增强现有代码的功能。
下面讲解下动态代理写法的相关语法:
动态代码涉及了一个非常重要的类 Proxy。正是通过 Proxy 的静态方法 newProxyInstance 才会动态创建代理。
Proxy:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
它的 3 个参数意义。
- loader 自然是类加载器
- interfaces 代码要用来代理的接口
- h 一个 InvocationHandler 对象
InvocationHandler:
InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。
proxy 代理对象
method 代理对象调用的方法
args 调用的方法中的参数
因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
参考:https://blog.csdn.net/briblue/article/details/73928350
代码地址:https://github.com/buder-cp/DesignPattern/tree/master/DesignPatterns