代理模式
业务类只需要关注业务逻辑本身,保证了业务类的重用性,这是代理的共有优点,同时协调调用者和被调用者的关系,使得两者一定程度的解耦。代理模式是常用的结构型设计模式之一,它为对象的间接访问提供了一个解决方案,可以对对象的访问进行控制。代理模式类型较多,其中远程代理、虚拟代理、保护代理等在软件开发中应用非常广泛。
模式优点
代理模式的共同优点如下:
(1) 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
(2) 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
此外,不同类型的代理模式也具有独特的优点,例如:
远程代理为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
缓冲代理为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。
保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
模式缺点
代理模式的主要缺点如下:
由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。
模式适用场景
代理模式的类型较多,不同类型的代理模式有不同的优缺点,它们应用于不同的场合:
(1) 当客户端对象需要访问远程主机中的对象时可以使用远程代理。
(2) 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。
(3) 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
(4) 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
(5) 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。
静态代理
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
举例
产品经理(客户)需要实现产品需求,于是找技术leader(代理)来找程序猿(实际执行者)写代码。对于产品经理来说,技术leader就是负责完成对应需求的人,对于产品来说,具体是谁完成怎样完成,并不关心。
- 对于产品来说,技术leader和普通程序猿一样,都有完成需求的能力
public interface IProgramer {
void code();
}
- 实际完成需求的苦逼程序猿
public class AndroidProgramer implements IProgramer {
@Override
public void code() {
System.out.println("Android 程序猿正在写代码...写代码......");
}
- 技术leader作为程序猿的代理,指示程序猿完成需求
public class ProgramerProxy implements IProgramer {
private IProgramer mProgramer;
public ProgramerProxy() {
mProgramer = new AndroidProgramer();
}
@Override
public void code() {
mProgramer.code();
}
}
- 产品经理直接把任务给代理人技术leader,并不关心具体的执行过程
public class ProductManager {
public static void main(String args[]){
ProgramerProxy proxy = new ProgramerProxy();
proxy.code();
}
}
这其中就体现出代理模式的优点了,产品经理对于具体的执行过程毫不关心,不管技术leader还是程序猿如何变化,业务逻辑都一样(就是还是调用proxy.code()就行了),即体现了只需要关注业务逻辑本身,保证了业务类的重用性的优点。
执行结果:
Android 程序猿正在写代码...写代码......
优点:
业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。 如果IProgramer中有几百个方法,那么代理类中也要代理这几百个方法,就显得代码很冗余
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。 很明显,如果IProgamer增加一个方法,AndroidProgramer要增加方法,代理类也需要增加方法。
应用场景举例:项目中要引用需要经常更新的第三方库的时候可以考虑这种方式,比如引入一个数据库代理,代理方法无非增删改查几个方法,但该数据库版本可能经常变更,所以这种情况下(一般不会有很多方法,不会有很多子类也不会经常新增方法),使用静态代理还是比较舒服的。
动态代理
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
创建动态代理过程:
一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(…);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、**通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。**
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))
客户所有的方法调用最终都是通过InvocationHandler.invoke()方法执行的。
优点
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。所以无论执行对象中有多少个方法,都只需需要调用这一个方法,而不像静态代理需要代理每一个方法,同样的,当接口新增方法的时候,也只需要在其子类中实现,而不用做其他任何额外的事(静态代理需要在代理类中也实现方法)。代理类由java已经帮我们完成,自己只需要正确调用即可
缺点
诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。
举例
依然是,产品经理要做需求,最后找到程序猿帮他完成了。
万能的程序猿
public interface IProgramer {
void codeWeb();
void codeAndroid();
void codeIOS();
void workOvertime();
}
程序猿
public class ProgramerImpl implements IProgramer {
@Override
public void codeWeb() {
System.out.println("写web代码----------");
}
@Override
public void codeAndroid() {
System.out.println("写Android代码----------");
}
@Override
public void codeIOS() {
System.out.println("写IOS代码----------");
}
@Override
public void workOvertime() {
System.out.println("今晚加班写代码----------");
}
}
程序猿的实际管理者
public class ProgramerHandler implements InvocationHandler{
private IProgramer mProgramer;
public ProgramerHandler(IProgramer programer) {
mProgramer = programer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(mProgramer, args);
return null;
}
}
java内部已经帮我们实现好了代理类,直接调用
public class ProxyProgramerFactory {
public static IProgramer getInstance(){
IProgramer programer = new ProgramerImpl();//实际执行者
ProgramerHandler handler = new ProgramerHandler(programer);//管理者
return (IProgramer) Proxy.newProxyInstance(
programer.getClass().getClassLoader(),
programer.getClass().getInterfaces(),
handler);
}
}
产品经理指挥程序猿干活
public class ProductManager {
public static void main(String args[]){
IProgramer programer = ProxyProgramerFactory.getInstance();
programer.codeWeb();
programer.codeAndroid();
programer.codeIOS();
programer.workOvertime();
}
}
输出结果:
写web代码----------
写Android代码----------
写IOS代码----------
今晚加班写代码----------
代理应用
- 保护代理 : 可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
修改InvocationHandler如下,修改invoke方法,当调用workTime()方法的时候拒绝调用:
public class ProtectProxyHandler implements InvocationHandler {
private IProgramer mProgramer;
public ProtectProxyHandler(IProgramer programer) {
mProgramer = programer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("workOvertime")){
System.out.println("cannot access wrokOverTime----我拒绝加班!!!!!!!!!");
}else {
method.invoke(mProgramer, args);
}
return null;
}
}
代理工厂增加方法getProtectProxy:
public class ProxyProgramerFactory {
public static IProgramer getInstance(){
IProgramer programer = new ProgramerImpl();//实际执行者
ProgramerHandler handler = new ProgramerHandler(programer);//管理者
return (IProgramer) Proxy.newProxyInstance(
programer.getClass().getClassLoader(),
programer.getClass().getInterfaces(),
handler);
}
public static IProgramer getProtectProxy(){
IProgramer programer = new ProgramerImpl();//实际执行者
ProtectProxyHandler handler = new ProtectProxyHandler(programer);//管理者
return (IProgramer) Proxy.newProxyInstance(
programer.getClass().getClassLoader(),
programer.getClass().getInterfaces(),
handler);
}
}
产品指挥程序猿:
public class ProductManager {
public static void main(String args[]){
IProgramer programer = ProxyProgramerFactory.getProtectProxy();
programer.codeWeb();
programer.codeAndroid();
programer.codeIOS();
programer.workOvertime();
}
}
当产品调用的时候输出:
写web代码----------
写Android代码----------
写IOS代码----------
cannot access wrokOverTime----我拒绝加班!!!!!!!!!
- 虚拟代理:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销
举例:某个应用启动的时候去加载线上配置,线上配置很大,会占用很大内存,但加载过程很慢或者网速很慢,甚至其他原因导致下载失败,此时采用虚拟代理,用于代替在加载过程中的默认配置。
配置服务接口:
public interface IConfigService {
String getConfig();
}
实际的在线配置:假设很大很大
public class OnlineConfigService implements IConfigService {
@Override
public String getConfig() {
return "real Online Config and I'm very big, will consume many memory";
}
}
虚拟代理配置:正常的产品逻辑都是,当配置还在下载过程中的时候,都会返回一个默认的配置,这个交给虚拟代理配置来实现
public class VirtualConfigServiceProxy implements IConfigService {
private IConfigService mConfigService;
@Override
public String getConfig() {
if(mConfigService == null) {
startDownloadConfig();
return "----default config-----";
}else{
return mConfigService.getConfig();
}
}
private void startDownloadConfig() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
mConfigService = new OnlineConfigService();
System.out.println("downloadConfig finish---");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
测试虚拟代理:
public class TestVirtualProxy {
public static void main(String[] args){
IConfigService configService = new VirtualConfigServiceProxy();
System.out.println("config=" + configService.getConfig());
try {
Thread.sleep(3000);
System.out.println("config=" + configService.getConfig());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
config=----default config-----
downloadConfig finish---
config=real Online Config and I'm very big, will consume many memory
-远程代理:为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率多见于web项目,原理如图: