平凡人中的大多数
概念
看过Spring的同学们都对AOP有所耳闻,aop即是一种动态代理,也使用了代理模式,那么什么是代理模式呢?本片文章即是从比较规范的角度来说明代理模式
在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。 如买票的黄牛即是代理,我们看电影时电影院也是代理
- 代理模式属于结构型模式
- 给某一个对象提供一个代理,并由代理对象控制对原对象的引用
- 通过代理的方式避免暴露被代理对象或者说代理不容易被取得的对象,满足开闭原则
结构
角色
代理模式一共分为四种角色
- 主题接口(Subject)角色:定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
- 真实主题(Real Subject)角色:真正实现业务逻辑的类;
- 代理(Proxy)角色:用来代理和封装真实主题;
- 客户端:使用代理类和主题接口完成一些工作
UML
例子
具体的程序,我同步更新到Github上了,一直都有更新,喜欢可以fork同步
通过结构中我们可以看出来有四个主要角色,为了简单起见,我们就建立四个类
-
主体接口
public interface DraSession { String query(); }
-
真实主题
public class DraSessionImpl implements DraSession { /** * 这个构造函数是为了模拟DraSessionImpl初始化所消耗的时间 */ DraSessionImpl() { try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String query() { return "我是返回的结果"; } }
-
代理角色
public class DraSessionImplProxy implements DraSession { private DraSessionImpl draSession; /** * 当真正使用这个方法的时候才初始化 * @return 查询的返回值 */ @Override public String query() { System.out.println("我这里可以前置加工"); if (draSession == null) { draSession = new DraSessionImpl(); } return draSession.query(); } }
-
客户端
public class ProxyClient { public static void runProxy() { DraSession draSession = new DraSessionImplProxy(); System.out.println(draSession.query()); } }
-
输出
我这里可以前置加工 我是返回的结果
通过结果我们可以看到,如果是直接运行真实主题的话,那么就是只能返回结果。所以我们通过代理模式增强了类和方法
但其实另外一点,这是一个懒加载的例子,通过代理模式,我们让DraSession
等到用到时才真正去连接(因为连接初始化比较耗时),而不是在没有用到方法时就直接初始化,这种懒加载的思想在Hibernate/Mybatis都有体现
扩展
我们可以发现,上面的代理是静态的,它是在编译期就已经产生了。这时你已经想到了, 还有一种代理模式是动态代理,动态代理是指在运行时动态生成代理类 , 代理类的字节码将在运行时生成并载入当前代理的 ClassLoader 。
动态代理最负盛名的应用就是SpringAOP。
-
根据上面的
DraSession
的例子,我们用java.java.reflect
包下的类来用动态代理修改一下public class DraSessionHandler implements InvocationHandler { private DraSessionImpl draSession = null; /** * 代理方法 * @return o 被代理方法增强的返回值 * @param method 被代理的方法 * @param objects 被代理的方法的参数 * @param o 被代理的类 */ @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("我这里可以前置加工"); if (draSession == null) { draSession = new DraSessionImpl(); } return draSession.query(); } } public class ProxyClient { public static void runDynamicProxy() { DraSession draSession = (DraSession) Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class[]{DraSession.class}, new DraSessionHandler()); System.out.println(draSession.query()); } }
-
生成动态代理类的方法很多,如,JDK 自带的动态处理、CGLIB、Javassist 或者 ASM 库。JDK 的动态代理使用简单,它内置在 JDK 中,因此不需要引入第三方 Jar 包,但相对功能比较弱。CGLIB 和 Javassist 都是高级的字节码生成库,总体性能比 JDK 自带的动态代理好,而且功能十分强大。ASM 是低级的字节码生成工具,使用 ASM 已经近乎于在使用 Java bytecode 编程,对开发人员要求最高,当然,也是性能最好的一种动态代理生成工具。但 ASM 的使用很繁琐,而且性能也没有数量级的提升,与 CGLIB 等高级字节码生成工具相比,ASM 程序的维护性较差,如果不是在对性能有苛刻要求的场合,还是推荐 CGLIB 或者 Javassist。
应用
应用场景
- 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
- 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
- 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
- 指针引用,是指当调用真实的对象时,代理处理另外一些事。比如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它,或当第一次引用一个持久对象时,将它装入内存,或是在访问一个实际对象前,检查是否已经释放它,以确保其他对象不能改变它。这些都是通过代理在访问一个对象时附加一些内务处理;
- 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate/Mybatis中就存在属性的延迟加载和关联表的延时加载。 当 Hibernate 加载实体 bean 时,并不会一次性将数据库所有的数据都装载。默认情况下,它会采取延迟加载的机制,以提高系统的性能。Hibernate 中的延迟加载主要分为属性的延迟加载和关联表的延时加载两类。实现原理是使用代理拦截原有的 getter 方法,在真正使用对象数据时才去数据库或者其他第三方组件加载实际的数据,从而提升系统性能。
应用实例
- nginx:nginx可以作为用户和后台诸如tomcat服务器的中间层,即作为用户的代理
- SpringAOP/动态代理
- Java RMI
- servlet filter
优劣
代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
其主要缺点是:
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;