Hibernate学习--hibernate延迟加载原理(动态代理)

 在正式说hibernate延迟加载时,先说说一个比较奇怪的现象吧:hibernate中,在many-to-one时,如果我们设置了延迟加载,会发现我们在eclipse的调试框中查看one对应对象时,它的内部成员变量全是null的(因为这个原因我还调了好久的代码!),贴张图给你们感受下:

左边是设置延迟加载的调试图,右边是没设置延迟加载的示意图;

                 

ok,估计大家也理解我说什么了,下面就从这个现象进作为入口,阐述hibernate延迟加载背后的原理----动态代理。

一、hibernate的延迟加载与动态代理

  1、hibernate中的延迟加载:get VS load

    我们知道,在hibernate方法中,直接涉及到延迟加载的方法有get和load,使用get时,不会延迟加载,load则反之。另外,在many-to-one等关系配置中,我们也可以通过lazy属性设置是否延迟加载,这是我们对hibernate最直观的认识。

  2、现象解释----动态代理机制

    所以,开头说到的奇怪现象的原因是什么呢?其实在hibernate设置延迟加载后,hibernate返回给我们的对象(要延迟加载的对象)是一个代理对象,并不是真实的对象,该对象没有真实对象的数据,只有真正需要用到对象数据(调用getter等方法时)时,才会触发hibernate去数据库查对应数据,而且查回来的数据不会存储在代理对象中,所以这些数据是无法在调试窗口查看到的。

    如果在调试是要查看该数据,我们可以查看代理对象中的hadler属性中的target变量,该对象变量才是真实的对象,看下面截图:

 

 

 也就是说,我们user变量仅仅是一个代理类,target才是真正数据库中获取的数据。当我们在调用getter方法式,hibernate会利用动态代理的方法,直接调用target中的getter方法发挥对应的值。这样也解释了为什么hibernate可以延迟加载:通过代理类进行加载时间的控制,在外界正真调用getter等方法操作数据时才会对相应的方法进行拦截,然后读取数据库。

 

二、动态代理原理

  上面也简单介绍了hibernate延迟加载是通过动态代理实现,所以上面是动态代理呢?

  1、理解代理的概念。

    代理是一个中间者,它的主要作用之一是我们可以利用代理对象来增强对真正对象的控制:例如在hibernate中控制数据加载的时间在正真调用数据时发生。具体的话,后面个人会写一篇代理模式的博客简单总结下代理模式,读者也可以去查查代理模式以加深理解,这里不详细讲解。

    在jdk中的代理,主要通过一个叫做InvocationHandler的委托接口和Proxy的代理类来实现动态代理,一般来说,Proxy会通过调用InvocationHandler的invoke方法进行代理委托:也就是invoke方法才是真正的代理方法,这个后面的代码例子会详细讲解。所以,java中要动态代理的话,必须有一个InvocationHandler的具体实现类。

  2、java动态代理的详细实现方式

       上面也提到了,java中动态代理中至少涉及三个对象:代理调用对象(参数代理实例),被代理对象,被委托的Handler对象即代理对象。下面就从三个对象,进行一个简单的动态代理实现

  首先,我们写一个真实的类,该类要被代理对象代理。这里需要注意的是:java中的动态代理是只能支持接口的动态代理的,所以我们在实现具体类前必须抽象该类的方法,定义一个接口,至于为什么在java中只能支持接口动态代理,后面会详细讲解,下面贴上我的代码,大家注意看注释:

/*
 * 首先定义一个接口被真实的类实现,jdk中的动态代理只能代理接口类对象
 */
interface RealClassIfc{
    public void method1(String myName);
}

/*
 * 这个是真实的对象
 */
class RealClass implements RealClassIfc{
    public void method1(String myName){
        System.out.println(this.getClass().getName() + " method1Name:" + myName);
    }
}

  然后,我们定义一个代理类也就是InvocationHandler接口的具体实现类Handler,该类的对象对应代理对象,具体的代码说明在注释中,请注意看:

/*
 * 代理类,用于给jdk代理类Proxy进行委托,该类需要实现一个接口InvocationHandler,该接口只有一个方法invoke
 */
