Java设计模式之代理模式

        周末偷闲学设计模式,以此文记之。

        代理模式是常见设计模式之一。它使用代理对象完成用户请求,屏蔽用户对真实对象的访问。

        使用意图:如因安全原因,需屏蔽客户端直接访问真实对象;或者远程调用中,使用代理类处理远程方法调用的细节;也可能是为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。

1、模式结构

角色作用
主题接口定义代理类和真实主题的公共对外方法,也是代理类真实主题的方法
真实主题真正实现业务逻辑的类
代理类用来代理和封装真实主题
Main客户端,使用代理类和主题接口完成一些工作

       使用数据库实例查询例子来解释代理模式,系统启动时只初始化代理类,而其他什么都没做。当用户真正发起查询请求时,再用代理类去加载真实的数据库查询类,完成用户请求,这个过程就是使用代理模式实现延迟加载,从而提升系统启动速度。时序图如下:

2、代理模式的实现

                                                              图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);
}

运行结果:

3、动态代理

        动态代理指在运行时,动态生成代理类。即,代理类的字节码将在运行时生成并载入当前的ClassLoader。与静态代理相比,动态代理不需要为每个接口写一个形式上完全一样的封装类,有利于系统的维护;其次,甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。

        生成动态代理类的方法有如:JDK自带的动态代理、CGLIB、Javassist或者ASM库。JDK动态代理使用简单,它内置在JDK中,因此不需要引入第三方jar包,但相对功能较弱。CGLIB和Javassist都是高级字节码生成库,总体性能比JDK自带的动态代理好,而且功能十分强大。ASM是低级的字节码生成工具,使用ASM近乎在使用Java bytecode编程,对于开发人员要求最高,当然也是性能最好的一种动态代理生成工具。但ASM的使用实在过于繁琐,而且性能也没有数量级的提升,与CGLIB等高级字节码生成工具相比,ASM程序的可维护性也比较差,如果不是在对性能有苛刻要求的场合,笔者还是推荐CGLIB或者Javassist。

4、动态代理实现

        以上面的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<CIRCLE;i++){
            d.request();
        }
        System.out.println("callJdkProxy:" + (System.currentTimeMillis() - begin));

        //测试cglib动态代理

        begin = System.currentTimeMillis();
        d = createCglibProxy();
        System.out.println("createCglibProxy:" + (System.currentTimeMillis() - begin));
        System.out.println("CglibProxy class:"+d.getClass().getName());
        begin = System.currentTimeMillis();
        for(int i = 0;i<CIRCLE;i++){
            d.request();
        }
        System.out.println("callCglibProxy:" + (System.currentTimeMillis() - begin));

        //测试javassit代理,工厂实现
        begin = System.currentTimeMillis();
        d = createJavassitDynProxy();
        System.out.println("createJavassitDynProxy:" + (System.currentTimeMillis() - begin));
        System.out.println("JavassitDynProxy class:"+d.getClass().getName());
        begin = System.currentTimeMillis();
        for(int i = 0;i<CIRCLE;i++){
            d.request();
        }
        System.out.println("callJavassitDynProxy:" + (System.currentTimeMillis() - begin));

        //测试javassit代理,动态代码实现
        begin = System.currentTimeMillis();
        d = createJavassitBytecodeDynamicProxy();
        System.out.println("createJavassitBytecodeDynamicProxy:" + (System.currentTimeMillis() - begin));
        System.out.println("JavassitBytecodeDynamicProxy class:"+d.getClass().getName());
        begin = System.currentTimeMillis();
        for(int i = 0;i<CIRCLE;i++){
            d.request();
        }
        System.out.println("callJavassitBytecodeDynamicProxy:" + (System.currentTimeMillis() - begin));
    }

运行截图如下:

        可以看到,1、JDK的动态类创建过程最快,这是因为在这个内置实现中defineClass()方法被定义为native实现,故性能高于其他几种实现。2、在代理类的函数调用性能上,JDK动态代理不如CGLIB和Javassist的基于动态代码的代理,而Javassist的基于代理工厂的代理实现,性能最差,甚至不如JDK。在实际中,代理类的方法的调用频率通常高于代理类的实际生成频率(相同类的重复生成会使用cache),故而,动态代理对象的方法调用的性能作为性能参考主要关注点。

5、应用

        HIbernate中使用代理实现延迟加载。主要有属性延迟加载和关联表延迟加载。有兴趣的读者可以深入研究Hibernate的内部实现。

 

需了解原文,请移步至《Java程序性能优化》--葛一鸣 著

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值