周末偷闲学设计模式,以此文记之。
代理模式是常见设计模式之一。它使用代理对象完成用户请求,屏蔽用户对真实对象的访问。
使用意图:如因安全原因,需屏蔽客户端直接访问真实对象;或者远程调用中,使用代理类处理远程方法调用的细节;也可能是为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。
角色 | 作用 |
主题接口 | 定义代理类和真实主题的公共对外方法,也是代理类真实主题的方法 |
真实主题 | 真正实现业务逻辑的类 |
代理类 | 用来代理和封装真实主题 |
Main | 客户端,使用代理类和主题接口完成一些工作 |
使用数据库实例查询例子来解释代理模式,系统启动时只初始化代理类,而其他什么都没做。当用户真正发起查询请求时,再用代理类去加载真实的数据库查询类,完成用户请求,这个过程就是使用代理模式实现延迟加载,从而提升系统启动速度。时序图如下:
图1 代理类的工作流程
图2 代理模式的一种实现
public interface IDBQuery {
String request();
}
public class DBQuery implements IDBQuery {
public DBQuery(){
try {
//耗时操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String request() {
return "request String";
}
}
public class DBQueryProxy implements IDBQuery {
private DBQuery real = null;
@Override
public String request() {
//在真正需要的时候,才创建真实对象,创建过程可能很慢
if(real == null){
real = new DBQuery();
}
//在多线程环境下,这里返回一个虚假类,类似于Future模式
return real.request();
}
}
public static void main(String args[]){
IDBQuery query = new DBQueryProxy();
String str = query.request();
System.out.println(str);
}
运行结果:
动态代理指在运行时,动态生成代理类。即,代理类的字节码将在运行时生成并载入当前的ClassLoader。与静态代理相比,动态代理不需要为每个接口写一个形式上完全一样的封装类,有利于系统的维护;其次,甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。
生成动态代理类的方法有如:JDK自带的动态代理、CGLIB、Javassist或者ASM库。JDK动态代理使用简单,它内置在JDK中,因此不需要引入第三方jar包,但相对功能较弱。CGLIB和Javassist都是高级字节码生成库,总体性能比JDK自带的动态代理好,而且功能十分强大。ASM是低级的字节码生成工具,使用ASM近乎在使用Java bytecode编程,对于开发人员要求最高,当然也是性能最好的一种动态代理生成工具。但ASM的使用实在过于繁琐,而且性能也没有数量级的提升,与CGLIB等高级字节码生成工具相比,ASM程序的可维护性也比较差,如果不是在对性能有苛刻要求的场合,笔者还是推荐CGLIB或者Javassist。
以上面的DBQueryProxy为例
4.1、使用JDK自带动态代理实现:
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(){
IDBQuery jdkProxy = (IDBQuery) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[] {IDBQuery.class},new JdkDbQueryHandler());
return jdkProxy;
}
4.2、使用CGLIB实现动态代理:
public class CglibDbQueryInterceptor implements MethodInterceptor {
private IDBQuery real = null;
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) {
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;
}
4.3、使用Javassist实现代理模式,由代理工厂实现
public class JavassistDynDbQueryHandler implements MethodHandler {
IDBQuery real = null;
@Override
public Object invoke(Object o, Method method, Method method1, Object[] objects) throws Throwable {
if(real == null){
real = new DBQuery();
}
return real.request();
}
}
public static IDBQuery createJavassitDynProxy() throws IllegalAccessException, InstantiationException {
ProxyFactory proxyFactory = new ProxyFactory();
//指定接口
proxyFactory.setInterfaces(new Class[] {IDBQuery.class});
Class proxyClass = proxyFactory.createClass();
IDBQuery javassitProxy = (IDBQuery) proxyClass.newInstance();
//设置Handler处理器
((ProxyObject)javassitProxy).setHandler(new JavassistDynDbQueryHandler());
return javassitProxy;
}
4.4、使用Javassist实现代理模式,使用动态代码创建
public static IDBQuery createJavassitBytecodeDynamicProxy() throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException {
ClassPool mPool = new ClassPool(true);
//定义类名
CtClass mCtc = mPool.makeClass(IDBQuery.class.getName()+"javaassitBytecodeProxy");
//需要实现的接口
mCtc.addInterface(mPool.get(IDBQuery.class.getName()));
//添加构造函数
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
//添加类的字段信息,使用动态java代码
mCtc.addField(CtField.make("public " + IDBQuery.class.getName() + " real;",mCtc));
String dbQueryName = DBQuery.class.getName();
//添加方法,这里使用动态java代码指定内部逻辑
mCtc.addMethod(CtNewMethod.make("public String request (){if(real == null){ real = new "+dbQueryName+"(); } return real.request();}",mCtc));
//基于以上信息,生成动态类
Class pc = mCtc.toClass();
//生成动态类的实例
IDBQuery byteCodeProxy = (IDBQuery) pc.newInstance();
return byteCodeProxy;
}
动态代理实现套路:在Java中,动态代理生成主要涉及对ClassLoader的使用。以CGLIB为例,简要阐述类加载过程。使用CGLIB首先要生成Enhancer类实例,并指定用户处理业务的回调类。在Cnhancer.create()方法中,会使用DefaultGeneratorStrategy.Generate()方法生成动态代理的字节码,并保存在byte数组中。接着使用ReflectUtils.defineClass()方法,通过反射,调用ClassLoader.defineClass()方法,将字节码装在到ClassLoader中,完成类的加载。最后使用ReflectUtils.newInstance()方法,通过反射,生成动态类的实例,并返回该实例。无论使用何种方法生成动态代理,虽然实现细节不同,但主要逻辑如下图:
图3 实现动态代理的基本步骤
几种动态代理实现的性能测试:
public static void testPerformance() throws InstantiationException, IllegalAccessException, NotFoundException, CannotCompileException {
IDBQuery d = null;
//测试JDK动态代理
long begin = System.currentTimeMillis();
d = createJdkProxy();
System.out.println("createJdkProxy:" + (System.currentTimeMillis() - begin));
System.out.println("JdkProxy class:"+d.getClass().getName());
begin = System.currentTimeMillis();
for(int i = 0;i
运行截图如下:
可以看到,1、JDK的动态类创建过程最快,这是因为在这个内置实现中defineClass()方法被定义为native实现,故性能高于其他几种实现。2、在代理类的函数调用性能上,JDK动态代理不如CGLIB和Javassist的基于动态代码的代理,而Javassist的基于代理工厂的代理实现,性能最差,甚至不如JDK。在实际中,代理类的方法的调用频率通常高于代理类的实际生成频率(相同类的重复生成会使用cache),故而,动态代理对象的方法调用的性能作为性能参考主要关注点。
HIbernate中使用代理实现延迟加载。主要有属性延迟加载和关联表延迟加载。有兴趣的读者可以深入研究Hibernate的内部实现。
需了解原文,请移步至《Java程序性能优化》--葛一鸣 著