1. 简介
代理模式,为了控制委托对象的访问,为其提供了一个代理类,该代理类拥有委托类对象的引用。代理类在委托类和客户之间起到中介的作用,用来控制委托对象的成员、方法等的访问,从而达到与客户代码的解耦。
一般的委托类只负责自身的业务处理,而代理类中会负责调用和使用委托类方法的逻辑处理,以便更好的使用委托对象中的方法,从而完成客户代码的需求。所以,代理类的目的,主要是控制委托对象的访问。
代理模式一般有如下的角色:
代理抽象接口:委托类与代理类共同继承该接口,他们拥有相同的接口方法。
委托类:真正客户需要的核心需求和功能,由委托类完成。
代理类:控制委托对象的访问,与客户进行交互,拥有委托对象的引用。
一般的,经常使用的java代理模式分为:静态代理和动态代理(jdk代理)。
2. 类图
如下,为代理模式的设计类图,这里你会发现,它与装饰模式的设计类图相似。笔者认为,两者在本质上是由区别的,主要体现在他们的使用目的上:
代理模式主要是对委托类对象进行访问控制,用于约束客户对委托对象的使用,它并不会破坏委托类的核心功能,也不会为委托类增加功能。
装饰模式主要目的是对装饰类的功能进行扩展,为了更加灵活的组合这些功能,才衍生出更多的装饰器,用于装饰前一个类对象。
有些地方写到的一个区别是:代理模式中的委托对象是在代理类中确定好的,且委托类对象的生成是在代理类中做的。这点不敢苟同,设计模式是灵活的,一种设计模式的写法不是固定死的,它的使用目的和原理是设计模式的灵魂,而它的设计结构上并不是一成不变的。我们可以通过外部将委托类对象摄入到代理类中,同样也可以在代理类构造函数中new出一个委托类对象,这些都不会影响对代理模式的的核心思想。
3. 代码实例
3.1. 静态代理
假设有一个委托者,委托代理律师为自己代理诉讼。那么,委托者和律师就是委托和代理的关系。代码如下:
interface ILawsuit {
void sue();
void wirteLawsuit();
}
class EDelegator implements ILawsuit {
public EDelegator() {
}
public void sue() {
System.out.println("Delegator start sue");
}
public void wirteLawsuit() {
System.out.println("Delegator start oral lawsuit");
}
}
class ELawyer implements ILawsuit {
private EDelegator delegator;
public ELawyer(EDelegator delegator) {
this.delegator = delegator;
}
public void sue() {
System.out.println("do some work before sue");
this.delegator.sue();
System.out.println("do some work after sue");
}
public void wirteLawsuit() {
System.out.println("do some work before write lawsuit");
this.delegator.sue();
System.out.println("do some work after write lawsuit");
}
}
客户代码调用:
EDelegator delegator = new EDelegator();
ELawyer lawyer = new ELawyer(delegator);
lawyer.wirteLawsuit();
lawyer.sue();
输出结果:
do some work before write lawsuit
Delegator start sue
do some work after write lawsuit
do some work before sue
Delegator start sue
do some work after sue
如上,代理律师在为委托者写诉讼的相关资料,以及申请起诉的时候,都会做一些工作,这些工作可能是流程化的,委托者不必要知道代理律师具体怎么做,这不属于它的职责,客户(法院)也不会代替律师做这些事情。所以才有了律师这样的角色,代理委托者去办理,专业的人做专业的事情。
3.1. 动态代理
如上,如果代理律师为委托者代理的以上两件事的前后工作内容是一样的话(例如代理律师会计算下此次服务的金额),我们是不是可以对这两个方法做同样的事呢。如果委托者类中新增方法,代理律师的工作内容也是一样的,如果是静态代理,那么代理类也需要在新增的方法中加同样的代码,这样显然会造成代码的冗余。
动态代理就可以解决这样的问题,动态的意思就是在程序运行中生成代理对象,根据动态的生成的代理对象,来对委托者进行代理。
在动态代理中,委托者类和接口类没有发生变化,只是代理类由一个中间类反射生成。这个中间类就是负责生成代理类,以及封装代理类对委托者类对象的约束行为,然后由生成的代理类对象进行调用。
那么这个中间类需要继承jdk提供的InvocationHandler接口,实现invoke的回调接口方法:
class DynamicProxyHandler implements InvocationHandler {
private Object delegator;
public DynamicProxyHandler(Object delegator) {
this.delegator = delegator;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("do some work before proxy");
Object invokeObj = method.invoke(delegator, args);
System.out.println("do some work after proxy");
return invokeObj;
}
}
上面说了,我们需要使用反射机制,生成代理对象。从类图中我们可以看到,代理类与委托类都继承了代理接口类,那么我们就可以通过这个接口类进行反射。如下,是客户代码的使用:
ILawsuit lawsuitProxy = (ILawsuit)Proxy.newProxyInstance(
ILawsuit.class.getClassLoader(),
new Class[] {ILawsuit.class},
new DynamicProxyHandler(delegator));
lawsuitProxy.wirteLawsuit();
lawsuitProxy.sue();
代理对象的生成,是由Proxy类的静态方法newProxyInstance实现的,参数分别是代理类的类加载器,根据接口类反射获取代理类的所有方法或生成一个代理类,中介者调用器类对象。
然后就可以使用生成的代理类对象,调用相关的方法了,输出结果如下:
do some work before proxy
Delegator start oral lawsuit
do some work after proxy
do some work before proxy
Delegator start sue
do some work after proxy
可以看到wirteLawsuit和sue方法的调用实际上是中介者类的invoke方法调用实现的。动态生成的类对象实际上是编译器在编译时根据我们的代码自动生成了一个代理类,这是对泛型的使用。至于java内部具体的原理,后面如果有时间可以加以说明。
以上,我们可以看出动态代理的一个好处,它可以对那些相同的代理约束或访问限制做统一处理,避免相同的代码多处使用,对程序猿是一种生产力的解放,同时也方便更好的维护。