在复杂的对象创建时,往往需要创建很多它所依赖的对象,通常这些所依赖的对象创建比较耗时,并且只有少数依赖的对象会被立即使用,其他依赖对象暂时不会使用到,这时候就用该考虑使用延时加载了。
.NET 中使用Lazy<T>这样的形式来实现延时加载。
1..NET中的Lazy加载
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LazyLoadDemo
{
internal class LargeObject
{
public LargeObject()
{
}
public void Test()
{
Console.WriteLine("Test");
}
}
class Program
{
static void Main(string[] args)
{
var lazyObject = new Lazy<LargeObject>();
lock (lazyObject)
{
Console.WriteLine(lazyObject.IsValueCreated);
lazyObject.Value.Test();
Console.WriteLine(lazyObject.IsValueCreated);
lazyObject.Value.Test();
Console.WriteLine(lazyObject.IsValueCreated);
}
Console.ReadKey();
}
}
}
输出结果如下:
False
Test
True
Test
True
这时候我们发现,延迟加载有一个是否已加载的标记,如果已经加载了,则第二次直接返回已加载的对象实例。
2.自己实现的延迟加载
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LazyDemo2
{
internal class MyLazyObject<T> where T : new()
{
private T _value;
private bool _isLoaded;
public MyLazyObject()
{
_isLoaded = false;
}
public bool IsLoaded
{
get { return _isLoaded; }
}
public T Value
{
get
{
if (!_isLoaded)
{
_value = new T();
_isLoaded = true;
}
return _value;
}
}
}
internal class LargeObject
{
public LargeObject()
{
}
public void Test()
{
Console.WriteLine("Test");
}
}
class Program
{
static void Main(string[] args)
{
var lazyObject = new MyLazyObject<LargeObject>();
lock (lazyObject)
{
Console.WriteLine(lazyObject.IsLoaded);
lazyObject.Value.Test();
Console.WriteLine(lazyObject.IsLoaded);
lazyObject.Value.Test();
Console.WriteLine(lazyObject.IsLoaded);
}
Console.ReadKey();
}
}
}
这里我们自己来实现对象的延迟加载,同样我们设定一个是否已经加载的标记。当对象已经被加载之后,我们就不再创建新的对象,而是直接返回实例!这里已经考虑了线程安全的问题,一个线程在使用当前的对象,另一个线程需要等待,保证了对象的唯一性。
3.Java中的延迟加载
1)Spring的延迟加载
Spring 作用就是装载bean,默认都是及时加载的,如果遇到大对象需要延迟加载怎么解决呢?其实Spring使用配置就可以解决延迟加载的问题了。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="employeeBean" class="javabeat.net.spring.ioc.Employee" />
<bean id="addressBean" class="javabeat.net.spring.ioc.Address"
lazy-init="true" />
</beans>
该配置中的lazy-init设置为true,Spring 就可以对其进行延迟加载!
注意:Spring配置延迟加载有两种方式,一种是配置在bean的节点上,另外一种是配置在beans节点上,前者只会影响到当前的bean,后者则会影响到所有的bean!
2)Hibernate的延迟加载
下面是hibernate延迟加载配置的实例:
Team.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="edu.dgut.ke.model.Team" table="TEAM" lazy ="true" ><!-- 多对一的延迟加载设置 -->
<id name="id" type="java.lang.String">
<column name="ID" length="32" />
<generator class="uuid.hex" />
</id>
<property name="teamname" type="java.lang.String">
<column name="TEAMNAME" length="32" not-null="true" unique="true" />
</property>
<set name="students" inverse="true" cascade="all" lazy="true" > <!-- 一对多的延迟加载设置 -->
<key>
<column name="TEAMID" length="32" not-null="true" />
</key>
<one-to-many class="edu.dgut.ke.model.Student" />
</set>
</class>
</hibernate-mapping>
Certificate.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="edu.dgut.ke.model.Certificate" table="CERTIFICATE" lazy="true" ><!-- 一对一的延迟加载设置 -->
<id name="id" type="java.lang.String">
<column name="ID" length="32" />
<generator class="uuid">
</generator>
</id>
<property name="describe" type="java.lang.String">
<column name="`DESCRIBE`" length="50" not-null="true" />
</property>
<one-to-one name="student"
class="edu.dgut.ke.model.Student"
constrained="true" ><!-- 一对一的延迟加载设置 -->
</one-to-one>
</class>
</hibernate-mapping>
Student.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="edu.dgut.ke.model.Student" table="STUDENT" lazy="true" >
<id name="id" type="java.lang.String">
<column name="ID" length="32" />
<generator class="uuid.hex" />
</id>
<many-to-one name="certificate"
class="edu.dgut.ke.model.Certificate"
unique="true"
column="cardId"
cascade="all"
>
</many-to-one>
<many-to-one name="team" class="edu.dgut.ke.model.Team">
<column name="TEAMID" length="32" not-null="true" />
</many-to-one>
<property name="studentname" type="java.lang.String">
<column name="STUDENTNAME" length="16" not-null="true" unique="true" />
</property>
</class>
</hibernate-mapping>
注意:对one-to-one 关系进行延迟加载和其他关系相比稍微有些不同。many-to-one 的延迟加载是在配置文件的class 标签
上设置 lazy="true" ,one-to-many 和 many-to-many 的延迟加载是在 set 标签中设置lazy="true"。而one-to-one 不只要在 classs
标签设置 lazy="true",而且要在one-to-one 标签中设置constrained="true" 。
如果不设置constrained="true",则一对一查询使用默认的预先抓取策略(fetch="join")。
4.总结
延迟加载的机制是大对象的管理的一部分,关于大对象管理还有大对象的回收机制等等,之前也发表过一篇关于大对象造成的内存碎片的问题的博客。在构建复杂系统的时候如何确保对象正确构建很重要,同时如何确保对象按需加载也非常重要,尤其是对于性能优化方面。