代理模式也是一种很常见的设计模式。它使用代理对象完成用户请求,屏蔽用户对其真实对象的访问。
在软件设计中,使用代理模式的意图也很多,比如因为安全原因,需要对用户屏蔽客户端直接访问真实的对象;或在远程调用过程中,使用代理类处理远程方法调用的细节(如RMI);也可能是为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。(实现延迟加载的作用,和单例模式实现的区别是单例模式能够直接访问实例对象)
代理模式主要参与者有4个,分别是
- 主题接口:定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法
- 真实主题:真正实现业务逻辑的类
- 代理类:用来代理和封装真实主题
- Main : 客户端,使用代理类和主题接口完成一些工作
静态代理模式
考虑这样一种情况,某客户端软件会根据客户请求去数据库查询数据。在查询数据之前,需要先获得数据库连接,软件开启时,初始化系统所有的类,此时尝试获得数据库连接。当系统有大量的类似操作存在时(比如xml解析等),所有这些操作同时进行会使系统的启动速度变得非常缓慢。为此,使用代理模式,使用代理类,封装对数据库查询中的初始化操作,当系统启动时,初始化这个代理类,而非真实的数据库查询类,而代理类什么都没有做,因此,它的构造是相当迅速的。
为此利用代理模式设计出这样一种结构:
- IDBQuery 为主题接口,定义了代理类和真实类需要对外提供的服务
- DBQuery是真实主题,负责实际的业务操作
- DBQueryProxy是DBQuery的代理类。
IDBQuery实现如下:
public interface IDBQuery {
String request();
}
DBQuery实现如下:
package bupt.xiaoye.charpter2;
public class DBQuery implements IDBQuery{
public DBQuery(){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
@Override
public String request() {
return "request string";
}
}
DBQueryProxy 实现如下:
public class DBQueryProxy implements IDBQuery{
private IDBQuery real = null;
@Override
public String request() {
//在真正需要的时候,才创建
if(real == null){
real = new DBQuery();
}
return real.request();
}
}
Main 角色实现如下:
public class Client {
public static void main(String[] args){
IDBQuery q = new DBQueryProxy();
q.request();
}
}
动态代理模式
一种用于转发请求,进行特殊处理的机制。使用动态代理,可以对很方便的对请求进行任何处理(如
事务,日志等)
动态代理是指在运行时,动态生成代理类。即,代理类的字节码将在运行的时候载入当前的ClassLoader。与静态代理类相比,动态类有诸多好处。
动态代理是指在运行时,动态生成代理类。即,代理类的字节码将在运行的时候载入当前的ClassLoader。与静态代理类相比,动态类有诸多好处。
- 首先,不需要为真实主题写一个形式上完全一样的封装类,
- 其次,使用一些动态代理的生成方法甚至可以在运行时指定代理类的执行逻辑,从而大大提升了系统的灵活性。
生成动态代理类的方法很多,包括
- JDK 自带的动态代理:使用简单,内置在JDK中,不需要第三方类库,但功能相对比较弱
- CGLIB和Javassist : 高级字节码生成库,总体性能比JDK 自带的好,而且功能强大
- ASM : 低级的字节码生成工具,接近于 Java bytecode 编程,对开发人员要求最高,当然性能也最好
JDK 动态代理实现:
首先,使用 JDK 动态代理需要实现一个 处理方法调用的 Handler ,用于实现代理方法的内部逻辑,然后使用这个 handler 生成动态代理对象:public class JdkDbQueryHandler implements InvocationHandler { IDBQuery real = null; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (real == null) { real = new DBQuery(); } return real.request(); } public static IDBQuery createJdkProxy() { // 动态生成代理类,它使用系统ClassLoader , 以IDBQuery.class为基础,使用 JdkDbQueryHandler // 进行包裹 IDBQuery jdkProxy = (IDBQuery) Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class[] { IDBQuery.class }, new JdkDbQueryHandler()); return jdkProxy; } }
以上代码生成了一个实现了IDBQuery 接口的代理类,代理类的内部逻辑由 JdkDbQueryHandler 决定,生成代理类后,由 newProxyInstance() 方法返回该代理类的一个实例。自此 , 一个完整的JDK 动态代理便完成了。CGLIB 动态代理实现
CGLIB 和 Javassist 的动态代理的使用和JDK 动态代理非常类似。下面,尝试使用CGLIB 生成动态代理。CGLIB 也需要实现一个处理代理逻辑的切入类以及织如器:public class CglibDbQueryInterceptor implements MethodInterceptor { IDBQuery real = null; @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { /* * 更通用的写法应该是 proxy.invokeSuper(obj, args); */ if (real == null) real = new DBQuery(); return real.request(); } public static IDBQuery createCglibProxy() { Enhancer enhancer = new Enhancer(); // 指定切入器,定义代理类逻辑 enhancer.setCallback(new CglibDbQueryInterceptor()); // 指定实现的接口 enhancer.setInterfaces(new Class[] { IDBQuery.class }); // 生成代理类的实例 IDBQuery cglibProxy = (IDBQuery) enhancer.create(); return cglibProxy; } public static void main(String[] args) { IDBQuery query = CglibDbQueryInterceptor.createCglibProxy(); System.out.println(query.request()); } }
在Java中,动态代理的生成主要涉及到对 ClassLoader 的使用。这里以CGLIB为例,简要阐述动态类的家在过程。
- 首先要生成 Enhancer 类实例,并指定用于处理代理业务的回调类。
- 在 Enhancer.create() 方法中,会调用 DefaultGeneratorStrategy.Generate() 方法生成动态代理类的字节码,并保存在 byte 数组中。
- 接着使用 ReflectUtils.defineClass() 方法,将字节码装载到 ClassLoader中,完成累的加载,生成动态类的实例,并返回该实例。
经过测试,JDK 的动态类创建过程最快,而在函数调用性能上,JDK 动态代理就不如 CGLIB 和 Javassist 的基于动态代码的实现。由于 方法调用的频率远远高于代理类的实际生成频率,故动态代理方法的调用性能应是高于 JDK 自带的动态代理。此外,JDK 动态代理要求代理类和真是主题都实现同一个接口, 而CGLIB 和Javassist 没有强制要求。