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、现象解释----动态代理机制(Spring的AOP也是动态代理,根本是反射,就是Spring的IOC的原理。参考:Spring IOC AOP的原理(百度二面) 如果让你自己设计IOC,AOP如何处理)
所以,开头说到的奇怪现象的原因是什么呢?其实在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等框架具有重要意义。
如果想了解Spring 的AOP 的动态代理机制,参考:Spring IOC AOP的原理(百度二面) 如果让你自己设计IOC,AOP如何处理)
Hibernate学习--hibernate延迟加载原理-动态代理(阿里电面)的更多相关文章
- Hibernate学习--hibernate延迟加载原理(动态代理)
在正式说hibernate延迟加载时,先说说一个比较奇怪的现象吧:hibernate中,在many-to-one时,如果我们设置了延迟加载,会发现我们在eclipse的调试框中查看one对应对象时,它 ...
- Spring学习笔记之aop动态代理(3)
Spring学习笔记之aop动态代理(3) 1.0 静态代理模式的缺点: 1.在该系统中有多少的dao就的写多少的proxy,麻烦 2.如果目标接口有方法的改动,则proxy也需要改动. Person ...
- Hibernate学习之延迟加载
转自:http://www.cnblogs.com/xiaoluo501395377/p/3371776.html 在hibernate中我们知道如果要从数据库中得到一个对象,通常有两种方式,一种是通 ...
- Java反射学习总结四(动态代理使用实例和内部原理解析)
通过上一篇文章介绍的静态代理Java反射学习总结三(静态代理)中,大家可以发现在静态代理中每一个代理类只能为一个接口服务,这样一来必然会产生过多的代理,而且对于每个实例,如果需要添加不同代理就要去添加 ...
- 新秀学习SSH(十四)——Spring集装箱AOP其原理——动态代理
之前写了一篇文章IOC该博客--<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大特性是IOC和AOP. IOC负责将对象动态的注入到容器,从而达到 ...
- Spring AOP实现原理-动态代理
目录 代理模式 静态代理 动态代理 代理模式 我们知道,Spring AOP的主要作用就是不通过修改源代码的方式.将非核心功能代码织入来实现对方法的增强.那么Spring AOP的底层如何实现对方法的 ...
- AOP的实现原理——动态代理
IOC负责将对象动态的 注入到容器,从而达到一种需要谁就注入谁,什么时候需要就什么时候注入的效果,可谓是招之则来,挥之则去.想想都觉得爽,如果现实生活中也有这本事那就爽 歪歪了,至于有多爽,各位自己脑 ...
- Spring事务管理机制的实现原理-动态代理
之前在做项目中遇到spring无法进行事务代理问题,最后发现是因为没有写接口,原因当时明白了,看到这篇文章写的清楚些,转过来 我们先来分析一下Spring事务管理机制的实现原理.由于Spring内置A ...
- 菜鸟学SSH(十四)——Spring容器AOP的实现原理——动态代理
之前写了一篇关于IOC的博客——<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大特性是IOC和AOP,换句话说,容器的两大特性就是IOC和AOP. ...
随机推荐
- Myeclipse破解总结
今天安装svn,Myeclipse莫名的崩了,然后就重装,然后不知为什么一直失败...经过无数次尝试,终于成功,应该是把这个破解过程遇到的所有问题都遇到了吧.有个别细节我没尝试,但以下总结用于Myec ...
- 20155232 2016-2017-2 《Java程序设计》第1周学习总结
20155232 2016-2017-2 <Java程序设计>第1周学习总结 认真学习考核方式,理解成绩构成 100分构成: 翻转课堂考核12次(60分) 实验5次(15分) 团队项目(2 ...
- 20155328 《Java程序设计》实验一(Java开发环境的熟悉) 实验报告
20155328 <Java程序设计>实验一(Java开发环境的熟悉) 实验报告 一.实验内容及步骤 (一)使用JDK编译.运行简单的java程序 命令行下的程序开发: 打开windows ...
- 【JUC源码解析】ConcurrentHashMap
简介 支持并发的哈希表.其中包括红黑树,扩容,分槽计数等知识点. 源码分析 常量 private static final int MAXIMUM_CAPACITY = 1 << 30; ...
- 微软office web apps 服务器搭建之在线文档预览(二)
上一篇文章已经介绍了整个安装过程了.只要在浏览器中输入文档转换server的ip,会自动跳转,出现如下页面. 那么就可以实现本地文档预览了,你可以试试.(注意:是本地哦,路径不要写错,类似“\\fil ...
- Electron小记
一.安装 1.安装NodeJS 2.安装electronjs:npm install -g electron --unsafe-perm=true --allow-root 安装完,环境为: Node ...
- IIC通讯程序
IIC程序 IIC起始信号 void IIC_Start(void) { SDA_OUT();//sda设为输出 IIC_SDA=; IIC_SCL=; delay_us();//延时一段时间,具体时 ...
- Git生成SSH密钥
git config --global user.name "yangjianliang"配置用户名 git config --global user.email "52 ...
- Python接口测试实战5(下) - RESTful、Web Service及Mock Server
如有任何学习问题,可以添加作者微信:lockingfree 课程目录 Python接口测试实战1(上)- 接口测试理论 Python接口测试实战1(下)- 接口测试工具的使用 Python接口测试实战 ...
- MySQL-MMM方案
参考文档: 官方文档:http://mysql-mmm.org/mmm2:guide 本文对mmm方案做简单介绍,并做1个简单的验证. 一.MySQL-MMM方案 1. MMM方案简介 MMM(Mul ...