这几天在学习公司内部搜索引擎客户端的使用,发现里面使用到了懒加载技术,趁机学习了cglib的东西。
什么是懒加载呢?其现象是这样的,例如:
// 调用memberDAO.find()方法时,不加载数据
MemberDO member = memberDAO.find("maomao");
// 调用member.getName()方法时,才去调用dao加载数据。这不同于传统的使用习惯。
String name = member.getName();
这个是怎么实现的呢?一开始受到搜索引擎客户端的影响走了弯路,它没有充分使用到cglib的特性。
先简单地介绍一下cglib里的一个类Enhancer。这个类的主要作用是动态生成一个类的子类,作动态代理。
Enhancer enhancer = new Enhancer();
// 设置需要代理的类
enhancer.setSuperclass(MemberDO.class);
// 设置一个Callback的过滤器。说它是过滤器,还不如说它是callback的选择器(比如执行哪个方法前,调用哪个callback),
// 它的一个抽象方法int accept(Method method) 返回的是callback数组的下标
enhancer.setCallbackFilter(new DefaultCallbackFilter());
// 这里就是设置上面所提到的callback数组
enhancer.setCallbacks(new Callback[] { new DefaultMethodInterceptor(),NoOp.INSTANCE });
// 返回一个动态生成的子类
enhancer.create();
要实现的需求是,执行member.getName()才去加载数据。原理就是在执行member.getName()前,把数据从dao或从外部接口读过来。 先看看我走弯路的实现方式:
public class MemberDAO {
/**
* callback过滤器
*
* <pre>
* 1.当执行的方法是toString,getName,getSex的时候,调用自定义的方法拦截器MethodInterceptor
* 2.当执行的是其他方法时,不调用方法拦截器MethodInterceptor
* </pre>
*/
private class DefaultCallbackFilter implements CallbackFilter {
String methods[] = { "toString", "getName", "getSex" };
@Override
public int accept(Method method) {
if (ArrayUtils.contains(methods, method.getName())) {
return 0;
} else {
return 1;
}
}
}
/**
* 方法拦截器MethodInterceptor的实现
*
* <pre>
* 这里就是具体加载数据的实现
* </pre>
*/
private class DefaultMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
MemberDO member = (MemberDO) obj;
if (member != null && member.isLoaded()) {
System.out.println("already loaded.");
return proxy.invokeSuper(obj, args);
}
// 从数据源取数据
MemberDO tmpMember = generateMemberDO();
member.setName(tmpMember.getName());
member.setSex(tmpMember.getSex());
// 设置已经加载的标志
// (这里把是否加载的标记写在DO里,不太好。我做一下实验才这样做的,为了简单。写在ThreadLocal是一个方式,不会侵入到DO)
member.setLoaded(true);
return proxy.invokeSuper(member, args);
}
}
/**
* 查询数据
*
* <pre>
* 1.当lazy为true的时候,会为MemberDO创建一个动态代理类,并设置好CallbackFilter和MethodInterceptor
* 2.当lazy为false的时候,直接从数据源取数据
* </pre>
*
* @param name 会员的名字
* @param lazy 是否使用懒加载
* @return
*/
public MemberDO find(String name, boolean lazy) {
if (lazy) {
Enhancer enhancer = new Enhancer();
enhancer.setUseCache(true);
enhancer.setSuperclass(MemberDO.class);
enhancer.setCallbackFilter(new DefaultCallbackFilter());
enhancer.setCallbacks(new Callback[] { new DefaultMethodInterceptor(), NoOp.INSTANCE });
return (MemberDO) enhancer.create();
} else {
return generateMemberDO();
}
}
/**
* 模拟从数据源取数据
*
* @return
*/
private MemberDO generateMemberDO() {
System.out.println("query data from db.");
MemberDO member = new MemberDO();
member.setName("maomao");
member.setSex("male");
return member;
}
}
看一下执行结果:
// 控制台显示:query data from db.
MemberDO member1 = memberDAO.find("test", false);
// 控制台无显示
MemberDO member2 = memberDAO.find("test", true);
// 控制台显示:query data from db.
// 控制台显示:name:maomao
System.out.println(String.format("name:%s", member2.getName()));
// 控制台显示:already loaded.
// 控制台显示:sex:male
System.out.println(String.format("sex:%s", member2.getSex()));
从执行结果来看,使用了懒加载,在调用member2.getName()才去加载数据,在调用member2.getSex()时候直接返回第一次加载的数据。
前面提到过上述的实现方式是我走的弯路,其实有更加简洁的实现方式,cglib有专门用来实现懒加载的callback接口,名叫LazyLoader。我们只要实现这个接口,写上具体的数据加载方式。看一个例子:
public class MemberDAO {
public MemberDO find(String name, boolean lazy) {
if (lazy) {
// 设置懒加载的callback
return (MemberDO) Enhancer.create(MemberDO.class, new MemberDOLazyLoader(name));
} else {
return generateMemberDO();
}
}
/**
* 模拟从数据源取数据
*
* @return
*/
private MemberDO generateMemberDO() {
System.out.println("query data from db.");
MemberDO member = new MemberDO();
member.setName("maomao");
member.setSex("male");
return member;
}
private class MemberDOLazyLoader implements LazyLoader {
/** 查询条件 */
private String name;
public MemberDOLazyLoader(String name){
this.name = name;
}
@Override
public Object loadObject() throws Exception {
return generateMemberDO();
}
}
}
最后来看看执行结果:
MemberDAO memberDAO = new MemberDAO();
// 控制台显示:query data from db.
MemberDO member1 = memberDAO.find("maomao", false);
MemberDO member2 = memberDAO.find("maomao", true);
// 控制台显示:query data from db.
// 控制台显示:name:maomao
System.out.println(String.format("name:%s", member2.getName()));
// 控制台显示:sex:male
System.out.println(String.format("sex:%s", member2.getSex()));
一样的执行结果,而且简单,没有侵入。是不是比较简单呢?
分享到:
相关推荐
简单的图片懒加载实现
Fragment初始化,Fragment切换,Fragment显示与否,以及Fragment隐藏;Demo中Fragment只创建一次,但可以一直刷新数据
java swing tree树的懒加载, 下载文件导入eclipse可直接运行。
主要为大家分享了Android界面数据懒加载实现代码,告诉大家怎样实现界面即Fragment的懒加载,感兴趣的小伙伴们可以参考一下
懒加载,滚动加载数据!demo
主要介绍了Android 多层嵌套后的 Fragment 懒加载实现示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
Android实现WebView懒加载,提前进行页面JS资源加载。减少WebView加载时间及加载资源问题。Android 8.0以前需要引入X5内核,Android 8.0以后无特殊要求。
本篇文章主要介绍了Android优化方案之Fragment的懒加载实现代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
JQuery实现页面图片懒加载效果,两个文件放进去,只需加入两行代码即可实现。
懒加载案例 lazyload 自己做了一个懒加载的案例,供大家参考
目前很多网页的页面很丰富,内容很多,为了给用户更好的体验效果,减轻服务器的压力,可以采用懒加载预加载技术。本视频主要介绍什么是懒加载预加载
最近开发项目时遇到一个需求就是采用tree的方式展示以万为单位的数据,因为数据量大第一反应就是采用“懒加载”的方式实现,为了方便用户在庞大的数据量中快速定位到某个节点搜索功能也是必不可少的;因为采用“懒...
利用element树形控件菜单栏被触发时加载事件进行Element树形结构懒加载的动态加载。
jquery实现图片懒加载.zip
前端实现图片懒加载url前端实现图片懒加载url前端实现图片懒加载url前端实现图片懒加载url前端实现图片懒加载url前端实现图片懒加载url前端实现图片懒加载url前端实现图片懒加载url前端实现图片懒加载url前端实现...
div懒加载区别于图片懒加载,模仿天猫 京东实现滚动动态加载div
完整的项目例子用两种js实现图片懒加载
js实现PC端移动端通用的图片懒加载.rar