一般,页面上会分为很多部分,而不同的部分更新的频率是不一样的。如果对整个页面采用统一的缓存策略则不太合适,
而且很多系统的
页面左上角都有一个该死的“Welcome
XXX”。这种特定于用户的信息我们是不能缓存的。对于这些情况我们就需要使用片段缓存了。对页面不同的部分(片段)施加不同的缓存策略,而要使用片段缓
存,首先就得对页面进行切分。土一点的办法可以用iframe,用iframe将页面划分为一块块的,不过我总觉得iframe是个邪恶的东西。好点的办
法可以用Ajax单独的请求这个片段的内容然后再填充,看起来挺美好的。不过使用Ajax也有一些限制:
1、如果页面上有许多片段,使用太多的这种技术,会有很多请求发送到服务器,HTTP对同一个域名有连接的限制,这样会降低并发连接的效率。
2、
如果说第一个不是什么问题,那么还有一点可能对用户体验不友好。比如有一个片段可能响应慢点,造成页面闪烁。不过如果前面两点都可以克服,这个方案还是可
以的。可恶的是我们的客户(此处省略500字),说他们的大多数用户处于一个禁用JavaScript的环境里。好吧,这个方案也不能使用了。如是我们进
行了一系列其他关于片段缓存的尝试:
我们的系统使用的是Spring+Hibernate+Oracle技术,模板引擎使用的是Apache Velocity。假设下面的片段是我们要缓存的内容:
[code="html"]
#foreach($book in $books)
$book.name---
Edit -- Delete
#end
显示一个图书列表。对这个页面改动最小的办法是加上一个标签,被这个标签包围的片段就是缓存的:
#cache
#foreach($book in $books)
$book.name---
Edit --
Delete
String cacheKey = (String) keyNode.value(context);
String cacheHtml = cacheHtml = (String)CacheManager.getInstance().get(cacheKey);
if (StringUtils.isEmpty(cacheHtml)) {
Node bodyNode = node.jjtGetChild(1);
Writer tempWriter = new StringWriter();
bodyNode.render(context, tempWriter);
cacheHtml = tempWriter.toString();
CacheManager.getInstance().set(cacheKey, cacheHtml);
}
writer.write(cacheHtml);
return true;
}
}
#end
#end
由于一个页面可能有很多片段,不同的片段肯定要用不同的cache key,所以这个标签应该还能传入一个cache key。当呈现这个页面,到解析这个标签的时候我们就用这个cache key去缓存中取,如果取到了我们就直接将缓存的东西输出,
而不再需要解析这个图书列表了。
有了这个想法,我们就需要找到如何让Velocity解析我们的标签的方案。很好,Velocity是支持自定义标签的:
[code="java"]String cacheKey = (String) keyNode.value(context);
String cacheHtml = cacheHtml = (String) CacheManager.getInstance().get(cacheKey);
if (StringUtils.isEmpty(cacheHtml)) {
Node bodyNode = node.jjtGetChild(1);
Writer tempWriter = new StringWriter();
bodyNode.render(context, tempWriter);
cacheHtml = tempWriter.toString();
CacheManager.getInstance().set(cacheKey, cacheHtml);
}
writer.write(cacheHtml);
return true;
}
}
关于Velocity的自定义标签的使用我会在后面稍作解释。
最主要的逻辑在render方法里,我们先根据cache key去缓存里取,如果没取到再使用代码render,然后render的结果放到缓存中。很典型的缓存使用场景是不。再来看看控制器端得代码:
@Controller
@RequestMapping("/books")
public class BookController {
private BookDAO bookDAO;
@Autowired
public BookController(BookDAO bookDAO) {
this.bookDAO = bookDAO;
}
@RequestMapping(value = {"", "index.html"}, method = RequestMethod.GET)
public ModelAndView index() {
return new ModelAndView("list", "books", bookDAO.findAll());
}
}
控制器很简单,调用DAO,将所有图书列出来即可。正在我们高兴这么棘手的问题被解决的时候,问题来了:
我们的缓存是为了什么?总不是为了节约Velocity解析的时间吧。我想大家应该都知道,最主要的还是为了节约这次
bookDAO.findAll()查询数据库的时间。但是回过头看看我们的方案。不管我们的cache命没命中,这个
bookDAO.findAll()都会执行一次,因为控制器的执行是在视图render之前发生的。我们唯一节省的是Velocity解析的时间,杯
具。
找到了问题的答案,寻找解决办法就容易了。我们要做的就是在缓存没有命中的时候才执行查询,那么这个数据查询就必须放到cache标签内部做。但是我们的cache标签可不是为了一个片段啊,有很多片段,而各种片段取数据的方式却不同。
嗯,你还记得接口么?还记得计算机里所有的问题都可以通过中间层解决的这个名言么?按照这个思路我们如此设计cache标签:
#cache("book_list",$dataProvider)
<ul>
#foreach($book in $books)
<li>
<a href="/book/books/$book.id">$book.name</a>---
<a href="/book/books/edit/$book.id">Edit</a> --
<a href="/book/books/delete/$book.id">Delete</a>
</li>
#end
</ul>
#end
我们传入一个dataProvider对象进来,而这个dataProvider是控制器里传入进来的,一个专门用来取数据的:
public class BookController {
private DataProvider dataProvider;
@Autowired
public BookController(BookDataProvider dataProvider) {
this.dataProvider = dataProvider;
}
@RequestMapping(value = {"", "index.html"}, method = RequestMethod.GET)
public ModelAndView index() {
return new ModelAndView("list", "dataProvider", dataProvider);
}
}
控制器还是一如既往的简单,我们再来看看cache标签的实现:
public class Cache extends Directive {
@Override
public String getName() {
return "cache";
}
@Override
public int getType() {
return BLOCK;
}
@Override
public boolean render(InternalContextAdapter context, Writer writer, Node node)
throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
Node keyNode = node.jjtGetChild(0);
String cacheKey = (String) keyNode.value(context);
String cacheHtml = cacheHtml = (String) CacheManager.getInstance().get(cacheKey);
if (StringUtils.isEmpty(cacheHtml)) {
Node dataProviderNode = node.jjtGetChild(1);
DataProvider dataProvider = (DataProvider) dataProviderNode.value(context);
Map<String, Object> map = dataProvider.load();
for (String key : map.keySet()) {
context.put(key, map.get(key));
}
Node bodyNode = node.jjtGetChild(3);
Writer tempWriter = new StringWriter();
bodyNode.render(context, tempWriter);
cacheHtml = tempWriter.toString();
CacheManager.getInstance().set(cacheKey, cacheHtml);
}
writer.write(cacheHtml);
return true;
}
}
我们在标签内部取到外部传入的dataProvider,它实现了一个接口DataProvider,然后在标签内部进行数据的查询。
然后将查询的数据put到velocity的context中,然后再次render,将render的结果放到缓存。这下好了,控制器里只需要向
视图传递一个可以取数据的对象就可以了,cache标签内部会进行判断。DataProvider和BookDataProvider的代码:
public interface DataProvider {
Map<String, Object> load();
}
@Service
public class BooksDataProvider implements DataProvider {
private BookDAO bookDAO;
@Autowired
public BooksDataProvider(BookDAO bookDAO) {
this.bookDAO = bookDAO;
}
public Map<String, Object> load() {
Map<String, Object> result = new HashMap<String, Object>();
result.put("books", bookDAO.findAll());
return result;
}
}
现在我们要改造的就是对于每个不同的缓存片段写一个DataProvider的实现,而实际上这个实现原来已经有了:
就是原来控制器内那部分代码。比如BooksDataProvider实际上就是原来BookController内的代码。通过这种方式,我们基本上就将一个页面划分为很多用cache包围的小片段了,只要划分出来了那么你就可以对不同
的片段采用不同的缓存策略。代码的结构还算清晰。
下面我稍微介绍一下Velocity自定义标签的使用:
Velocity自定义标签
每个自定义标签都从Directive派生下来,我们要覆盖几个方法。
1、getName,返回一个字符串,这个就是你在velocity模板里使用的那个标签的名字:#cache。
2、标签的类型,像我们的cache这种#cache…#end的叫块级标签,那么返回的就是一个BLOCK(常量1)。还有一个类型是LINE(2),那就没有那个#end了。
3、render方法,这是最主要的,你可以覆盖一些行为。
取外部传入的参数
那么在标签内部如何取得外部传入的参数呢?其实它的行为和xml
path的操作方式差不多。在render方法的参数里有一个Node,这个就是标签自身。我们可以通过node.jjtGetChild(index)
来取得各种参数。以0开始,如果是BLOCK类型的标签,那么最后一个就是标签包围的内容了(比如我们的cache标签)。然后我们可以通过node的
value取到参数的值。取值的时候还将context传入进去了,这说明这个值是可计算的。比如现在有这么一个需求,我们的图书列表是分页的,但是只缓
存第一页,后面的不缓存。那我们就期望能传入
一个表达式,让cache标签自己计算一把,如果这个表达式为true则缓存,否则不缓存:
#cache("book_list",$pageIndex==1,$dataProvider)
...
#end
那么在cache标签内部呢:
Node needCacheNode = node.jjtGetChild(1);
Boolean needCache = (Boolean) needCacheNode.value(context);
String cacheHtml = StringUtils.EMPTY;
if (needCache) {
cacheHtml = (String) CacheManager.getInstance().get(cacheKey);
}
这样就可以计算出$pageIndex==1这个表达式的结果(当然,$pageIndex这个变量是需要传入进来的)。
好了,编写好自定义标签的代码我们可以使用了。而并不是我们写好这代码往那儿一丢就可以使用了,还需要一个配置环节:
<bean class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/templates/"/>
<property name="velocityProperties">
<props>
<prop key="userdirective">com.yuyijq.web.Cache</prop>
</props>
</property>
</bean>
1、有的时候我们会在片段里set一些变量,然后在这个片段外使用。但是现在使用了cache之后,cache之后的是HTML
文本,这些变量全部消失了,那么片段外也取不到这些变量的值了。那我们就需要仔细搜查Velocity模板,将这些变量的使用全部移动到片段内部。
如果是一开始就设计了这个片段缓存还好,我们可以注意这个问题。但问题是现在是项目的中途提出的,系统中velocity模板成千上万,我们每缓存一个片
段就要仔细检查一番,而且Velocity模板没有测试(当然可以写测试,但很麻烦)。这个过程全部靠人肉,所以出bug的几率会很高。
2、还是一样,改动比较大,不仅模板,后面的控制器也需要修改。不过貌似也没什么更好的方法,要使用片段缓存貌似改动是避免不了的。
附:原文链接
:http://www.cnblogs.com/yuyijq/archive/2011/05/07/fragment_cache_one.html
分享到:
相关推荐
分布式缓存方案Velocity是微软为了解决大规模分布式系统中的数据缓存问题而推出的一个高效、可扩展的技术。在现代Web应用程序中,缓存是提升性能的关键工具,它减少了数据库的负载,提高了数据读取速度。Velocity...
- 缓存管理:Velocity支持缓存编译后的模板,提高重复渲染的性能。 - 自定义指令:可以通过扩展Velocity,编写自定义指令以满足特定需求。 7. **最佳实践** - 尽量避免在模板中进行复杂的逻辑处理,保持模板简洁...
3. **性能优化**:利用缓存机制减少模板加载的时间开销,提高渲染速度。 ### 实践案例分析 #### 八、Velocity 在实际项目中的应用 - **示例一:新闻系统**:在新闻系统中,使用 Velocity 渲染文章列表和详情页。 ...
1. **配置 Velocity**: 学习如何设置Velocity的配置文件,如velocity.properties,以调整引擎的行为,比如模板的编码、缓存策略等。 2. **创建并运行模板**: 了解如何创建模板文件,以及如何使用VelocityEngine实例...
Velocity 是一个基于 Java 的模板引擎,它允许开发者将呈现逻辑从业务逻辑中分离出来,使得开发者可以专注于编写应用程序的核心功能,而将页面展示的工作交给 Velocity 模板来处理。Velocity 提供了一种简单且强大的...
配置文件通常为 `velocity.properties`,可以自定义模板加载器、缓存策略等。 **5. 模板语法** Velocity 模板语法包括引用($variable)、逻辑控制(#if, #else, #elseif, #foreach)以及方法调用等。例如: ```...
- **模板缓存**:Velocity 可以缓存编译后的模板,提高性能。 - **自定义指令和工具库**:可以通过实现`MacroLibrary`接口添加自定义宏,或者通过`VelocityEngine.setProperty()`注册自定义工具类。 - **错误...
Velocity 是一款开源的 Java 模板引擎,它允许开发者将页面设计与业务逻辑分离,使得 Web 开发更加高效且结构清晰。Velocity 的核心思想是"MVC"(Model-View-Controller)模式,其中 View 层的实现就是通过 Velocity...
2. **性能优化**:合理使用缓存机制提高性能。 3. **错误处理**:适当处理可能出现的异常情况。 4. **代码复用**:通过宏定义等方式提高代码复用率。 通过以上介绍,我们了解了 Velocity 的基本语法和高级特性,...
1. **自定义宏**:创建可重用的模板片段。 2. **模板继承**:通过继承实现模板重用。 3. **国际化支持**:利用Velocity实现多语言支持。 4. **错误处理**:合理处理模板解析时可能出现的异常。 #### 七、最佳实践 ...
6. **性能优化**:包括缓存策略、页面压缩、减少HTTP请求等。 通过以上内容的学习,你可以从零开始,逐步建立起对JSP的深入理解,并能独立完成小规模的Web项目。同时,随着经验的积累,你还可以进一步探索更高级的...
4. **模板和布局**:为了保持一致的界面设计,可以使用自定义标签库(如JSTL的fmt和fn标签)或第三方框架(如FreeMarker、Velocity)来实现页面模板和布局。 5. **错误和异常处理**:通过设置错误页面和异常处理器...
系统可能使用自定义标签库(Tag Libraries)或标准标签库(JSTL),如EL(Expression Language)和JSTL Core,以简化页面代码,提高可读性和维护性。 7. **安全与权限控制** 对于财务系统,安全性和权限管理至关...
8. **缓存(Caching)**:为了提高性能,NVelocity可以缓存编译后的模板,避免每次都重新解析。 在实践中,NVelocity常用于ASP.NET应用中生成动态页面内容,或者在邮件系统中创建个性化的邮件模板。通过熟练掌握这些...
7. **模板引擎**:为了提高开发效率和一致性,许多CMS系统会使用模板引擎,如FreeMarker或Velocity,这些引擎允许开发者创建可重用的布局和部分页面。 8. **插件和模块化**:好的CMS系统应支持插件和模块化,使功能...
自定义`generatorSqlmapCustom`还可以帮助优化性能,例如,通过生成更高效的SQL语句,减少数据库查询次数,或者通过合理的缓存策略来提升应用的响应速度。 8. **错误处理和调试** 调试自定义的代码生成规则时,要...