4. Web集成
4.1. Web提供的全局变量
Web集成模块向模板提供web标准的变量,做如下说明
- request 中的所有attribute.在模板中可以直接通过attribute name 来引用,如在controller层 request.setAttribute("user",user),则在模板中可以直接用${user.name} .
- session 提供了session会话,模板通过session["name"],或者session.name 引用session里的变量.注意,session并非serlvet里的标准session对象。参考servlet来获取HTTPSession。
- request 标准的HTTPServletRequest,可以在模板里引用request属性(getter),如${request.requestURL}。
- parameter 读取用户提交的参数。如${parameter.userId} (仅仅2.2.7以上版本支持)
- ctxPath Web应用ContextPath
- servlet 是WebVariable的实例,包含了HTTPSession,HTTPServletRequest,HTTPServletResponse.三个属性,模板中可以通过request,response,session 来引用,如 ${servlet.request.requestURL};
- 所有的GroupTemplate的共享变量
- pageCtx是一个内置方法 ,仅仅在web开发中,用于设置一个变量,然后可以在页面渲染过程中,调用此api获取,如pageCtx("title","用户添加页面"),在其后任何地方,可以pageCtx("title") 获取该变量。(仅仅2.2.7以上版本支持)
你可以在模板任何地方访问这些变量
如果你需要扩展更多属性,你也可以配置beetl.properties配置文件的WEBAPP_EXT属性,实现WebRenderExt接口,在渲染模板之前增加自己的扩展,如:
RESOURCE.root=/WEB-INF/views
WEBAPP_EXT = com.park.oss.util.GlobalExt
public class GlobalExt implements WebRenderExt{
static long version = System.currentTimeMillis();
@Override
public void modify(Template template, GroupTemplate arg1, HttpServletRequest arg2, HttpServletResponse arg3) {
//js,css 的版本编号
template.binding("sysVersion",version);
}
}
这样,每次在模板里都可以访问变量sysVersion了,不需要在controller里设置,或者通过servlet filter来设置
4.2. 集成技术开发指南
Beetl默认提供了WebRender用于帮助web集成开发,所有内置的集成均基于此方法。如果你认为Beetl内置的各个web框架集成功能不够,你可以继承此类,或者参考此类源码重新写,其代码如下
package org.beetl.ext.web;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.exception.BeetlException;
/**
* 通常web渲染的类,将request变量赋值给模板,同时赋值的还有session,request,ctxPath
* 其他框架可以继承此类做更多的定制
* @author joelli
*
*/
public class WebRender{
GroupTemplate gt = null;
public WebRender(GroupTemplate gt){
this.gt = gt;
}
/**
* @param key 模板资源id
* @param request
* @param response
* @param args 其他参数,将会传给modifyTemplate方法
*/
public void render(String key, HttpServletRequest request, HttpServletResponse response, Object... args){
Writer writer = null;
OutputStream os = null;
try{
//response.setContentType(contentType);
Template template = gt.getTemplate(key);
Enumeration attrs = request.getAttributeNames();
while (attrs.hasMoreElements()){
String attrName = attrs.nextElement();
template.binding(attrName, request.getAttribute(attrName));
}
WebVariable webVariable = new WebVariable();
webVariable.setRequest(request);
webVariable.setResponse(response);
webVariable.setSession(request.getSession());
template.binding("session", new SessionWrapper(webVariable.getSession()));
template.binding("servlet", webVariable);
template.binding("request", request);
template.binding("ctxPath", request.getContextPath());
modifyTemplate(template, key, request, response, args);
String strWebAppExt = gt.getConf().getWebAppExt();
if(strWebAppExt!=null){
WebRenderExt renderExt = this.getWebRenderExt(strWebAppExt);
renderExt.modify(template, gt, request, response);
}
if (gt.getConf().isDirectByteOutput()){
os = response.getOutputStream();
template.renderTo(os);
}else{
writer = response.getWriter();
template.renderTo(writer);
}
} catch (IOException e){
handleClientError(e);
} catch (BeetlException e){
handleBeetlException(e);
} finally{
try{
if (writer != null)
writer.flush();
if (os != null)
os.flush();
} catch (IOException e){
handleClientError(e);
}
}
}
/**
* 可以添加更多的绑定
* @param template 模板
* @param key 模板的资源id
* @param request
* @param response
* @param args 调用render的时候传的参数
*/
protected void modifyTemplate(Template template, String key, HttpServletRequest request,
HttpServletResponse response, Object... args){
}
/**处理客户端抛出的IO异常
* @param ex
*/
protected void handleClientError(IOException ex){
//do nothing
}
/**处理客户端抛出的IO异常
* @param ex
*/
protected void handleBeetlException(BeetlException ex){
throw ex;
}
}
4.3. Servlet集成
只需要在Servlet代码里引用ServletGroupTemplate就能集成Beetl,他提供了一个render(String child, HttpServletRequest request, HttpServletResponse response)方法。例子如下:
protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
//模板直接访问users
request.setAttribute("users",service.getUsers());
ServletGroupTemplate.instance().render("/index.html", request, response);
}
ServletGroupTemplate同其他web集成一样,将读取配置文件来配置,如果需要通过代码配置,可以在Servlet listener里 ServletGroupTemplate.instance().getGroupTemplate()方法获取GroupTemplate
4.4. SpringMVC集成
需要做如下配置即可
同其他集成方式一样,模板的配置将放在beetl.properties中。
如果想获取GroupTemplate,可以调用如下代码
BeetlGroupUtilConfiguration config = (BeetlGroupUtilConfiguration) this.getApplicationContext().getBean("beetlConfig");
GroupTemplate group = config.getGroupTemplate();
Controller代码如下:
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView index(HttpServletRequest req) {
ModelAndView view = new ModelAndView("/index");
//total 是模板的全局变量,可以直接访问
view.addObject("total",service.getCount());
return view;
}
http://git.oschina.net/xiandafu/springbeetlsql 有完整例子
4.5. SpringMVC集成高级
spring集成还允许注册被spring容器管理的Function,Tag等,也允许配置多个视图解析器等功能
如上图所示,BeetlGroupUtilConfiguration有很多属性,列举如下
- configFileResource 属性指定了配置文件所在路径,如果不指定,则默认在classpath下
- functions 指定了被spring容器管理的function,key为注册的方法名,value-ref 指定的bean的名称
- functionPackages,指定了被spring容器管理的functionPackage,key为注册的方法包名,value-ref 指定的bean的名称
- tagFactorys ,注册tag类,key是tag类的名称,value-ref指向一个org.beetl.ext.spring.SpringBeanTagFactory实例,该子类是一个Spring管理的Bean。属性name对应的bean就是tag类。需要注意,由于Tag是有状态的,因此,必须申明Scope为 "prototype"。如代码:
@Service
@Scope("prototype")
public class TestTag extends Tag {
}
- typeFormats: 同functions,参数是 Map
, Format>,其中key为类型Class - formats:同functions,参数是 Map
,其中key为格式化函数名 - virtualClassAttributes 同functions,参数Map
, VirtualClassAttribute>,其中key为类型Class - virtualAttributeEvals ,类型为List
- resourceLoader,资源加载器 ,值是 实现ResourceLoader的一个Bean
- errorHandler ,错误处理,值是实现ErrorHandler的一个Bean
- sharedVars,同functions,类型是Map
,可以在此设置共享变量 - configProperties,类型是Properties,可以覆盖配置文件的某些属性
如下配置,指定了三个视图解析器,一个用于beetl页面渲染,一个用于cms,采用了beetl技术,另外一个是一些遗留的页面采用jsp
/template/**
/cmstemplate/**
Beetl视图解析器属性同spring自带的视图解析器一样,支持contentType,order,prefix,suffix等属性。
注意视图解析器里的属性viewNames,这个用于判断controller返回的path到底应该交给哪个视图解析器来做。
- 以/template开头的是beetlViewResolver来渲染。
- 以/cmstemplate是交给cmsBeetlViewResolver渲染。
- 如果都没有匹配上,则是jsp渲染
如果你想更改此规则,你只能增加canHandle方法指定你的逻辑了。详情参考org.springframework.web.servlet.view.UrlBasedViewResolver.canHandle
对于仅仅需要redirect和forward的那些请求,需要加上相应的前缀
- 以"redirect:"为前缀时:表示重定向,不产生BeetlView渲染模版,而直接通过Servlet的机制返回重定向响应.redirect:前缀后面的内容为重定向地址,可以采用相对地址(相对当前url),绝对地址(完整的url),如果采用/开头的地址,会自动的在前面接上当前Web应用的contextPath,即contextPath为test的Web应用中使用redirect:/admin/login.html 实际重定向地址为 /test/admin/login.html
- 以"forward:"为前缀时:表示转发,不产生BeetlView渲染模版。而是直接通过Servlet的机制转发请求(关于转发和重定向的区别,请自行查看Servlet API) forward:前缀后面的内容为转发地址,一般都是以/开头相对于当前Web应用的根目录
其他集成需要注意的事项:
- spring集成,请不要使用spring的 前缀配置,改用beetl的RESOURCE.ROOT 配置,否则include,layout会找不到模板
4.6. Spring Boot集成
Spring Boot 通过java config来配置 beetl需要的BeetlGroupUtilConfiguration,和 BeetlSpringViewResolver,参考代码如下
@Configuration
public class BeetlConf {
@Value("${beetl.templatesPath}") String templatesPath;//模板跟目录
@Bean(initMethod = "init", name = "beetlConfig")
public BeetlGroupUtilConfiguration getBeetlGroupUtilConfiguration() {
BeetlGroupUtilConfiguration beetlGroupUtilConfiguration = new BeetlGroupUtilConfiguration();
try {
ClasspathResourceLoader cploder = new ClasspathResourceLoader(BeetlConf.class.getClassLoader(),templatesPath);
beetlGroupUtilConfiguration.setResourceLoader(cploder);
return beetlGroupUtilConfiguration;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Bean(name = "beetlViewResolver")
public BeetlSpringViewResolver getBeetlSpringViewResolver(@Qualifier("beetlConfig") BeetlGroupUtilConfiguration beetlGroupUtilConfiguration) {
BeetlSpringViewResolver beetlSpringViewResolver = new BeetlSpringViewResolver();
beetlSpringViewResolver.setContentType("text/html;charset=UTF-8");
beetlSpringViewResolver.setOrder(0);
beetlSpringViewResolver.setConfig(beetlGroupUtilConfiguration);
return beetlSpringViewResolver;
}
}
spring boot集成需要注意的是要添加spring-devtools.properties文件,并配置如下选项
restart.include.beetl=/beetl-xxx.jar
restart.include.beetlsql=/beetlsql-xxx..jar
spring-devtools.properties 为spring boot的配置文件,位于META-INF目录下
4.7. Jodd集成
需要配置web.xml,将所有请求交给jodd处理,参考:http://jodd.org/doc/madvoc/setup.html
madvoc
jodd.madvoc.MadvocServletFilter
madvoc.webapp
test.MyWebApplication
madvoc.configurator
test.MyAutomagicMadvocConfigurator
madvoc
/*
MyWebApplication 和 MyAutomagicMadvocConfigurator 需要自己参照如下例子写一个,前者用来设置beetl作为视图渲染,后者配置Jodd不要扫描beetl struts集成里引用的struts类
public class MyAutomagicMadvocConfigurator extends AutomagicMadvocConfigurator {
public MyAutomagicMadvocConfigurator(){
super();
//不扫描beetl 里jar文件里的action和result,否则,会扫描StrutsResultSupport不相干的class
this.rulesJars.exclude("**/*beetl*.jar");
}
}
public class MyWebApplication extends WebApplication{
@Override
protected void init(MadvocConfig madvocConfig, ServletContext servletContext) {
//设置默认
madvocConfig.setDefaultActionResult(BeetlActionResult.class);
}
}
最后,可以写Action了,浏览器输入/index.html,jodd将执行world方法,并渲染ok.html模板。如果你想配置GroupTemplate,正如其他集成框架一样,只需要写一个beetl.properties 即可。
@MadvocAction
public class IndexAction {
@Out
String value;
@Action("/index.html")
public String world() {
value = "Hello World!";
return "/ok.html";
}
}
https://git.oschina.net/xiandafu/beetl-jodd-sample 有完整例子
4.8. JFinal3.0&JFinal2.o集成方案
Beetl提供 JFinal3.0 集成,使用JFinal3BeetlRenderFactory ,通过如下注册即可使用beetl模板引擎
public class DemoConfig extends JFinalConfig {
public void configConstant(Constants me) {
PropKit.use("a_little_config.txt"); // 加载少量必要配置,随后可用PropKit.get(...)获取值
me.setDevMode(PropKit.getBoolean("devMode", false));
JFinal3BeetlRenderFactory rf = new JFinal3BeetlRenderFactory();
rf.config();
me.setRenderFactory(rf);
GroupTemplate gt = rf.groupTemplate;
//根据gt可以添加扩展函数,格式化函数,共享变量等,
}
业务逻辑代码:
public void modify(){
int artId = getParaToInt(0, -1);
setAttr("title", "修改文章");
List cateLists = Cate.getAllCate();
//模板里访问cateLists,atr,
setAttr("cateLists", cateLists);
setAttr("art", Article.dao.findById(artId));
render("/modify.html");
}
BeetlRenderFactory 默认使用FileResourceLoader ,其根目录位于WebRoot目录下,如果你需要修改到别的目录,可以设置配置文件,如
RESOURCE.root= /WEB-INF/template/
https://git.oschina.net/xiandafu/beetl-jfinal-sample 有完整例子,采用jfinal+beetl写的一个博客系统
https://git.oschina.net/xiandafu/jfinal_beet_beetsql_btjson 同上,但DAO部分采用了BeetlSql
JFinal3 与 Jfinal2.0不兼容,且不像Appache Common Lang那样,不兼容情况下采用不同的包名,导致了Beetl不能同时兼容:如果想在beetl(2.7.10)以后版本中仍然使用Jfinal2,需要自己写集成代码,自己写一个IMainRenderFactory的实现类
//Jfinal2 集成
import java.io.IOException;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.ResourceLoader;
import org.beetl.core.resource.WebAppResourceLoader;
import com.jfinal.kit.PathKit;
import com.jfinal.render.IMainRenderFactory;
import com.jfinal.render.Render;
public class Jfinal2BeetlRenderFactory implements IMainRenderFactory
{
public static String viewExtension = ".html";
public static GroupTemplate groupTemplate = null;
public Jfinal2BeetlRenderFactory()
{
init(PathKit.getWebRootPath());
// init(null); use jfinalkit instead
}
public Jfinal2BeetlRenderFactory(ResourceLoader resourceLoader)
{
if (groupTemplate != null)
{
groupTemplate.close();
}
try
{
Configuration cfg = Configuration.defaultConfiguration();
groupTemplate = new GroupTemplate(resourceLoader, cfg);
}
catch (IOException e)
{
throw new RuntimeException("加载GroupTemplate失败", e);
}
}
public Jfinal2BeetlRenderFactory(String templateRoot)
{
init(templateRoot);
}
private void init(String root)
{
if (groupTemplate != null)
{
groupTemplate.close();
}
try
{
Configuration cfg = Configuration.defaultConfiguration();
WebAppResourceLoader resourceLoader = new WebAppResourceLoader(root);
groupTemplate = new GroupTemplate(resourceLoader, cfg);
}
catch (IOException e)
{
throw new RuntimeException("加载GroupTemplate失败", e);
}
}
public Render getRender(String view)
{
return new BeetlRender(groupTemplate, view);
}
public String getViewExtension()
{
return viewExtension;
}
}
业务逻辑代码:
import org.beetl.core.GroupTemplate;
import org.beetl.core.exception.BeetlException;
import org.beetl.ext.web.WebRender;
import com.jfinal.render.Render;
import com.jfinal.render.RenderException;
//Jfinal2 集成
public class BeetlRender extends Render
{
GroupTemplate gt = null;
private transient static final String encoding = getEncoding();
private transient static final String contentType = "text/html; charset=" + encoding;
public BeetlRender(GroupTemplate gt, String view)
{
this.gt = gt;
this.view = view;
}
@Override
public void render()
{
try
{
response.setContentType(contentType);
WebRender webRender = new WebRender(gt);
webRender.render(view, request, response);
}
catch (BeetlException e)
{
throw new RenderException(e);
}
}
}
然后在Jfinal2里配置完成
import org.beetl.ext.jfinal.BeetlRenderFactory
public class DemoConfig extends JFinalConfig{
public void configConstant(Constants me){
me.setMainRenderFactory(new Jfinal2BeetlRenderFactory());
// 获取GroupTemplate ,可以设置共享变量等操作
GroupTemplate groupTemplate = Jfinal2BeetlRenderFactory.groupTemplate ;
}
}
4.9. Nutz集成
Nutz集成提供了 BeetlViewMaker ,实现了 ViewMaker方法,如下代码
@At("/ctx")
@Ok("beetl:ctx.btl")
public Context withContext() {
Context ctx = Lang.context();
Pager pager = dao.createPager(1, 20);
pager.setRecordCount(dao.count(UserProfile.class));
List list = dao.query(UserProfile.class, null, pager);
ctx.set("pager", pager);
ctx.set("list", list);
return ctx;
}
Beetl&Nutz
总共 ${list.~size}
<%
for(user in list){
%>
hello,${user.nickname};
<% } %>
当前页${pager.pageNumber},总共${pager.pageCount}页
需要注意的是,如果使用了nutz的obj(http://www.nutzam.com/core/mvc/view.html),则需要在模板顶部申明obj是动态对象,如
<%
directive dynamic obj
%>
${obj.user.title}
${obj.user.name}
或者使用beetl的默认引擎,采取如下配置
ENGINE=org.beetl.core.engine.DefaultTemplateEngine
4.10. Struts2集成
需要在struts2配置文件里添加result-types做如下配置
text/html; charset=UTF-8
/hello.html
/table.html#table
该类会根据struts配置文件获取模板,如上例的hello.html,并将formbean的属性,以及request属性作为全局变量传递给模板
https://git.oschina.net/xiandafu/beetl-struts2-sample 有完整例子
郑重申明
鉴于struts2有安全漏洞,而官方补丁打法很消极,所以请谨慎使用Struts2,Beetl的安全性已经通过在线体验和多个使用Beetl的网站得以体现 一旦你的struts2网站被攻破,请先确定是否是struts2 的问题
4.11. MVC分离开发
对于web应用来说,必须通过controller才能渲染模板,beetl也可以写完模板后,在未完成controller情况下,直接渲染模板 此方法既可以作为通常的全栈式开发人员使用,也可以用于前端人员单独开发模板用。
Beetl使用WebSimulate来模拟模板渲染或者REST请求返回json数据,WebSimulate 会取出请求路径,然后执行values目录下同一个请求路径的脚本,脚本的顶级变量都将作为全局变量,并渲染请求路径同名的的模板文件。
比如请求路径是http://127.0.0.1:8080/user/userlist.html, 则WebSimulate会执行/values/user/userlist.html.var 脚本,获取到所有顶级变量,并渲染/user/userlist.html 页面
如果脚本定义了名为json的变量,则WebSimulate 返回的是json数据,否则,则是模板渲染
如果脚本里还定义了ajax变量,则认为是局部渲染,ajax变量因为字符串,就是代表ajaxId
WebSimulate允许使用path变量,且在values目录下,用$$代替,比如对于REST请求
/user/1,如果在values目录下有/values/users/$$.var, 则能匹配上此模拟脚本
WebSimulate对应到脚本的时候,允许根据HTTP METHOD对应,比如一个REST的GET请求 /user/1,可以对应/values/user/$$.get.var
对应的关系,总是精确匹配优先,对于/user/1,优先精确匹配/user/1.var,其次是/user/$$.get.var, 最后才是/user/$$.var
则WebSimulate 在执行脚本的时候,总是先读取/values/common.var, 以获得需要的公共变量
安装WebSimulate较为简单,以springboot为例子
@Controller
@RequestMapping("/simulate")
public class SimulateController {
@Autowired
WebSimulate webSimulate;
@RequestMapping("/**/*.html")
public void simluateView(HttpServletRequest request,HttpServletResponse response){
webSimulate.execute(request, response);
}
@RequestMapping("/api/**")
public void simluateJson(HttpServletRequest request,HttpServletResponse response){
webSimulate.execute(request, response);
}
}
如上,所有以/smulate 开头的请求,都会使用模拟数据来支持分离开发,其中simluateView来模拟视图渲染,simluateJson来模拟REST请求的数据
WebSimulate 初始化代码如下
@Bean
public WebSimulate getWebSmulate(BeetlSpringViewResolver resolver){
WebSimulate webSimulate = new WebSimulate(resolver.getConfig().getGroupTemplate()){
public String getValuePath(HttpServletRequest request){
return this.removePreffix( request.getServletPath());
}
protected String getRenderPath(HttpServletRequest request)
{
return this.removePreffix( request.getServletPath());
}
private String removePreffix(String path){
return path.replaceFirst("/simulate", "");
}
};
return webSimulate;
}
WebSimulate 通常可以直接使用,但本例子中,为了完全模拟,需要去掉/simulate",这样不必要创建一个/values/simulate
如上配置完毕,如果普通模板请求
/simulate/user/userlist.html
将会执行/values/user/userlist.html.var 的脚本,比如,模拟users数据
var users = [{"name":"xiandafu"},{"name":"lucy"}];
如果一个REST请求
/simulate/api/user/1
可以创建如下文件/values/api/user/$$.get.var,内容直接返回一个json字符串
var json = "{'success':true}";
WebSimulate 构造的时候需要一个实现JsonUtil的类(Beetl并不自带json序列化工具),这样,对于要返回的json数据,可以不必向上面的例子那样,返回json字符串,可以返回一个对象,如Map,然后交给jsonUtil来序列化返回客户端
脚本本身可以获取模拟请求的参数,如session,parameter等,从而灵活的模拟数据,具体请参考WebSimulate源码
直接访问模板前提是使用了伪模型,这与实际的项目采用的模型并不一致,因此当模板采用伪模型验证后,需要重启web应用,才能使用真正的模型去测试,否则,模板引擎会报错,这是因为beetl默认的FastRuntimeEngine会根据模型优化模板,对同一个模板不同的模型会报错,除非采用DefaultTemplateEngine 或者页面申明类型变量是动态的。
4.12. 整合ajax的局部渲染技术
越来越多web网站依赖于ajax,如table的翻页,流行方式是浏览器发出ajax请求,后台处理后返回一个json,浏览器端将json数据拆开,拼成一条一条的行数据,然后生成dom节点,追加到表格里。 作为另外一种可选技术,beetl支持局部渲染技术,允许后台处理返回的是一个完成的html片段,这样,前端浏览器可以直接将这个html片段追加到表格里。在我做的性能测试里,俩种方式性能差别不大(http://bbs.ibeetl.com/ajax//)
比如模板index.html有很多动态内容,有动态生成的菜单,有右侧的top10,也有核心区域的表格,大概内容如下
<#menu/>
<#top10> ....#top10>
<%
//ajax片段开始
#ajax userTable: {
%>
id 姓名
<% for(user in users){ %>
${user.id} ${user.name}
<% } %>
当前页面${page!1}
next pre
<%
//ajax片段结尾
}
%>
#ajax 用于告诉告诉模板引擎,此处是个局部渲染标记,标记为"userTable",对于正常渲染视图"index.html"页面,#ajax标记没什么用处,table仍能得到正常渲染。如果渲染的视图是index.html#userTable,则模板只会渲染#ajax标记得模板片段,其他部分将忽略。关于完整例子,可以参考https://git.oschina.net/xiandafu/beetlajax
后台代码如下:
render("/index.html#userTable");
只需要在模板路径后加上#就表示渲染的并非是整个模板,而是模板的一部分,这一部分由#后面的标记来标示
ajax 片段渲染也支持默认情况下不渲染,仅仅做为一个片段使用,如一个页面有许多后台交互操作,并返回相应的html片段,可以将这些html片段也放到同一个模板里,使用ajax norender,表示渲染整个模板的时候默认并不需要渲染此ajax片段
<%
#ajax norender success: {
%>
操作成功
<%
}
%>
#ajax norender failure: {
%>
操作失败
<%
}
%>
这样,此页面默认情况下并没有输出success,和 failure片段
注意,Ajax片段本质上是从模版的ajax标记处开始渲染,因此,ajax需要的变量在模版里也必须是全局变量,如果你只是个局部变量,beetl会报出找不到变量,即使你binding了这个变量,beetl也认为这个是局部变量,如
<%
var tableData = paras.table;
#ajax userTable: {
for(user in tableData);
%>
<%
//ajax片段结尾
}
%>
变量tableData是从paras里获取的,是个临时变量,因此就算你在后台binding了一个tableData,beetl 也不能识别。在渲染ajax片段的时候会报变量tableData找不到。改正的办法只能是让tableData全局变量。
返回Json好还是返回html片段好?这个难以定论.
- 从后台性能看,将模型序列化成json性能会比渲染模板性能更好,但是,json还需要前端重新解析生成最终html dom节点,这可能会延迟最终数据的现实效果。而返回的html片段就是已经生成好的dom
- 从网络传入来看,json无疑更好的,html片段会有额外的html标记,css属性,以及有可能的js调用。传入流量有可能增加50%到100%。但是,对于web应用类,这些额外数据,并不算多。
- 从开发效率来讲,返回html片段的开发效率更高一些,因为渲染在后台操作,可以随心所欲的用模板语言来渲染,来取得后台数据,完成复杂渲染,而json就比较困难,可以说所有的json lib都没有完美的解决办法。
- 从用户体验上来讲,Beetl 采用ajax标记,混合了传统的模板渲染和ajax加载。用户进入页面即能看到数据,而经典的ajax json方式还需要异步加载,显示延迟。另外如果页面同时有多个ajax加载,则会对服务器造成很大的压力。
- 关心服务器cpu消耗? 模板方式消耗更多的cpu,json方式则少点。但是俩者差距并不大。而且更多的web网站面临的情况是有富余的服务器CPU能力
- 关心客户端CPU消耗? 过多的js无疑是客户端运行慢的主要原因。如果采用经典的json方式,返回的json数据必然还需要经过js的计算和渲染。会影响客户机器cpu。
符号#ajax 实际上用来标记一个模板渲染片段,它还有个别名的叫#fragment,俩着是一样的,比如
<%
#fragment part2:{
println("part2");
}
%>
4.13. 在页面输出错误提示信息
2.2.3版本以后,新增加org.beetl.ext.web.WebErrorHandler,可以在web开发的时候在页面输出提示信息,在产品模式下在后台输出提示信息(通过配置属性ESOURCE.autoCheck= true来认为是开发模式),仅仅需要配置如下:
ERROR_HANDLER = org.beetl.ext.web.WebErrorHandler
5. 附录
5.1. 内置方法
5.1.1. 常用内置方法
- date 返回一个java.util.Date类型的变量,如 date() 返回一个当前时间(对应java的java.util.Date); ${date( "2011-1-1" , "yyyy-MM-dd" )} 返回指定日期
- print 打印一个对象 print(user.name);
- println 打印一个对象以及回车换行符号,回车换号符号使用的是模板本身的,而不是本地系统的.如果仅仅打印一个换行符,则直接调用println() 即可
- nvl 函数nvl,如果对象为null,则返回第二个参数,否则,返回自己 nvl(user,"不存在")
- isEmpty 判断变量或者表达式是否为空,变量不存在,变量为null,变量是空字符串,变量是空集合,变量是空数组,此函数都将返回true
- isNotEmpty 同上,判断对象是否不为空
- has 变量名为参数,判断是否存在此全局变量,如 has(userList),类似于1.x版本的exist("userList"),但不需要输入引号了
- assert 如果表达式为false,则抛出异常
- trunc 截取数字,保留指定的小数位,如trunc(12.456,2) 输出是12.45
- decode 一个简化的if else 结构,如 decode(a,1,"a=1",2,"a=2","不知道了")},如果a是1,这decode输出"a=1",如果a是2,则输出"a==2", 如果是其他值,则输出"不知道了"
- debug 在控制台输出debug指定的对象以及所在模板文件以及模板中的行数,如debug(1),则输出1 [在3行@/org/beetl/core/lab/hello.txt],也可以输出多个,如debug("hi",a),则输出hi,a=123,[在3行@/org/beetl/core/lab/hello.txt]
- parseInt 将数字或者字符解析为整形 如 parseInt("123");
- parseLong 将数字或者字符解析为长整形,parseInt(123.12);
- parseDouble 将数字或者字符解析为浮点类型 如parseDouble("1.23")
- range 接收三个参数,初始值,结束值,还有步增(可以不需要,则默认为1),返回一个Iterator,常用于循环中,如for(var i in range(1,5)) {print(i)},将依次打印1234.
- flush 强制io输出。
- json,将对象转成json字符串,如 var data = json(userList) 可以跟一个序列化规则 如,var data = json(userList,"[*].id:i"),具体参考 https://git.oschina.net/xiandafu/beetl-json
- pageCtx ,仅仅在web开发中,设置一个变量,然后可以在页面渲染过程中,调用此api获取,如pageCtx("title","用户添加页面"),在其后任何地方,可以pageCtx("title") 获取该变量
- type.new 创建一个对象实例,如 var user = type.new("com.xx.User"); 如果配置了IMPORT_PACKAGE,则可以省略包名,type.new("User")
- type.name 返回一个实例的名字,var userClassName = type.name(user),返回"User"
- global 返回一个全局变量值,参数是一个字符串,如 var user = global("user_"+i);
- cookie 返回指定的cookie对象 ,如var userCook = cookie("user"),allCookies = cookie();
5.1.2. 字符串相关方法
strutil方法对参数均不做空指针检测,你可自定义方法来覆盖这些内置的方法
- strutil.startWith ${ strutil.startWith(“hello”,”he”) 输出是true
- strutil.endWith ${ strutil.endWith(“hello”,”o”) 输出是true
- strutil.length ${ strutil. length (“hello”),输出是5
- strutil.subString ${ strutil.subString (“hello”,1),输出是“ello”
- strutil.subStringTo ${ strutil.subStringTo (“hello”,1,2),输出是“e”
- strutil.split ${ strutil.split (“hello,joeli”,”,”),输出是数组,有俩个元素,第一个是hello,第二个是joelli”
- strutil.contain ${ strutil.contain (“hello,”el”),输出是true
- strutil.toUpperCase ${ strutil.toUpperCase (“hello”),输出是HELLO
- strutil.toLowerCase ${ strutil.toLowerCase (“Hello”),输出是hello
- strutil.replace ${ strutil.replace (“Hello”,”lo”,”loooo”),输出是helloooo
- strutil.format ${ strutil.format (“hello,{0}, my age is {1}”,”joeli”,15),输出是hello,joelli, my age is 15. 具体请参考http://docs.oracle.com/javase/6/docs/api/java/text/MessageFormat.html
- strutil.trim 去掉字符串的尾部空格
- strutil.formatDate var a = strutil.formatDate(user.bir,’yyyy-MM-dd’);
- strutil.index var index = strutil.index("abc","a");返回 索引0
- strutil.lastIndex var index = strutil.lastIndex("aba","a");返回索引2
5.1.3. 数组相关方法
- array.range 返回数组或者Collection一部分,接受三个参数,第一个是数组或者Collection子类,第二,三个参数分别是起始位置
- array.remove 删除某个数组或者Collection的一个元素,并返回该数组或者Collection.第一个是数组或者Collection子类,第二个参数是元素
- array.add 向数组或者Collection添加一个元素,并返回该数组或者Collection。第一个是数组或者Collection子类,第二个参数是元素
- array.contain 判断数组或者元素是否包含元素,如果包含,返回true。否则false。第一个是数组或者Collection子类,第二个参数是元素
- array.toArray 转化成数组,如array.toArray(1,2,"a");
- array.collection2Array 将java集合转化为数组 array.collection2Array([1,2,''])
5.1.4. 正则表达式相关方法
- reg.match(str,regex) str为需要处理的字符串,regex是表达式
- reg.replace(str,regex,replace),str为需要处理的字符串,regex是表达式,替换的字符串替换字符串
- reg.find(str,regex) 返回找到的符合表达式的第一个字符串,否则返回空字符串
- reg.findList(str,regex) 找到所有符合表达式的字符串,否则返回空列表
- reg.split(str,regex),对字符串进行切分,返回列表
- reg.split(str,regex,limit) 同上,limit是最多返回个数
5.1.5. Spring 相关函数
Spring函数并没有内置,需要注册,如下
spel(spelString, rootObject) SpEL方法传入一个Spring SpEL表达式以获取表达式结果,方法建议以函数的方式定义在BeetlGroupUtilConfiguration的functions中
spelString: SpEL表达式字符串,必传(否则返回null) rootObject: 作为spel的根对象(对应#root),可以是一个Map或Bean对象,默认取空Map。由于Beetl运行上下文无法直接获取模版局部变量的变量名,建议局部变量采用自定义Map的方式传入
-
列表筛选(以自定义Map为根对象传入局部变量)
<% var intArray = [12, 1, 2, 3]; %>
${spel('#root.intArray.?[#this>10]', {intArray: intArray})}
-
以Bean对象为根对象
<% var now = date(); %>
${spel('#root.year + 1900', now)}
-
直接new对象
${spel('(new java.util.Date()).year + 1900')}
-
直接引用Spring Bean
${spel('@testBean')}
默认变量
#root 表示SpEL的根对象, 由spel函数第二参数传入,默认是一个空map
#context 表示Beetl执行上下文
#global 表示Beetl的共享变量Map,由于Beetl上下文无法获取临时变量名,临时变量建议使用根对象的方式传入
#ctxPath 表示Servlet Context Path(由Beetl WebRender提供)
#servlet 可以从中获取到Servlet request,response,session原生实例(由Beetl WebRender提供)
#parameter 表示请求参数Map(由Beetl WebRender提供)
#request 表示请求对象(由Beetl WebRender提供)
#session 表示会话域属性Map(由Beetl WebRender提供)
sputil 提供了spring内置的一些功能,如
// 测试source中是否包含了candidates的某个成员(相当于交集非空)
sputil.containsAny(Collection> source, Collection> candidates)
// 返回在source集合总第一个也属于candidates集的元素
sputil.findFirstMatch(Collection> source, Collection> candidates)
// 测试指定文本是否匹配指定的Ant表达式(\*表达式), 多个表达式只要一个匹配即可
sputil.antMatch(String input, String... patterns)
// 返回指定路径表示的文件的扩展名(不带点.)
sputil.fileExtension(String path)
// 忽略大小写的endsWith
sputil.endsWithIgnoreCase(String input, String suffix)
// 忽略大小写的startsWith
sputil.startsWithIgnoreCase(String input, String prefix)
// 测试输入值是否为空白, null视为空白, 无视字符串中的空白字符
sputil.isBlank(String input)
// 首字母大写转换
sputil.capitalize(String input)
// 首字母小写转换
sputil.uncapitalize(String input)
// 在集合或数组元素之间拼接指定分隔符返回字符串
// null表示空集, 其他类型表示单元素集合
sputil.join(Object collection, String delim)
// 同上, 只是会在最后结果前后加上前缀和后缀
// 注意这个函数名叫做joinEx
sputil.joinEx(Object collection, String delim, String prefix, String suffix)
// 对文本进行html转义
sputil.html(String input)
// 对文本进行javascript转义
sputil.javaScript(String input)
5.1.6. Spring security
下列三个函数只需以函数的方式定义在BeetlGroupUtilConfiguration的functions中即可,与spel函数一样的,函数名声明在functions中,可以更改
auth() 对应类: org.beetl.ext.spring.AuthenticationFunction 方法无参数 返回值: 返回当前安全上下文中的用户认证凭证Authentication实例 如果当前环境不存在Spring Security安全上下文,将返回null值
-
urlIf(, ) 对应类: org.beetl.ext.spring.AccessUrlIfFunction 参数: url: 字符串表示的测试URL Path,不需要指定Context Path,缺省会直接返回true method: 字符串表示的访问方式, 默认为GET, 建议全大写 返回值: 测试当前登录用户是否能访问指定的URL Path, 返回true or false
示例:
urlIf('/system/admin_update.do', 'POST'))
如果当前环境不存在Spring Security安全上下文,将返回true 如果当前环境不存在用户认证凭证,作为匿名登录进行测试
-
expIf() 对应类: org.beetl.ext.spring.AccessExpressionIfFunction 参数: exp: Spring Security安全表达式,缺省会直接返回true 返回值: 测试当前登录用户是否满足指定的安全表达式,返回true or false 示例:
expIf('isAuthenticated()')
如果当前环境不存在Spring Security安全上下文,将返回true 如果当前环境不存在用户认证凭证,作为匿名登录进行测试
注意: 使用此方法,必须开启Spring Security的expression功能(use-expressions="true"):
Spring Security Expression相关语法,请阅读: http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#el-access
5.1.7. shiro
参考文档 https://my.oschina.net/xiandafu/blog/143109
5.2. 内置格式化方法
- dateFormat 日期格式化函数,如 $yyyy-Mm-dd,等于符号后的参数也可以没有,则使用本地默认来做格式化如 ${date,dateFormat}
- numberFormat ${0.345,numberFormat="#.%"} 输出是 34.5%,具体请参考文档 http://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html
5.3. 内置标签函数
-
include include一个模板,如 :
<% include("/header.html"){} %>
如果想往子模板中传入参数,则可以后面跟一个json变量
<% include("/header.html",{'user':user,'id':user.id}){} %>
这样user,和id 可以在header.html被引用,并成为header.html的全局变量
(beetl1.2 也叫includeFileTemplate ,2.0仍然支持,但不再文档里体现了)
-
layout 提供一个布局功能,每个页面总是由一定布局,如页面头,菜单,页面脚,以及正文。 layout标签允许为正文指定一个布局,如下使用方式
content.html内容如下:
<%
//content.html内容如下:
layout("/inc/layout.html"){ %>
this is 正文
..........
<% } %>
```
layout.html 是布局文件,内容如下
```javascript
<% include("/inc/header.html"){} %>
this is content:${layoutContent}
this is footer:
```
运行content.html模板文件后,,正文文件的内容将被替换到layoutContent的地方,变成如下内容
```javascript
this is header
this is content:this is 正文
............
this is footer:
如果想往layout页面传入参数,则传入一个json变量,如下往layout.html页面传入一个用户登录时间
<% layout("/inc/header.html",{'date':user.loginDate,'title':"内容页面"}){ %>
this is 正文
..........
<% } %>
```
如果layoutContent 命名有冲突,可以在layout第三个参数指定,如
```javascript
<% layout("/inc/header.html",{'date':user.loginDate,'title':"内容页面"},"myLayoutContent"){ %>
this is 正文
..........
<% } %>
```
-
cache 能Cache标签的内容,并指定多长时间刷新,如
<% :cache('key2',10,false){ %>
内容体
<% } %>
需要指定三个参数
第一个是cache的Key值
第二个是缓存存在的时间,秒为单位
-
第三个表示是否强制刷新,false表示不,true表示强制刷新
Cache默认实现org.beetl.ext.tag.cache.SimpleCacheManager. 你可以设置你自己的Cache实现,通过调用CacheTag. cacheManager= new YourCacheImplementation();
可以在程序里调用如下方法手工删除Cache:
public void clearAll();
public void clearAll(String key);
public void clearAll(String... keys);
-
includeJSP,可以在模板里包括一个jsp文件,如:
<%
includeJSP("/xxxx.jsp",{"key":"value"}){}
%>
key value 都是字符串,将以parameter的形式提供给jsp,因此jsp可以通过request.getParameter("key")来获取参数
主要注意的是,这个标签并非内置,需要手工注册一下
groupTemplate.registerTag("incdlueJSP",org.beetl.ext.jsp.IncludeJSPTag.class);
5.4. 性能优化的秘密
Beetl2.0目前只完成了解释引擎,使用解释引擎好处是可以适用于各种场景,性能测试表明,Beetl2.0引擎是Freemaker的4-6倍,跟最好的编译引擎性能相比,也相差只有30%百分点。为什么Beetl能跑的如此之快呢,简单的说,有如下策略
- 优化IO输出,允许使用字节直接输出,模板中的静态文本事先转化为字节
- encode优化,对于number类型,输出通常是.toString 转化成String,然后encode输出,这中间浪费了大量的资源,Beetl实现了encode,输出一步到位
- Context 采用一维数组,语言里的Context通常采用Map实现,每次进入{} ,就新增一个child Map,尽管map很快,但不够快。也有其他模板语言采用二位数组提高性能,Beetl是通过固定大小一维数组来维护模板的Context,因此访问更快,也避免了Map和二维素组的频繁创建。其实除了此处,beetl很多地方都不采用Map来维护key-value, 而都采用数组索引,以追求性能极限
- 字节码访问属性,通过反射获取性能比较慢,就算JVM有优化,但优化效果也不确定。Beetl通过字节码生成了属性访问类,从而将属性访问速度提高了一个数量级
- 类型推测:Beetl 是强制类型的,因此预先知道类型,可以对模板做一些优化而省去了动态判断类型的时间
- 使用数组Buffer,避免频繁创建和销毁数组
- 编译引擎将模板编译成类,会产生大量的类,虚拟机很难对这些做优化。而解释引擎只有几十个固定的类,虚拟机容易优化
相关文章
- 为什么JSP会比Beetl慢 http://my.oschina.net/xiandafu/blog/475740
- Beetl 性能揭秘 2 :语言如何存取变量 http://my.oschina.net/xiandafu/blog/293167
- Beetl 性能揭秘 1 :如何输出一个整型变量 http://my.oschina.net/xiandafu/blog/284823
5.5. Eclipse 插件
启动Eclipse
-
打开菜单栏按一下菜单路径依次打开
Help -> Install New Softwave… ->点击Add按钮弹出一个对话框
-
弹出的对话框中Name随意填写,如填写“beetl”,Location请填写
http://ibeetl.com/eclipse/
选中您要安装的Beetl Eclipse Plugin,按提示依次Next,直至Finish重启Eclipse即可.
使用说明:
- 工程属性里有个beetl属性,可以指定定界符号等,默认是<%%> ${}。也可以指定模板根目录(可选,不必手工填写,在模板单击定位里会提示你选择)
- ctrl-2 定位到下一个beetl 块
- ctrl-3 定位到上一个beetl块
- ctrl-4 将普通文件以beetl editor方式打开,并保持同步编辑
- ctrl-5 静态文本全部折叠和打开静态文本折叠
- 可以ctrl+单击字符串定位到字符串对应的模板文件,第一次使用的时候,需要选择模板根目录,随后,也可以在project属性的beetl配置里配置模板根目录
- alt-/ 进行上下文提示。也可以键入此快速输入定界符号和占位符号
- alt-shift-p 从{ 快速移动到 匹配的},或者反之亦然。如果只单击{ 则会框选住匹配的} 而光标不移动
- 选中任何id,都能全文框选住同样的id。
- ctrl-/ 单行注释,或者取消注释
- 通常eclipse具有的快捷操作方式,beetl仍然予以保留不变
- 具备一定的错误提示,目前只提示第一个发现的错误。
- 双击{ } 可以选中之间的内容
5.6. 性能测试对比
测试用例一 https://github.com/javamonkey/ebm
测试用例二 http://git.oschina.net/kiang/teb
测试用例三 https://github.com/javamonkey/template-benchmark
Benchmark
version
Threads
Samples
Score
Score Error (99.9%)
Unit
Beetl
2.7
1
50
42125.112914
3512.147131
ops/s
Freemarker
2.3
1
50
13099.139808
339.612022
ops/s
Handlebars
4.0
1
50
15808.044125
235.109622
ops/s
Mustache
0.9
1
50
17961.391809
158.524109
ops/s
Rocker
0.1
1
50
33631.370722
417.915637
ops/s
Thymeleaf
3.0
1
50
4625.981276
67.313609
ops/s
注意
Score得分越高表示模板引擎每秒处理量越大性能越好
这个性能测试基本上结合了国内外的模板引擎,随着JDK版本的升级,JDK8提高了反射能力,减少了和Freemarker等模板引擎的性能差距,但Beetl依旧以3倍以上的性能优势秒杀Freemarker。
5.7. Beetl 开发团队
作者
- 闲.大赋
助手
- 作死模式:核心代码开发
- 一粟蜉蝣:核心代码开发和版本发布
代码捐助者
- 逝水fox :出色完成spring集成
- kraken: 集合方法等扩展
- 西安玛雅牛:复合加载器
- 级?!: beetl扩展,crossMVC
- orangetys: beetl插件
- Oo不懂oO: beetl插件
- 原上一颗草:Beetl早期使用者。
- 龙图腾飞 ,WebErrorHandler,用来开发模式在 web上显示错误而不是控制台
- nutz: nutz 集成和MapResourceLoader
- 天方地圆 :提供正则方法
文档校验
- 九月
- Daemons
- Darren