概念和功能
代理模式(Proxy Pattern)是让一个类代表另一个类(作为另一个类的代理)去实现该类要求的特定功能。这种类型的设计模式属于结构型模式。
代理模式和装饰器模式很像。装饰器模式是拿到一个类的实例然后再该类基础上进行添加功能;代理模式也是拿到一个类的实例然后再该类的基础上添加功能,但是代理类相对于装饰类更有“针对性”。打个比方:装饰类就好比“锦上添花”,在拿到了“美丽的锦物”之上还绣上“美丽的花”;代理类好比那“丞相”/“宰相”,拿着“皇帝授予的权利”专门来“为皇帝打理朝政”的“臣子”。一个侧重“添加和完善功能”,一个侧重“代理实施功能”。具体代码实现是什么样的下面再看。
使用场景和方法
使用场景大抵是:1、需要使用现有的类的一些功能,但是“不方便直接访问”或者“懒得访问”,需要通过他的“代理”来访问他的功能。像丞相代替皇帝行驶部分功能一样,中介代替买房人行驶获取买房信息办理买房流程等功能(决策等重要功能还是需要买房人自己实现的),代理律师替代理人行驶辩护权一样。只要涉及到部分功能需要“代理”实现的地方都可以用代理模式。
使用方法大抵是:1、持有委托人的实例去实现需要代理的功能。
实例分析
简单的实例:买房子
买(二手房)房子大抵流程是“找房子”和需要一些琐碎的时间来办理过户手续,其中比较耗时的事情是“找房子”,这部分可以很消耗时间,你要去找到在这段时间在你期望的地段有想要卖房子的人,并且他们家的户型、价格、过户手续要求等都和你期望的相差不大。那这部分耗时操作“找房子”你可以交由“中介”去做。
方法一
静态代理
需要托管出去的功能——找房子接口:
public interface FindHouses {
void findHouses();
}
委托人类:
public class HouseBuyer implements FindHouses{
private int money;
private Object expect;
@Override
public void findHouses() {
//find houses in many ways
}
private boolean decideHouse(House house){
//decide whether or not to buy the house
return true;
}
private void pay(){
//pay the money
}
}
中介类:
public class BuyHouseProxy implements FindHouses{
private FindHouses findHouses;
public BuyHouseProxy(FindHouses houseBuyer){
this.findHouses = houseBuyer;
}
@Override
public void findHouses() {
//invoke the houseBuyers' method findHouses to find the Houses as Requested.
findHouses.findHouses();
}
}
自己直接找房子:
//find houses by yourself
FindHouses houseBuyer = new HouseBuyer();
houseBuyer.findHouses();
这么房子肯定还是能找到的,就是很辛苦罢了。
交由中介找房子:
//find houses by proxy
BuyHouseProxy proxy = new BuyHouseProxy(houseBuyer);
proxy.findHouses();
这样子就是中介代替委托人去辛苦的找房子了,你就只需要将相应的要求传达给中介就行了。但是我作为HouseBuyer还有很多事情需要做,比如付房租、付首付、拟购房合同等等,其中很多事情可以交给中介BuyHouseProxy但是还有很多不能交由中介去做,(1.能交给中介的功能:比如拟购房合同——接口需要新加方法,HouseBuyer需要实现该方法,BuyHouseProxy需要实现该方法;2:不能交给中介的功能:比如付首付——接口需要新建一个,HouseBuyer需要实现该接口,BuyHouseProxy需要实现该接口;)同样对于一个系统需要多个服务,每个服务对应都需要一个代理,那这种方式写代理类的工作量就太大了,维护也比较麻烦。为解决这个问题,JDK提供了一种方案:
方法二
动态代理
通过实现InvocationHandler接口来实现运行时生成代理类,我们只需要定义一个你需要“中介”去干哪些事情的接口findHouses和代理生成器的处理类就行,不需要自己新建具体的代理类。
动态代理处理类:
public class DynamicProxyHandler implements InvocationHandler {
private Object object;
public DynamicProxyHandler(final Object object) {
this.object = object;
}
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object, args);
return result;
}
}
由动态代理去找房子:
//find houses by dynamic proxy
FindHouses proxyBuyHouse = (FindHouses) Proxy.newProxyInstance(FindHouses.class.getClassLoader(),
new Class[]{FindHouses.class}, new DynamicProxyHandler(houseBuyer));
proxyBuyHouse.findHouses();
看动态代理处理类就可以发现这个类很像上面的代理类BuyHouseProxy.java,获取到实例之后需要invoke相应参数的方法。而方法Proxy.newProxyInstance前面两个参数[FindHouses.class.getClassLoader(), new Class[]{FindHouses.class}]主要是获取接口的结构来构造动态代理类。那这样我就通过DynamicProxyHandler来生成相关代理类的处理方法了。细分一下newProxyInstance第一个参数是用来定义代理类的classLoader,第二个参数是代理类需要实现的那些接口列表,第三个参数是代理类调用方法的处理类。
两个方法对比: 这个栗子体现不出动态代理的优势,现在我们假设我们很“土豪”——找房子和收集资料都“外包”给“中介”去帮我们去做,那我会有两个service需要proxy去代理。按照我们方法一静态代理去做的话只能新建两个代理类分别“代理实现”两个功能,一个代理类去实现接口“找房子”一个代理类去实现接口“收集购房资料”,他们都是在被代理类实现的基础上“再度实现”相关功能的。针对这种情况,我需要再加一个interface CollectMaterial,一个代理类CollectMaterialProxy,原来的HouseBuyer需要再implements CollectMaterial重写相关的方法。如果按照我们方法二动态代理去做的话,interface CollectMaterial肯定是要加的,但后面我们只需要让原来的HouseBuyer implements CollectMaterial重写相关的方法,然后调用的地方稍微修改下:
//find houses and collect materials by dynamic proxy
Object proxyInstance = Proxy.newProxyInstance(HouseBuyer.class.getClassLoader(), new
Class[]{FindHouses.class, CollectMaterial.class}, new DynamicProxyHandler(houseBuyer));
FindHouses findHouses = (FindHouses) proxyInstance;
CollectMaterial collectMaterial = (CollectMaterial) proxyInstance;
findHouses.findHouses();
collectMaterial.collect();
调用相关方法运行就可以了,不需要再创建具体的代理类了。
动态代理的方法是很灵活的,你可以在一个接口定义多个方法,然后在InvocationHandler里面对method name判断进行不同处理;也可以写多个接口,然后也是在InvocationHandler里面对method name判断进行不同处理;看业务划分去做不同的处理方式。
方法三
CGLIB代理
方法二是“省去了新增proxy类的步骤”,因为这个proxy只是为了“重写一个方法”那它完全可以去掉,方法处理这一步可以提取出来,那就不需要各个proxy类了,只需要这个handler。那那些个“接口”呢?如果没有那些接口行不行?可以!就是方法三:CGLIB代理。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。下面还是看我们的例子
CGLIB代理的处理类:
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(final Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("do sth before");
Object result = methodProxy.invoke(o, args);
System.out.println("do sth after");
return result;
}
}
由CGLIB代理类去找房子和搜集资料:
//find houses and collect materials by cglibProxy
HouseBuyer houseBuyer = new HouseBuyer();
CglibProxy cglibProxy = new CglibProxy();
HouseBuyer buyHouseCglibProxy = (HouseBuyer) cglibProxy.getInstance(houseBuyer);
buyHouseCglibProxy.findHouses();
buyHouseCglibProxy.collect();
但是这个实现方式有一个缺点:创建代理对象需要花费较长的时间,比JDK多得多,虽然CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
结构模式之间的比较
结合之前两个结构型模式可以发现:代理模式是增强功能,装饰器模式是增加功能(某种程度上的增强功能),适配器模式是让功能灵活起来。或者说代理模式专注的是增强功能或者让功能更成熟更专业,装饰器模式专注的是增加功能让被装饰类的功能根据需要拆开开发,适配器模式专注的是增加功能的灵活性让被适配类更加灵活、使用更方便;如果你想说装饰器模式的方法也能实现代理模式呀,在两种模式实现的第一种方式中都是拿到类的实例然后再基础上“包装”方法。确实是,从这种实现方式来说,他们很像。这些模式都是抽象的,实现方式也是多样的,不能以一种实现方式很相近就说他们“一样”。就像你给你女票送饭来表达爱意,这和外卖小哥送饭给你女票概念完全不一样。但是你想让“你送外卖”的这个行为显得更专业那你就可以用“外卖小哥”来代替你送这个外卖。