class ProxyClass implements InvocationHandler {
    /*
     * 参数说明:
     * proxy:代理对象,该对象用于查询代理对象的其他信息,更具体作用可以参考这篇博客:
     *        http://blog.csdn.net/bu2_int/article/details/60150319;
     * method:真实对象所对应的方法
     * args:执行上面method所需要的参数
     * 有需要该方法可以选择返回值
     */
    //真实对象,invoke方法中需要用到
    Object realClass = null;
    public ProxyClass(Object realClass){
        this.realClass = realClass;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //在invoke方法体内部执行织入的代码
        System.out.println("这个是RealClass method1执行前要执行的代码");
        method.invoke(realClass,args);
        System.out.println("这个是RealClass method1执行后要执行的代码");
        return null;
    }
    
}

 这里简单说明下:InvacationHandler中只有一个方法(具体可以查看jdk源码)invoke,而我们便是在该方法内部进行我们的代码织入操作(这个可以联系spring中的AOP思想,其实Spring AOP 的实现大体就是这样,更详细的AOP可以参考本人这篇博客)。所以,到这里我们可以大概猜到:hibernate的查询操作大概就是在invoke方法中调用正式的getter等获取数据方法前进行的,动态代理帮我们拦截了getter等获取数据方法并对应地在这之前进行了数据查询等操作,具体可以参考查看hibernate源码。

  然后,我们需要一个客户端类来帮我们获取代理实例,完成代理过程,请看下面代码:

/*
 * 这个是客户端类,在hibernate中,如果延迟加载被设置了,我们获取的对象只是代理对象,就是对应这个类的
 * 该类会通过jdk 的Proxy类的getInstance方法获取一个代理类,该代理类会自动帮你实现真实类的所有接口对应方法
 */
class ClientClass {
    //该属性是真实类
    Object realClassInterface = null;
    //在构造代理类时初始化该类
    public ClientClass(Object realClassInterface){
        this.realClassInterface = realClassInterface;
    }
    //获取代理实例方法,该方法用于获取代理实例
    public void proxyMethod(){
        InvocationHandler handler = new Handler(realClassInterface);
        RealClassIfc realClass = (RealClassIfc)Proxy.newProxyInstance(Handler.class.getClassLoader(), RealClass.class.getInterfaces(), handler);
        realClass.method1("动态代理的方法");
     

      //打印发现realClass的真实类是一个jre运行时生成的一个代理类
      System.out.println(realClass.getClass().getName());


    }
}

  一般来说,客户端类不会直接调用InvocationHandler对象的invoke方法,而是通过jdk的Proxy类获取一个代理对象实例(对应上面的realClass对象),然后通过该对象来直接调用真实类的同名方法,这样才能给调用者一个“我调用的是真实的类”,因为对调用者而言,代理类应当是透明的。

  这里简单说说Proxy的newProxyInstance方法的几个参数:

  第一个参数是代理类的加载器,代理对象通过该加载器来以反射的方式获得委托的InvocationHadler具体实现类的对象,然后通过接口调用对应invoke方法来实现真实类的方法调用,需要类加载器是因为Proxy生成动态代理对象过程会生成一个描述代理类的字节码,该字节码加载时正需要一个classLoader;

  第二个参数是真实的类的所有接口信息,该信息给代理类有的作用:代理类会对应“实现”真实类的所有接口(其实应该是调用invoke方法+真实类对应的方法),这样也正是代理类的真正运行机理:这种方法可以让我们有机会在正真方法执行前拦截到该方法,然后织入代码,这也是动态代理实现AOP的大概原理,hibernate的延迟加载正是这样实现的。从这里我们可以回答上面提出的问题:java中为什么只能通过接口实现动态代理了,这是因为Proxy的newInstance方法限制的,更本质的原因其实就是java不支持多继承,所以代理对象不得不通过操作接口操作真实对象。

  第三个参数就是真正的代理类对象了,该对象才是正真的负责代理操作。

  注意的是,打印realClass的类名字可以得这样结果:review.blog.hibernate.$Proxy0,并非是RealClass。从这里可以看出,jdk动态代理机制的确生成一个动态代理类字节码,代理实例就是通过该字节码对应的类来创建的。更具体可以参考这个链接

 

    动态代理是AOP实现的一种重要方式,通过InvocationHandler接口进行方法的拦截并利用反射机制执行一定的代码正是AOP中织入代码的重要手段,理解动态代理的原理对于我们理解更好的理解hibernate和spring等框架具有重要意义。  

   

                                            

 

posted @ 2017-04-24 23:57  CoderLcp  阅读(11502)  评论(3编辑  收藏  举报