以下讲解及UML类图都是基于上面的例子进行
如果你是一个不懂得拒绝别人的同学,但是又不想让别人打扰你,那么你可以找一个黑脸同学做你的代理。这就是代理的功能:控制管理对象的访问。就如互联网研发团队中,产品经理就是研发同学的代理。业务方不会直接找到研发同学提需求,只能通过产品经理访问研发。
RMI技术
RMI(remote method invoke)远程方法调用。RMI应用程序通常包括两个独立的程序:服务端和客户端,通常这两个程序是运行在两个不同的JVM中的。调用机制:stub 和 skeleton ,使得客户端像调用本地程序一样调用位于另一个JVM中的服务,stub负责进行参数的编组和处理网络细节。在这里stub就是服务端程序位于客户端的一个代理。
如 webservice 中,客户端是不需要关注服务端运行在哪个JVM中,地址是什么的。它只要调用本地stub即可,stub负责分发服务。
stub控制了客户端程序对服务端对象的访问。
定义
为另一个对象提供占位符或替身,以控制对这个对象的访问。代理对象与被代理对象实现相同的接口,
被代理的对象可以是远程对象,如RMI中的服务端对象;可以是创建开销大的对象(资源宝贵的对象);可以是需要进行安全控制的对象,如ERP权限等。。
代理模式分为静态代理和动态代理,分别是在编译期创建代理类、运行期创建代理类。
静态代理代码及UML
eg:安全起见,需要对登录权限进行安全控制,提供一个LoginService对象的代理。代码地址:
uml图
动态代理代码及UML
如果需要在运行时动态创建代理类,使用jdk自带的动态代理。同样是登陆权限进行安全访问控制,代码地址:
https://github.com/Afengzi/design-pattern/blob/master/src/main/java/com/afengzi/design/proxy/dynamic/LoginExecutor.java
spring代理实践
spring的核心模块AOP的实现基于jdk的动态代理模式实现的。在运行时为被代理对象创建一个占位符,在占位符中对请求进行过滤、限制或者增加功能等操作。
数据库连接池中的连接数是有上限的,不能每次请求都从池中获取一个连接,需要创建一个代理对创建链接进行控制,以延迟对数据库连接数的创建时机。
LazyConnectionDataSourceProxy 就是链接数据库的代理,管理对数据库链接的访问。getConnection(....)方法中并不返回一个真实的Connection对象,而是返回Connection对象的代理。
public Connection getConnection(String username, String password) throws SQLException {
return (Connection) Proxy.newProxyInstance(
ConnectionProxy.class.getClassLoader(),
new Class[] {ConnectionProxy.class},
new LazyConnectionInvocationHandler(username, password));
}
每次对Connection对象进行操作都会转到InvocationHandler类中的invoke方法中。
JDK中的虚拟代理
jdk本身用到了很多代理模式,如:CopyOnWrite
1、jdk 1.6 中提供了很多CopyOnWrite集合,如 CopyOnWriteArrayList \ CopyOnWriteSet \ CopyOnWriteMap 。咱们就用CopyOnWriteArrayList做例子。CopyOnWrite思想就是在写集合操作时,先copy出
array
的 一个副本出来,对副本进行写操作,完成后再把
array
的 引用指向副本。在copy副本时需要先加锁,否则在并发的情况下回拷贝出多个副本出来。读操作则不需要进行枷锁控制,因为
array
是volatile进行修饰的,本身具有可见性。
在这里,副本就是
array
的一个虚拟代理,在进行写操作时,向副本中写数据。如果array占用内存几十兆,那么副本也会占用内存几十兆,会严重消耗内存资源。 所以副本只有在写操作时才会创建,这样就延迟了copy时机,延迟创建。
注意:
一、因为arrray和副本会同时存在内存中,所以消耗内存非常大;
二、写操作是操作array的虚拟代理对象,并不是真正的array对象,所以数据一致性会延迟。
代理变体
常见的代理模式有:代理防火墙,对网络上的请求进行过滤,控制;虚拟代理:延迟创建开销大的对象,在真正需要的时机创建。