什么是代理模式?
代理模式是一种结构型设计模式。
代理模式是为了给对象提供一个代理或者占位符,由代理对象全权负责对元对象的访问。
比如说,我们想要购物。在以前,我们可能需要去米厂,面厂去排队购买。但是随着时代的进步,这些日常生活中的东西,我们可以通过超市、淘宝、京东等手段进行购买,这种新颖的购物方式其实就是一种代理模式,而超市、淘宝和天猫等购物渠道其实就相当于代理角色。
再举一个我们生活中的例子,我们平时使用的windows操作系统中的快捷方式,其实就是代理模式的体现。
核心结构
- Subject。抽象主题角色,它通过接口或抽象类的方式,声明了具体主题角色以及代理角色需要实现的业务方法,这样,可以在任何使用具体主题角色的地方使用代理角色。
- RealSubject。具体主题角色,它实现了抽象主题角色,并定义了需要实现的业务逻辑,客户端可以通过调用代理角色来间接调用真实角色。
- Proxy。代理角色,它实现了抽象主题角色的同时,也包含了对具体主题角色的引用。代理角色实现抽象主题的原因,是可以在任何时候都可以对具体主题角色进行代理。代理角色还可以对具体主题角色进行控制,在需要的时候创建代理主题角色,不需要时进行删除,方便对具体主题角色进行管理。
类图展示
代码分析
代理模式包括远程代理、虚拟代理、保护代理、缓冲代理和智能引用代理等几种实现方式,主要分为静态代理和动态代理两大类,我们将分别对这两种情况举例说明。
静态代理
首先,定义抽象主题角色:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 静态代理模式——抽象主题
*/
public interface Push {
public void push ();
}
接着,定义具体主题角色:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 代理模式——具体主题
*/
public class FullAmountPush implements Push {
@Override
public void push() {
System.out.println("full amount push task");
}
}
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
*/
public class AppointPush implements Push{
@Override
public void push() {
System.out.println("appoint push task");
}
}
针对AppointPush对象,生成一个代理角色:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 代理模式——代理
*/
public class AppointPushProxy implements Push {
private AppointPush appointPush = new AppointPush();
@Override
public void push() {
System.out.println("gain the device info");
appointPush.push();
System.out.println("push task has done");
}
}
通过静态代理的实例,我们发现,只需要持有代理角色的对象,即可完成对具体主题角色的调用。
动态代理
通过静态代理模式的代码实例,我们可以很轻易的分析出静态模式的弊端:每个具体主题角色都需要一个一对一的代理角色。这在实际开发过程中可能会生成比较多的类,从而不利于代码的维护。针对这种情况,我们可以使用动态代理模式。
JDK动态代理
还是同样的配方。
抽象主题角色:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 静态代理模式——抽象主题
*/
public interface Push {
public void push ();
}
具体主题角色:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 代理模式——具体主题
*/
public class FullAmountPush implements Push {
@Override
public void push() {
System.out.println("full amount push task");
}
}
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
*/
public class AppointPush implements Push{
@Override
public void push() {
System.out.println("appoint push task");
}
}
代理角色,我们将会使用JDK自带的Proxy来实现动态代理模式:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 代理模式——代理
*/
public class JDKPushProxy implements InvocationHandler{
private Push target;
public JDKPushProxy(Push target) {
this.target = target;
}
@Override
public Push invoke(Object proxy, Method method, Object[] args) throws Throwable {
Push result = (Push) method.invoke(target, args);
return result;
}
public Push getProxy() {
ClassLoader classLoader = target.getClass().getClassLoader();
// 需要具体的主题角色实现接口
Class<?>[] interfaces = target.getClass().getInterfaces();
return (Push) Proxy.newProxyInstance(classLoader, interfaces, this);
}
}
CGLIB动态代理
CGLIB动态代理比较灵活,由于Java单一继承的约束,所以JDK动态代理只能通过接口来实现。但是CGLIB动态代理则逃脱了这种桎梏,既可以使用继承类来实现,也可以同过实现接口来实现。笔者将通过继承的方式做一个CGLIB动态代理的实例。
抽象主题角色:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 代理模式——抽象主题
*/
public abstract class AbstractPush {
public abstract void push();
}
具体主题角色:
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 代理模式——具体主题
*/
public class WebspherePush extends AbstractPush {
@Override
public void push() {
System.out.println("websphere push task");
}
}
通过CGLIB动态代理实现的代理角色。
/**
* Created by <sunshine> mysunshinedreams@163.com on 2017/1/11 0011.
* 代理模式——代理
*/
public class CglibPushProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Push intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws hrowable {
return (Push) methodProxy.invokeSuper(o, objects);
}
public Push getProxy(Class<?> clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return (Push) enhancer.create();
}
}
虽然在这个实例中,笔者展现的是以继承方式实现的CGLIB动态代理,但是在源码中,我们对两种情况都做了测试,具体请移步源码查看。
模式总结
优点
- 实现了调用者和被调用者之间的解耦,在一定程度上降低了系统的耦合度。
- 符合开闭原则。因为只需针对抽象主题角色进行变成扩展即可,一般情况下,添加新的代理角色无需修改原有类,同时也保护了原对象。
缺点
- 有些代理模式的实现很复杂,可能需要大量的额外操作。
- 由于调用者和被调用者之间出现了一层代理角色,所以可能会影响程序性能。
适用场景
代理模式在我们的编程中使用度还是很频繁的,比如Spring AOP就使用了代理模式,根据代理模式所要表现的行为不同,代理模式也有不同的应用场景:
- 远程代理。当调用者需要访问远程对象时,可以使用远程代理。
- 虚拟代理。通过虚拟代理,可以用一个消耗资源较小的对象,代替一个消耗资源较多的对象。
- 保护代理。对不同角色提供不同权限时可以使用保护代理。
- 缓冲代理。如果一个对象需要被频繁的使用,可以使用缓冲代理提供为这个对象提供一个临时的存储空间,这样可以减少不必要的资源消耗。
- 智能引用代理。智能引用代理可以为一个对象的访问提供一些额外的操作。