定义
是Java中的一种重要的设计模式。它为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。代理模式分为两种,静态代理和动态代理。
作用
- 某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和目标对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- 代理类除了是客户类和目标类的中介之外,还可通过给代理类增加额外的功能来拓展目标类的功能。这样子我们只需要修改代理类,而不需要再修改目标类,从而符合代码设计的开闭原则。
演示案例
其实直接说定义是非常枯燥乏味的,我们还是通过一个日常生活中的常见例子来探析代理模式吧。大家都听过海淘,代购吧,这些行为的本质其实就是消费者,通过中间商或者中间人,从海外购买货品的一种消费行为。对应到我们的代理模式来说,货品其实就是被代理对象,中间商是代理对象,消费者则是被调用对象。
代理模式(静态代理)类图如下:
在这里,假如有个消费者想购买国外最新款的PS4,它需要通过中间商代理,通过中间商去国外采购,工厂生产出来后,再通过中间商将PS4快递给消费者。对比上图我们可以清晰地认识到,PS4就是真实对象,也就是被代理对象,中间商就是代理对象,消费者是调用者。由于真实对象和代理对象需要实现共同的接口,也就是需要一个抽象对象。所以这里我们先定义一个游戏工厂接口,如下:
/**
* 游戏工厂,包含所有游戏的生产
*/
public interface GameFactory {
/**
* 生产方法
* @return
*/
String make();
}
然后定义Ps4工厂,实现GameFactory接口,在这里它就是真实对象。
/**
* Ps4工厂
*/
public class Ps4Factory implements GameFactory {
@Override
public String make() {
return "ps4";
}
}
接着定义中间商代理类,由类图可知,它也要实现GameFactory接口,在这里它就是代理对象。
/**
* 中间商代理
*/
public class MiddlemanProxy implements GameFactory {
GameFactory factory;
public MiddlemanProxy(GameFactory factory) {
this.factory = factory;
}
@Override
public String make() {
doSomethingBefore();
String toy = factory.make();
System.out.println("生产的玩具:"+toy);
doSomethingAfter();
return null;
}
/**
* 售后服务
*/
private void doSomethingAfter() {
System.out.println("物流配置,用户售后");
}
/**
* 售前服务
*/
private void doSomethingBefore() {
System.out.println("接收订单,生产排期");
}
}
我们可以看到,它除了实现GameFactory接口,在构造时,也需要一个GameFactory实例(由类图可知),然后在make()方法的内部,又通过这个GameFactory实例,调用GameFactory实例本身的make()方法,还在调用前后,做了其他处理。由于Ps4Factory本身也实现了GameFactory接口,所以根据多态的思想,调用者在构造MiddlemanProxy实例时,只要将Ps4Factory实例传入,通过MiddlemanProxy实例调用自身的make()方法的方式,就实现了代理模式。
接下来我们看看消费者(调用者)如何调用代理:
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) {
//消费者要购买ps4,要通过代理
GameFactory ps4Factory = new Ps4Factory();
MiddlemanProxy middlemanProxy = new MiddlemanProxy(ps4Factory);
middlemanProxy.make();
}
}
运行结果如下:
接收订单,生产排期 生产的玩具:ps4 物流配置,用户售后
现在,我们总结一下,类图与我们这个案例的各个类的对应关系:
类图 | 案例 | |
---|---|---|
抽象对象接口 | Subject | GameFactory |
真实对象实现类 | RealSubject | Ps4Factory |
代理对象实现类 | Proxy | MiddlemanProxy |
真实对象执行方法 | RealSubject.play() | Ps4Factory.make() |
代理对象执行方法 | Proxy.play() | MiddlemanProxy.make() |
上面的案例其实是代理模式的一种:静态代理。除了静态代理,还有动态代理,下面我们来了解一下他们的区别,同时也会举另外一个例子来说明如何实现动态代理。
代理模式的种类
静态代理
需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者继承相同的父类。
动态代理
代理类在程序运行时创建的代理方法被成为动态代理。代理类不是在Java代码中定义的。而是在运行时根据在Java代码中的“指示”动态生成的。
我们按照上面的例子来进一步拓展。上面的例子,我们新建了一个游戏工厂的接口,代理对象(中间商)和被代理对象(游戏)都实现了这个接口。现在问题来了,如果这个中间商一直都代理游戏等商品,每当有新的游戏,只要创建新的游戏类,实现游戏工厂接口就行,而不会改动已有的代码。但是像人一样,生意总是越做越大的,随着需求种类的增加,代理对象可能也想代理不同种类的对象,这时候代理类就要实现多个工厂接口,比如实现一个球鞋工厂接口;或者代理相同种类的不同流程,比如,我们上面的make()方法表示制造,如果想代理设计,是不是要在游戏工厂接口中增加设计的方法,既然接口都定义了抽象方法,那么所有的实现类都要实现这个抽象方法。所以功能拓展时,又要涉及到很多已有接口和类的修改,这就违背了开闭原则。那么,我们能不能在运行时,才动态地生成代理对象,而不用写死一个代理类?答案是可以的,这就是我们接下来要介绍的另外一种设计模式——动态代理。
动态代理案例
JDk已经提供了可以实现动态代理的接口InvocationHandler,只要实现了这个接口,设置目标对象,重写invoke()方法,就可以实现动态代理。动态代理的本质是基于反射的。想要深究原理的话,可以看这篇博客:动态代理原理解析。
public class MiddlemanProxy2 implements InvocationHandler {
//1、定义目标对象,即被代理对象
private Object target;
public MiddlemanProxy2(Object target){
this.target = target;
}
//2、通过反射取得代理对象
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
//3、通过代理对象反射调用目标对象的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里可以自定义前置处理
doSomethingBefore();
//核心代码,反射,完成目标对象的方法的调用
Object result = method.invoke(target, args);
System.out.println(result);
//这里可以自定义后置处理
doSomethingAfter();
return result;
}
/**
* 售后服务
*/
private void doSomethingAfter() {
System.out.println("物流配置,用户售后");
}
/**
* 售前服务
*/
private void doSomethingBefore() {
System.out.println("接收订单,生产排期");
}
}
实现InvocationHandler这个接口时,最开始invoke()方法默认返回空,我们可以看到这个方法需要一个proxy,即代理对象,因此我们就要定义一个返回代理对象的getProxy()方法,而构建代理对象,肯定需要知道目标对象,因此我们定义了一个目标对象target的成员变量,通过构造方法传入。然后我们在getProxy()方法中,通过JDK的Proxy.newProxyInstance方法,传入目标对象的类加载器,接口数组,以及InvocationHandler类对象this,生产代理对象。最后我们重写了invoke方法,通过目标对象,反射调用了对应的方法,并且可以在调用前后,自定义相关的处理代码。接下来我们来看调用者代码:
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) {
//消费者要购买ps4,要通过代理
GameFactory ps4Factory = new Ps4Factory();
// MiddlemanProxy middlemanProxy = new MiddlemanProxy(ps4Factory);
// middlemanProxy.make();
MiddlemanProxy2 middlemanProxy2 = new MiddlemanProxy2(ps4Factory);
GameFactory proxy = (GameFactory)middlemanProxy2.getProxy();
proxy.make();
}
}
运行结果如下:
接收订单,生产排期
ps4
物流配置,用户售后
可以看出,他们得到了相同的结果。同样的,假如现在要代理书籍,我们只需要定义书籍工厂的接口和对应的实现类,生成代理对象时,传入书籍实现类对象即可。之前的目标类和代理类本身都不需要被修改,因为动态代理实际上是通过类而不是接口代理的,根本不需要跟目标类实现相同的接口,真正满足了开闭原则。 最后,我们来总结一下静态代理和动态代理的区别吧:
静态代理 | 动态代理 | |
---|---|---|
是否需要实现接口 | 需要 | 不需要 |
可接收的目标对象类 | 事先定义好的类型 | 动态指定的类型 |
实现方式 | 基于接口实现 | 基于类反射 |
特点 | 代码重复,耦合度高,不利于拓展 | 可代理任意类型对象,松耦合 |
最后,有需要的话,博客源码传送门~