代理模式是对象的结构模式,代理模式给某一个对象提供一个代理对象,并由代理对象控制原对象的引用。换言之,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或不能直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
一、代理的种类
①远程代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是本机器中,也可是在另一台机器中。
②虚拟代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。
③copy-on-write代理:虚拟代理中的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。
④保护代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
⑤cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
⑥防火墙代理:保护目标,不让恶意用户接近。
⑦同步化代理:使几个用户能够同时使用一个对象而没有冲突。
⑧智能引用代理:当一个对象呗引用是,提供一些额外的操作,比如讲对此对象调用的次数记录下来。
二、代理模式的结构
代理模式所涉及的角色有:
①抽象主题角色:声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以使用代理主题;
②代理主题(proxy)角色:代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主题;控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);代理角色通常在将客户端调用传递给真实的主题之前或者之后,都要执行某个操作,而不是单纯的将调用传给真实主题对象。
③真实主题角色:定义了代理角色所代表的真实对象。
实例类图如下:
示例代码如下:
//抽象主题的源代码
public abstract class Subject {
//声明一个抽象的请求方法
public abstract void request();
}
//真是主题角色源代码
public class RealSubject extends Subject{
public RealSubject(){}
//实现请求方法
public void request(){
System.out.println("From real subject");
}
}
//代理主题角色的源代码
public class ProxySubject extends Subject{
private RealSubject realSubject;
public ProxySubject(){}
@Override
public void request() {
preRequest();
if(realSubject == null){
this.realSubject = new RealSubject();
}
realSubject.request();
postRequest();
}
//请求前的操作
private void preRequest(){
//something you want to do before requesting
}
//请求后的操作
private void postRequest(){
//something you want to do after requesting
}
}
//调用代理主题
Subject subject = new ProxySubject();
subject.request();
从上面的代理主题类的示意性源代码可以看出代理模式是怎样工作的。首先,代理主题并不改变主题的接口,因为模式的用意是不让客户端感觉到代理的存在;其次,代理使用委派将客户端的调用委派给真实的主题对象,换言之,代理主题起到的是一个传递请求的作用;最后,代理主题在传递请求之前和之后都可以执行特定的操作,而不是单纯传递请求。
三、动态代理
在java提供的反射基础上,衍生出来了动态代理。动态代理具有更强的灵活性,因为它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们可以把这种指定延迟到程序运行时由JVM来实现。
我们知道,所谓代理,就是需要代理类和被代理类有相同的对外接口或者说成服务,所以代理类一般都必须实现了所有被代理类已实现的接口,因为接口就是制定了一系列对外服务的标准。动态代理正因为有这样灵活的特性,所以我们在设计动态代理类(DynamicProxy)时不用显式地让它实现与真实主题类(RealSubject)相同的接口(interface),而是把这种实现推迟到运行时。
下面看一下动态代理实现的实例代码:
//抽象主题类,这里不能用abstract抽象类,一定要是interface
interface AbstractSubject {
public void request();
}
// 真实主题类,即被代理类
class RealSubject implements AbstractSubject {
public void request() {
System.out.println("RealSubject's request() ...");
}
}
// 动态代理类,实现InvocationHandler接口
class DynamicProxy implements InvocationHandler {
// 被代理类的实例
Object obj = null;
// 将被代理者的实例传进动态代理类的构造函数中
public DynamicProxy(Object obj) {
this.obj = obj;
}
/**
* 覆盖InvocationHandler接口中的invoke()方法
*
* 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构
* 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
* 控制被代理对象的行为,下面的before、after就是我们可以进行特殊
* 代码切入的扩展点了。
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*
* before :doSomething();
*/
Object result = method.invoke(this.obj, args);
/*
* after : doSomething();
*/
return result;
}
}
// 测试类
public class Client {
public static void main(String[] args) {
// 被代理类的实例
AbstractSubject realSubject = new RealSubject();
// 获得被代理类的类加载器,使得JVM能够加载并找到被代理类的内部结构,以及已实现的interface
ClassLoader loader = realSubject.getClass().getClassLoader();
// 获得被代理类已实现的所有接口interface,使得动态代理类的实例
Class<?>[] interfaces = realSubject.getClass().getInterfaces();
// 用被代理类的实例创建动态代理类的实例,用于真正调用处理程序
InvocationHandler handler = new DynamicProxy(realSubject);
/*
* loader : 被代理类的类加载器
* interfaces :被代理类已实现的所有接口,而这些是动态代理类要实现的接口列表
* handler : 用被代理类的实例创建动态代理类的实例,用于真正调用处理程序
*
* return :返回实现了被代理类所实现的所有接口的Object对象,即动态代理,需要强制转型
*/
//获得代理的实例
AbstractSubject proxy = (AbstractSubject) Proxy.newProxyInstance(
loader, interfaces, handler);
proxy.request();
//打印出该代理实例的名称
System.out.println(proxy.getClass().getName());
}
}
输出内容为:RealSubject's request() ...
总体来说,我们可以按照下面的步骤创建动态代理对象:
①指明一系列的接口来创建一个代理对象;
②创建一个调用处理器对象;
③将这个代理指定为某个其他对象的代理对象;
④在调用处理器的invoke()方法中采取代理,一方面将调用传递真实对象,另一方面执行各种需要做的操作;