前段时间老婆看上了一条不知道什么牌子的皮带,在国内的商店里面搜了一下发现都没有货,于是跑去咨询她加的代购小姐姐有没有做这款皮带的代购。看着她的这一通操作,我不禁感觉一阵熟悉,这代购的模式和 代理模式 何其相似,代购小姐姐代替了实际的客户前去实际地点进行商品的试用和购买,然后在发给对应的客户,在这一过程中客户只需要告诉代购小姐姐需要购买某件商品即可。
代理模式是一种 对象结构型设计模式 ,它通过引入一个 代理对象 来控制对目标对象的访问控制。代理对象处在客户端对象和目标对象,扮演着中介的角色,它能够屏蔽客户端对象不需要感知的内容或者增加一些额外的处理能力。
从上面的类图中可以看到,为了保证客户端能够一致性地对待代理对象和目标对象,需要客户端对象面向两者的抽象进行编程。
AbstractSubject
:抽象主题类,作为代理对象和目标对象的共同接口/抽象类,提供给客户端对象进行调用;ConcreteSubject
:具体主题类,实际提供客户端对象所需功能的对象,在这个对象进行了实际的业务处理,是代理对象所代理的实际对象;Proxy
:代理主题类,在代理对象中持有了实际对象的引用,在进行实际对象调用的同时,代理对象还提供了访问控制、创建/删除实际对象、额外功能处理等能力;这里通过代理模式来实现一下上面说的代购的例子。
package ProxyPattern;
/**
* 抽象商场类
*
* @author brucebat
* @version 1.0
*/
public abstract class AbstractShop {
/**
* 购买
*
* @param commodityName 商品名称
*/
public abstract void buy(String commodityName);
}
package ProxyPattern;
/**
* 实际商店类
*
* @author brucebat
* @version 1.0
*/
public class ConcreteShop extends AbstractShop {
/**
* 根据商品名称进行数量是否足够检查
*
* @param commodityName 商品名称
* @return 商品数量是否足够
*/
public boolean checkEnough(String commodityName) {
return "皮带".equals(commodityName);
}
@Override
public void buy(String commodityName) {
System.out.println("购买" + commodityName + "完成");
}
}
package ProxyPattern;
/**
* 商品代理类
*
* @author brucebat
* @version 1.0
*/
public class ShopProxy extends AbstractShop {
private final ConcreteShop concreteShop;
public ShopProxy() {
this.concreteShop = new ConcreteShop();
}
public void preBuy(String commodityName) {
boolean checkEnough = concreteShop.checkEnough(commodityName);
if (checkEnough) {
System.out.println("当前商品货源充足可以购买");
return;
}
throw new NullPointerException("当前商品处于缺货状态");
}
@Override
public void buy(String commodityName) {
this.preBuy(commodityName);
concreteShop.buy(commodityName);
this.afterBuy();
}
public void afterBuy() {
System.out.println("完成购买,开始发货");
}
}
package ProxyPattern;
/**
* @author brucebat
* @version 1.0
*/
public class App {
public static void main(String[] args) {
AbstractShop abstractShop = new ShopProxy();
abstractShop.buy("皮带");
}
}
结果如下:
当前商品货源充足可以购买
购买皮带完成
完成购买,开始发货
在上面的概念和例子中,代理类和目标类都是事先已经存在的,其接口/抽象方法和所需要代理的方法都已经指明,这种代理模式被称为 静态代理 。在这种代理模式下,每有一个需要代理的目标对象都需要对应的编写一个与之对应的代理对象,这就会造成系统中类个数的爆炸式增长。那么如何解决这个问题呢?动态代理 由此诞生。
动态代理能够让系统在运行时根据实际需要 动态创建代理对象 ,并且可以让同一个代理对象代理不同目标对象和不同方法。在Java中较为常见的两种动态代理方式如下:
JDK原生的动态代理主要依赖两个主要的类,位于 java.lang.reflect
中的 Proxy
和 InvocationHandler
。
Proxy
:提供了创建动态代理类和其实例对象的静态方法,同时它也是这些被创建出来的动态代理类的超类。在创建动态代理对象过程中需要将对应的 InvocationHandle
与之绑定,代理对象通过该调用处理器进行目标对象的实际业务方法调用。InvocaitonHandler
:是由代理对象的调用处理器实现的接口,每个代理对象都会关联一个调用处理器。当在代理对象上调用代理方法时,方法调用会被编码并分配到对应调用处理器的invoke方法进行执行。在 InvocationHandler
当中我们可以实现上面提到的诸如访问控制、额外功能处理等逻辑处理,这里去实现 InvocationHandler
时,可以写成针对所有类都适用的通用逻辑,也可以是针对特定类的定制化处理逻辑。 这里我们在具体看下 Proxy
当中提供的创建动态代理类和创建动态代理对象的方法签名,方法的具体源码就不一一列出,有兴趣的同学可以自行查找:
/**
* 创建了一个Class类型的代理类,在参数列表中可以看到需要提供类加载器和需要代理的接口数组。
* 注意,这里的接口数组需要和目标类的接口列表一致。
*/
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
/**
* 该方法返回了一个动态创建的代理对象,在参数列表中可以看到需要提供类加载器和需要代理的接口
* 数组以及调用处理器。
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
从上面的方法签名中可以看到,JDK动态代理 所代理的对象为接口 ,对于抽象类和具体类则无能为力。对于这一缺陷,我们可以使用下面这个动态代理工具进行补足。
CGLib是一款功能强大的代码生成工具,它可以在运行期扩展Java类和实现Java接口。区别于JDK动态代理,CGLib既能够对接口进行代理,也能够对抽象类或者具体类进行代理。
和JDK动态代理相同,在CGLib当中也存在两个类 Enhancer
和 MethodInterceptor
来分别实现动态创建代理类以及方法调用拦截的功能。
Enhancer
:和Proxy不同的是,Enhancer
并不是生成一个包含目标对象实例的代理对象,而是生成动态子类以启用方法拦截。除了实现接口之外,它还允许代理对象扩展具体的基类。动态生成的子类会覆盖超类的非最终方法,并具有回调到用户定义的拦截器实现的钩子。MethodInterceptor
:提供了方法维度的拦截处理,是最初也是最通用的回调类型(注意,这里指的是CGLib类库当中的 MethodInterceptor
,而不是AOP项目中的)。它在AOP术语中相当于 “环绕通知” ,这意味着你可以在调用super方法(即被代理的父类方法)之前和之后处理一些自定义的逻辑。总的来看,无论是JDK动态代理还是CGLib都是在运行时通过动态生成字节码的形式来动态创建代理对象,区别就在于JDK动态代理只能代理接口,其与目标对象间的关系为 关联关系 ,而CGLib则即能够代理接口也能够代理类,其与目标类的关系为 继承关系 ,通过动态生成子类方法覆盖父类方法完成代理处理。
代理模式可以说是日常开发中一个用途广泛、作用重大的结构型设计模式,它提供了一个间接访问目标对象并提供对目标对象访问控制的解决方案。该解决方案让我们在编码过程中可以更加灵活、可扩展的进行逻辑处理,比如Java RMI中使用到的远程代理,在进行本地开发时,我们只需要像使用本地对象一样使用远程代理对象进行变成即可,无须关心远程代理在实际调用方法时需要进行序列化、网络通信等处理。
但这并不意味着代理模式就没有缺陷,就如同天下没有不赚差价的中间商,代理模式的实现引入了代理对象,这就带了额外资源的消耗,同时有些代理模式的实现逻辑较为复杂,实现起来较为困难且耗时,这也带来了额外的成本。所以如何合理地使用代理模式是一个需要持续思考的问题。
本文分享自 Brucebat的伪技术鱼塘 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!