4.1. Web提供的全局变量
Web集成模块向模板提供web标准的变量,做如下说明
request 中的所有attribute.在模板中可以直接通过attribute name 来引用,如在controller层 request.setAttribute("user",user),则在模板中可以直接用${user.name} .
session 提供了session会话,模板通过session["name"],或者session.name 引用session里的变量
request 标准的HTTPSerlvetRequest,可以在模板里引用request属性(getter),如${request.requestURL}。
parameter 用户读取用户提交的参数。如${parameter.userId} (仅仅2.2.7以上版本支持)
ctxPath Web应用ContextPath
servlet 是WebVariable的实例,包含了HTTPSession,HTTPSerlvetRequest,HTTPSerlvetResponse.三个属性,模板中可以通过request.response,session 来引用,如 ${serlvet.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. Serlvet集成
只需要在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集成一样,将读取配置文件来配置,如果需要通过代码配置,可以在Serlvet 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;
}
https://git.oschina.net/xiandafu/springbeetlsql有完整例子
4.5. SpringMVC集成高级
spring集成还允许注册被spring容器管理的Function,Tag等,也还允许配置多个视图解析器等功能
如上图所示,BeetlGroupUtilConfiguration有很多属性,列举如下
总共 ${list.~size}
hello,${user.nickname};
当前页${pager.pageNumber},总共${pager.pageCount}页
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处理,参考:https://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. JFinal集成
Beetl提供 JFinal 集成,使用BeetlRenderFactory ,通过如下注册即可使用beetl模板引擎
import org.beetl.ext.jfinal.BeetlRenderFactory
public class DemoConfig extends JFinalConfig
{
public void configConstant(Constants me)
{
me.setMainRenderFactory(new BeetlRenderFactory());
// 获取GroupTemplate ,可以设置共享变量等操作
GroupTemplate groupTemplate = BeetlRenderFactory.groupTemplate ;
}
业务逻辑代码:
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目录下,如果你需要修改到别的目录,可以设置配置文件,如
1
RESOURCE.root= /WEB-INF/template/
https://git.oschina.net/xiandafu/beetl-jfinal-sample有完整例子,采用jfinal+beetl写的一个博客系统
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;
}
需要注意的是,如果使用了nutz的obj(https://www.nutzam.com/core/mvc/view.html),则需要在模板顶部申明obj是动态对象,如
${obj.user.title}
${obj.user.name}
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. 直接Web中运行Beetl模板
对于web应用来说,必须通过controller才能渲染模板,beetl也可以写完模板后,在未完成controller情况下,直接渲染模板 此方法既可以作为通常的全栈式开发人员使用,也可以用于前端人员单独开发模板用。
步骤如下:
配置监听器,监听器指定对*.btl的请求进行监听(假定模板名字都是以btl.结尾)。
实现监听器,该监听器继承父类 org.beetl.ext.web.SimpleCrossFilter,实现protected abstract GroupTemplate getGroupTemplate()方法。依据不同的集成方式,比如你的环境是Servlet,则只需要调用ServletGroupTemplate.instance().getGroupTemplate(),如果是Jfinal,需要调用BeetlRenderFactory.groupTemplate等
SimpleCrossFilter 提供一些有用的方法,可以帮助你定制一些特性,可以参考源码了解
置完成后,对于要测试的模板,可以新建一个对应的伪模型文件,比如要测试模板WebRoot/user/userList.html,可以新建立WebRoot/values/user/userList.html.var 。 values是监听器默认的伪模型的根目录
编辑伪模型文件,对应于userList.html需要的全局变量,userList.html.var可以申明这些些变量
var proudct = {id:1,name:'测试产品',pic:'xxxx.jpg'};
var userList = [{id:2,name:'用户一'}];
var session= {admin:{id:1,name:'admin'}};
通过浏览器直接访问https://ip:port/user/userList.html,监听器会预先执行userList.html.var,并将返回值作为模板的全局变量,传给userList.html
可以将一些公共的变量放到WebRoot/values/common.var里(比如上面代码的session). 监听器会先执行common.var,然后再执行userList.html.var
直接访问模板前提是使用了伪模型,这与实际的项目采用的模型并不一致,因此当模板采用伪模型验证后,需要重启web应用,才能使用真正的模型去测试,否则,模板引擎会报错,这是因为beetl默认的FastRuntimeEngine会根据模型优化模板,对同一个模板不同的模型会报错,除非采用DefaultTemplateEngine 或者页面申明类型变量是动态的。
4.12. 整合ajax的局部渲染技术
越来越多web网站依赖于ajax,如table的翻页,流行方式是浏览器发出ajax请求,后台处理后返回一个json,浏览器端将json数据拆开,拼成一条一条的行数据,然后生成dom节点,追加到表格里。 作为另外一种可选技术,beetl支持局部渲染技术,允许后台处理返回的是一个完成的html片段,这样,前端浏览器可以直接将这个html片段追加到表格里。在我做的性能测试里,俩种方式性能差别不大(https://beetlajax.oschina.mopaas.com/)
比如模板index.html有很多动态内容,有动态生成的菜单,有右侧的top10,也有核心区域的表格,大概内容如下
<#menu/>
<#top10> ....
id姓名
${user.id}${user.name}
当前页面${page!1} next pre
#ajax 用于告诉告诉模板引擎,此处是个局部渲染标记,标记为"userTable",对于正常渲染视图"index.html"页面,#ajax标记没什么用处,table仍能得到正常渲染。如果渲染的视图是index.html#userTable,则模板只会渲染#ajax标记得模板片段,其他部分将忽略。关于完整例子,可以参考https://beetlajax.oschina.mopaas.com/
后台代码如下:
render("/index.html#userTable");
ajax 片段渲染也支持默认情况下不渲染,仅仅做为一个片段使用,如一个页面有许多后台交互操作,并返回相应的html片段,可以将这些html片段也放到同一个模板里,使用ajax norender,表示渲染整个模板的时候默认并不需要渲染此ajax片段
操作成功
#ajax norender failure: { %>
操作失败
这样,此页面默认情况下并没有输出success,和 failure片段
注意,Ajax片段本质上是从模版的ajax标记处开始渲染,因此,ajax需要的变量在模版里也必须是全局变量,如果你只是个局部变量,beetl会报出找不到变量,即使你binding了这个变量,beetl也认为这个是局部变量,如:
" >
变量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。
4.13. 在页面输出错误提示信息
2.2.3版本以后,新增加org.beetl.ext.web.WebErrorHandler,可以在web开发的时候在页面输出提示信息,在产品模式下载后台输出提示信息(通过配置属性ESOURCE.autoCheck= true来认为是开发模式),仅仅需要配置如下:
ERROR_HANDLER = org.beetl.ext.web.WebErrorHandler