代理模式是一种常见的设计模式,它使用代理对象完成用户请求,屏蔽用户对真实对象的访问
在软件设计中,使用代理模式的意图很多,比如处于安全原因屏蔽客户端直接访问真实对象;在远程调用中,使用代理类处理远程方法调用的技术细节;提升系统性能,对真实对象进行封装,达到延迟加载的目的。
静态代理
在一个客户端中,有根据用户请求,去数据库查询数据的功能。在数据查询前,需要获得数据库连接。在系统有大量类似操作(比如XML解析)存在时,所有这些初始化操作的叠加,将会导致系统启动速度非常缓慢。
为此,使用代理类封装对数据库查询的初始化操作,当系统启动时,初始化这个代理类,而非真实的数据库查询类。因为代理类什么都没有做,所以它的构造是十分快速的。
延迟加载的核心思想是:如果当前并没有使用这个组件,则不需要真正地初始化它,使用一个代理对象替代它原有的位置,只有在真正需要的时候,才对它进行加载。
采用延迟加载不仅可以在时间轴上分散系统压力,而且可以避免加载一些从软件启动到关闭的整个过程中都没有使用的组件。
考虑下面这个静态代理的实现:
主题接口IDBQuery:
public interface IDBQuery {
String request();
}
DBQuery的实现,这是一个重量级对象,构造会比较慢:
public class DBQuery implements IDBQuery{
public DBQuery(){
//可能包含数据库连接等耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String request() {
return "request string";
}
}
代理类DBQueryProxy是轻量级对象,创建很快,用它来代替DBQuery的位置:
public class DBQueryProxy implements IDBQuery {
private DBQuery real=null;
@Override
public String request() {
if(real==null)
real=new DBQuery();
return real.request();
}
}
动态代理
动态代理指的是可以在运行时,动态生成代理类。即代理类的字节码在运行时生成并且载入当前的类加载器。
和静态代理相比,动态代理有诸多的好处:
- 无需为真实的主题写一个形式上完全一样的封装类。如果接口有改动,则真实的主题和代理类都需要修改,不利于系统维护。
- 一些动态代理的生成方法甚至可以在运行时指定代理类的执行逻辑,从而提高系统的灵活性。
动态代理有很多生成方法,比如JDK自带的动态代理、CGLIB、Javassist或者ASM。
他们的区别分别是:
- JDK的动态代理使用简单,因为它内置在JDK中,所以不用引入第三方Jar包,但相对的功能比较弱。
- CGLIB和Javassist是高级的字节码生成库,总体性能比JD自带的动态代理好,而且功能十分强大。
- ASM是低级的字节码生成工具,是性能最好的一种动态代理生成工具,但是使用过于繁琐,而且性能也没有数量级的提升。
考虑下面这个动态代理的实现:
以上个例子中的DBQueryProxy为例,使用动态代理生成动态类,替代上个例子中的DBQueryProxy。使用JDK的动态代理生成对象,它要求实现一个处理方法调用的Handler,用于实现代方法的内部逻辑。
public class JdkDbQeuryHandler 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();
}
}
以上代码实现了一个Handler,它的内部逻辑和DBQueryProxy是类似的。在调用真实主题的方法之前,先尝试生成真实主题对象。接着,需要使用这个Handler生成动态代理对象:
public static IDBQuery createJdkProxy(){
IDBQuery jdkProxy = (IDBQuery) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[] { IDBQuery.class }, new JdkDbQeuryHandler());
return jdkProxy;
}
以上代码生成了一个实现了IDBQuery接口的代理类,代理类的内部逻辑由JdkDbQeuryHandler决定。生成代理类后,由newProxyInstance()方法返回该代理类的一个实例。
就动态代理的方法调用性能而言,CGLIB和Javassist的基于动态代码的代理都优于JDK自带的动态代理。此外,JDK的动态代理要求代理类和真实主题都实现同一个接口,但是CGLIB和Javassist没有这个强制要求。