zoukankan      html  css  js  c++  java
  • 代理模式性能提升--延时加载

    一、代理模式的结构

      代理模式主要参与者又4个,如下表所示:

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

      以一个简单的示例来阐述使用代理模式实现延时加载的方法及意义。假设客户端软件,有根据用户请求,去数据库查询数据的功能。

    在查询数据库之前,需要获得数据库连接,软件开启时,初始化系统的所有类,此时尝试获得数据库连接,当系统有大量的类似操作存在时,

    (比如xml解析等),所有初始化这些操作的叠加,会使得系统启动速度变得非常缓慢。为此使用代理模式,使用代理类,封装对数据库查询

    中的初始化操作,当系统启动时,初始化这个代理类,而非真实的数据库查询类,而代理什么都没有做,因此,它的构造是非常快速的。

      在系统启动时,将消耗资源最多的方法都使用代理模式分离,就可以加快系统的启动速度,减少用户的等待时间。而在用户真正的查询

    操作时,再由代理类,单独去加载真实的数据库查询类,完成用户的请求,这个过程就是使用了代理模式现实了延迟加载。

      延时加载的核心思想是:如果当前并没有使用这个组件,则不需要真正的初始化它,使用一个代理对象他带它原有的位置,

    只要在真正需要使用的时候,才对它进行加载。使用代理模式的延时加载是非常有意义的,首先,它可以在时间轴上分散系统压力,

    尤其在系统启动时,不必完成所有的初始化工作,从而加快系统的启动速度;其次,对很多真实主题而言,在软件启动直到被关闭的

    整个过程中,可能根本不会被调用,初始化这些数据完全是在浪费资源。下图显示了使用代理类封装数据库查询类后,系统的启动过程。

      系统若不使用代理模式,则在启动时就回去初始化DBQuery对象,而使用代理模式之后,启动时只需要

    初始化一个轻量级的对象DBQueryProxy。

      系统的机构如下图所示,IDBQuery是主题接口,定义代理类和和真实类需要对外提供的服务,在本例中

    定义了实现数据库查询的公共方法request()函数。DBQuery是真实主题,负责实际的业务操作,DBQueryProxy

    是DBQuery的代理类。

    二、代理模式的实现和使用

      基于以上设计,IDBQuery的实现如下,它只有一个request()方法:

    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();
            }
            //在多线程环境下,这里返回一个虚假类,类似于Future模式
            return real.request();
        }
    }

      最后,主函数如下,它引用IDBQuery接口,并你用代理类工作:

    public class Main {
        public static void main(String[] args) {
            IDBQuery q = new DBQueryProxy();  //使用代理
            q.request();                      //在真正使用时才创建真是对象
        }
    }

     三、动态代理介绍

      动态代理是指在运行时,动态的生成代理类。即,代理类的字节码将在运行时生成并载入当前的ClassLoader。于静态代理类相比,

    动态代理类有诸多好处。首先,不需要位真实主题写一个形式上完全一样的封装类,假如主题接口中的方法比较多,为每一个接口写一

    个代理方法也是非常烦人的事,如果接口有变动,则真实主题和代理类都需要改动,不利于系统维护;其次,使用一些动态代理的生成

    方法甚至可以在运行时指定代理类的执行逻辑,从而大大挺升系统的灵活性。

      生成动态代理的方法很多,如,JDK自带的动态代理、CGLIB、Javassist或者ASM库。JDK的动态代理很简单,它内置在JDK中,

    因此不需要引入第三方开发包,但相对功能比较弱。CGLIB和Javassist都是高级的字节码生成库,总体性能比JDK自带的动态代理好,

    而且功能十分强大。ASM是低级的字节码生成工具,使用ASM已经近乎于在使用Java bytecode编程,对开发人员要求最高,当然,

    也是性能最好的一种动态代理生成工具。但ASM的使用实在过于频繁,而且性能也没有数量级的提升,于CGLIB等高级字节码生成

    工具相比,ASM程序的可维护性也很差,如果不是在对性能有苛刻要求的场合,还是推荐CGLIB和Javassist。

      四、动态代理实现

      以上例中的DBQueryProxy为例,使用动态代理生成动态类,替代上例中的DBQueryProxy。首先,使用JDK的动态代理生成动

    态对象。JDK的动态代理需要实现一个处理方法调用Handle,用于实现代理方法的内部逻辑。

    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();    //使用真实主题完成实际的操作
        }
    }

      以上代码实现类一个Handler,可以看到,它的内部逻辑和DBQueryProxy是类似的。在调用真实主题的方法前,先城市生成真实主题对象。接着,需要使用这个

    Handler生成动态代理对象:

      

        public static IDBQuery createJdkProxy() {
    
            IDBQuery jdkProxy = (IDBQuery) Proxy.newProxyInstance(
                    ClassLoader.getSystemClassLoader(),
                    new Class[]{IDBQuery.class},
                    new JdkDbQueryHandler());
            return jdkProxy;
        }

      以上代码生成类一个实现了IDBQuery接口的代理类,代理类的内部逻辑由jdkDbQueryHandler决定。生成代理类之后,

    由newProxyInstance()方法返回该代理类的一个实例。致此,一个完整的JDK动态代理就完成了。

      CGLIB和Javassist生成动态代理的使用和JDK的动态代理非常类似。下面,尝试使用CGLIB生成动态代理。CGLIB也

    需要实现一个处理代理逻辑的切入类:

    public class CglibDbQueryInterceptor implements MethodInterceptor {
        IDBQuery real = null;
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            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;
            
        }

      使用Javassist生成动态代理可以使用两种方式:一种是使用代理工厂创建,另一种是通过使用动态代码创建。

    使用代理工厂创建时,方法于CGLIB类似,也需要实现一个代理逻辑处理的Handler:

    public class JavassistDynDbQueryHandler implements MethodHandler {
        IDBQuery real = null;
        @Override
        public Object invoke(Object arg0, Method arg1, Method arg2, Object[] arg3) throws Throwable {
            if (real == null) {
                real = new DBQuery();
            }
            return real.request();
        }
    }

    以这个Handler为基础,创建动态Javassist代理:

        public static IDBQuery creatJavassistDynProxy() throws Exception {
            ProxyFactory proxyFactory = new ProxyFactory();
            proxyFactory.setInterfaces(new Class[] {IDBQuery.class});//指定接口
            Class proxyClass = proxyFactory.createClass();
            IDBQuery javassistProxy = (IDBQuery) proxyClass.newInstance();//设置Handler处理器
            
            ((ProxyObject) javassistProxy).setHandler(new JavassistDynDbQueryHandler());
            return javassistProxy;
        }

    Javassist使用动态Java代码创建代理的过程和前文的方法略有不同。Javassist内部可以通过动态Java代码,生成字节码。这种方式创建的

    动态代理可以非常灵活,甚至可以在运行是生成业务逻辑:

        public static IDBQuery createJavassistBytecodeDynamicProxy() throws Exception {
            ClassPool mpool = new ClassPool(true);
            //定义类名
            CtClass mCtr = mpool.makeClass(IDBQuery.class.getName()+"JavassistBytecodeProxy");
            //需要实现的接口
            mCtr.addInterface(mpool.get(IDBQuery.class.getName()));
            //添加构造函数
            mCtr.addConstructor(CtNewConstructor.defaultConstructor(mCtr));
            //添加类的字段信息,使用动态Java代码
            mCtr.addField(CtField.make("public " +IDBQuery.class.getName() + "real;", mCtr));
            String dbqueryname = DBQUery.class.getName();
            //添加方法,这里使用动态Java代码指定内部逻辑
            mCtr.addMethod(CtNewMethod.make("public String request() { if(real ==" +
                    "null) {real = new "+ dbqueryname+"();} return real.request(); }", mCtr));
            //基于以上信息,生成动态类
            Class pc = mCtr.toClass();
            //生成动态类的实例
            IDBQuery bytecodeProxy = (IDBQuery) pc.newInstance();
            return bytecodeProxy;
        }

      在以上代码中,使用CtField.make()方法和CtNewMethod.make()方法在运行时生成代理类的字段和方法。这些逻辑由Javassist的CtClass对象处理,将Java代码转换为对应的字节码,并生成动态代理类的实例。

      在Java中,动态代理类的生成主要涉及对ClassLoader的使用。这里CGLIB为例,简要阐述动态类的加载过程。使用CGLIB生成动态代理,首先需要生成Enhancer类实例,并指定用于处理代理业务的回调类。在Enhancer.create()方法中,会使用DefaultGeneratorStrategy.Generate()方法生成动态代理类的字节码,并保存在byte数组中。接着使用ReflectUtils.defineClass()方法,通过反射,调用ClassLoader.defindeClass()方法,将字节码装载到ClassLoader中,完成类的加载。最后使用ReflectUtils.newInstance()方法,通过反射,生成动态类的实例,并返回该实例。无论使用何种方法生成动态代理,虽然实现细节不同,但主要逻辑如下图所示。

       前文介绍的几种动态代理的生成方法,性能有一定差异。为了能更好地测试他们的性能,去掉

    DBQuery类中的sleep()代码,并使用以下方法测试:

     public static final int CIRCLE = 30000000;
    
        public static void main(String[] args) throws Exception {
            IDBQuer d = null;
            long begin = System.currentTimeMillis();
            d = createJdkProxy(); //测试JDK动态代理
            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));
            begin = System.currentTimeMillis();
            d = createCglibProxy(); //测试CGLIB动态代理
            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);
            begin = System.currentTimeMillis();
            d = createJavassistDynProxy(); //测试Javassist动态代理
            System.out.println("createJavassistDynProxy:" + d.getClass().getName()));
            begin = System.currentTimeMillis();
            for (int i = 0; i < CIRCLE; i++) {
                d.request();
            }
            System.out.println("callJavassistDynProxy:" + (System.currentTimeMillis() - begin));
            begin = System.currentTimeMillis();
            d = createJavassistBytecodeDynamicProxy(); //测试Javassist动态代理
            System.out.println("createJavassistBytecodeDynamicProxy:" + (System.currentTimeMillis() - begin));
            System.out.println("JavassstBytecodeDynamicProxy class:" + d.getClass().getName());
            begin = System.currentTimeMillis();
            for (int i = 0; i < CIRCLE; i++) {
                d.request();
            }
            System.out.println("callJavassistBytecodeDynamicProxy:" + System.currentTimeMillis() - begin);
        }

    以上代码分别生成了4中代理,并对生成的代理类进行高频率调用,最后输出各个代理类的创建耗时,动态类类名和方法调用耗时。结果如下:

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

      五、Hibernate中代理模式的应用

      用代理模式实现延时加载的一个经典应用在Hibernate框架中。当Hibernate加载实体bean时,并不会一次性将数据库所有的数据都装载。默认情况下,它会采去延时加载的机制,以提高系统的性能。Hibernate中的延时加载主要有两种:一是属性的延时加载,二是关联表的延时加载。这里以属性的延时加载为例,简单阐述Hibernate是如何使用动态代理的。

      假定有用户模型:

    public class User implements java.io.Serializable {
        private Integer id;
        private String name;
        private int age;
        //省略getter 和 setter 
    }

    使用以下代码,通过Hibernate加载一条User信息:

        public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
            //从数据库载入ID为1的用户
            User u = (User) HibernateSessionFactory.getSession().load(User.class, 1);
            //打印类名称
            System.out.println("Super Class Name:" + u.getClass().getSuperclass().getName());
            //实现的所有接口
            Class[] ins = u.getClass().getInterfaces();
            for (Class cls : ins) {
                System.out.println("interface:" + cls.getName());
            }
            System.out.println(u.getName());
        }

      以上代码中,在session.load()方法后,首先输出类User的类名、它的超类、User实现的接口,最后输出调用User的getName()方法取得数据库数据。这段代码的输出如下(Hibernate3.2.6):

       仔细观察这段输出,可以看到,session的载入类并不是之前定义的User 类,而是名叫javatuning.ch2.proxy.hibernate.User$$EnhancerByCGLIB$$96d498be 的类。从名称上可以推测,它是使用了CGLIB的Enhancer类生成的动态类。该类的父类才是应用程序定义的User 类。

      此外,它实现类HibernateProxy接口。由此可见,Hibernate使用一个动态代理子类替代用户定义的类。这样,在载入对象是,就不必初始化对象的所有信息,通过代理,拦截所有的getter方法,可以在真正使用对象数据时,采取数据库加载实际的数据,从而提升系统性能。由这段输出的顺序来看,也正是这样,在getName()被调用之前,Hibernate从未输出过一条SQL语句。这表示:User 对象被加载时,根本没有访问数据库,而在getName()方法被调用时,才真正完成了数据库操作。

     

  • 相关阅读:
    童鞋,[HttpClient发送文件] 的技术实践请查收
    有关[Http持久连接]的一切,卷给你看
    浅谈MemoryCache的原生插值方式
    HTTP1.1 KeepAlive到底算不算长连接?
    C2 hits the assertion assert(base>is_AddP()) failed: should be addp but is Phi
    C2 EA
    OOM Hook
    C2 Loop predicate
    C2 Build IR
    C2 CCP
  • 原文地址:https://www.cnblogs.com/klyjb/p/11522968.html
Copyright © 2011-2022 走看看