### Web集成 #### 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接口,在渲染模板之前增加自己的扩展,如: ```properties RESOURCE.root=/WEB-INF/views WEBAPP_EXT = com.park.oss.util.GlobalExt ``` ```java 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来设置 对于Web框架,你可以利用Filter等机制设置模板需要的公共变量,是个更值得推荐的办法 #### 集成技术开发指南 Beetl默认提供了WebRender用于帮助web集成开发,所有内置的集成均基于此方法。如果你认为Beetl内置的各个web框架集成功能不够,你可以继承此类,或者参考此类源码重新写,其代码如下 ```java 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<String> 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; } } ``` #### Servlet集成 只需要在Servlet代码里引用ServletGroupTemplate就能集成Beetl,他提供了一个render(String child, HttpServletRequest request, HttpServletResponse response)方法。例子如下: ```java 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 #### SpringMVC集成 需要做如下配置即可 ```xml <bean id="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"/> <bean id="viewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <property name="contentType" value="text/html;charset=UTF-8"/> </bean> ``` 同其他集成方式一样,模板的配置将放在beetl.properties中。 如果想获取GroupTemplate,可以调用如下代码 ```java BeetlGroupUtilConfiguration config = (BeetlGroupUtilConfiguration) this.getApplicationContext().getBean("beetlConfig"); GroupTemplate group = config.getGroupTemplate(); ``` Controller代码如下: ```java @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](http://git.oschina.net/xiandafu/springbeetlsql) 有完整例子 通常可以把模板放到WEB-INF目录下,除了可以配置beetl.propertis 外,还可以使用Spring配置 ```xml <bean id="beetlConfig" class="org.beetl.ext.spring." init-method="init"> <property name="root" value="/WEB-INF/templates"/> </bean> ``` #### SpringMVC集成高级 spring集成还允许注册被spring容器管理的Function,Tag等,也允许配置多个视图解析器等功能 ```xml <bean name="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="configFileResource" value="/WEB-INF/beetl.properties"/> <property name="functions"> <map> <entry key="testFunction" value-ref="testFunction"/> </map> </property> <property name="functionPackages"> <map> <entry key="fp" value-ref="testFunctionPackage"/> </map> </property> <property name="tagFactorys"> <map> <entry key="html.output" value-ref="testTagFactory"/> <entry key="html.output2" value-ref="testTagFactory2"/> </map> </property> </bean> <bean name="testTagFactory" class="org.beetl.ext.spring.SpringBeanTagFactory"> <property name="name" value="testTag"/> </bean> <bean name="testTagFactory2" class="org.beetl.ext.spring.SpringBeanTagFactory"> <property name="name" value="testTag2"/> </bean> <bean name="beetlViewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <property name="config" ref="beetlConfig"/> <property name="contentType" value="text/html;charset=UTF-8"/> </bean> ``` 如上图所示,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"。如代码: ```java @Service @Scope("prototype") public class TestTag extends Tag { } ``` - typeFormats: 同functions,参数是 Map<Class<?>, Format>,其中key为类型Class - formats:同functions,参数是 Map<String, Format>,其中key为格式化函数名 - virtualClassAttributes 同functions,参数Map<Class<?>, VirtualClassAttribute>,其中key为类型Class - virtualAttributeEvals ,类型为List<VirtualAttributeEval> - resourceLoader,资源加载器 ,值是 实现ResourceLoader的一个Bean - errorHandler ,错误处理,值是实现ErrorHandler的一个Bean - sharedVars,同functions,类型是Map<String, Object>,可以在此设置共享变量 - configProperties,类型是Properties,可以覆盖配置文件的某些属性 如下配置,指定了三个视图解析器,一个用于beetl页面渲染,一个用于cms,采用了beetl技术,另外一个是一些遗留的页面采用jsp ```xml <bean name="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="configFileResource" value="/WEB-INF/beetl.properties"/> </bean> <bean name="cmsbeetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="configFileResource" value="/WEB-INF/cms-beetl.properties"/> </bean> <!-- Beetl视图解析器1 --> <bean name="beetlViewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <!-- 多视图解析器,需要设置viewNames和order --> <property name="viewNames"> <list> <value>/template/**</value> </list> </property> <property name="suffix" value=".btl"/> <property name="contentType" value="text/html;charset=UTF-8"/> <property name="order" value="0"/> <!-- 多GroupTemplate,需要指定使用的bean --> <property name="config" ref="beetlConfig"/> </bean> <!-- Beetl视图解析器2 --> <bean name="cmsBeetlViewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <!-- 多视图解析器,需要设置viewNames和order --> <property name="viewNames"> <list> <value>/cmstemplate/**</value> </list> </property> <property name="contentType" value="text/html;charset=UTF-8"/> <property name="order" value="1"/> <!-- 多GroupTemplate,需要指定使用的bean --> <property name="config" ref="cmsbeetlConfig"/> </bean> <!-- JSP视图解析器 --> <bean name="JSPViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 注意JSP的这个视图解析器order必须在最后 --> <property name="order" value="256"/> <!-- beetl配置不支持前缀,这不同于jsp 和 freemaker --> <property name="prefix" value="/WEB-INF/"/> <property name="suffix" value=".jsp"/> <property name="contentType" value="text/html;charset=UTF-8"/> </bean> ``` Beetl视图解析器属性同spring自带的视图解析器一样,支持contentType,order,prefix,suffix等属性。 注意视图解析器里的属性viewNames,这个用于判断controller返回的path到底应该交给哪个视图解析器来做。 - 以/template开头的是beetlViewResolver来渲染。 - 以/cmstemplate是交给cmsBeetlViewResolver渲染。 - 如果都没有匹配上,则是jsp渲染 你也可以通过扩展名来帮助Spring决定采用哪种视图解析器,比如 ~~~xml <property name="viewNames"> <list> <value>/**/*.btl</value> </list> </property> ~~~ 如果你想更改此规则,你只能增加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会找不到模板 - 如果根目录不是默认目录,可以通过添加root属性 ~~~xml <bean name="cmsbeetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="root" value="/WEB-INF/views"/> </bean> ~~~ #### Spring Boot集成 ~~~xml <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl-framework-starter</artifactId> <version>1.2.30.Beetl.RELEASE</version> </dependency> ~~~ starter 自动处理以btl结尾的视图,模板根目录是Spring Boot默认的templates目录。如下配置可以修改beetl部分属性 * beetl-beetlsql.dev,默认为true,即自动检查模板变化 * beetl.enabled 默认为true,集成beetl。 * beetl.suffix 默认为btl,表示只处理视图后缀为btl的模板,比如controller里代码是“return /common/index.btl”,则能被Beetl处理,你写成"return /common/index",或者"/common/index.html",都会出现404错误。 Starter可以实现BeetlTemplateCustomize来定制Beetl ~~~java @Configuration public MyConfig{ @Bean public BeetlTemplateCustomize beetlTemplateCustomize(){ return new BeetlTemplateCustomize(){ public void customize(GroupTemplate groupTemplate){ } }; } } ~~~ 使用Starter来配置已经够用,如果你想自己配置模板引擎, 通过java config来配置 beetl需要的BeetlGroupUtilConfiguration,和 BeetlSpringViewResolver,参考代码如下 ```java @Configuration public class BeetlConf { @Value("${beetl.templatesPath}") String templatesPath;//模板根目录 ,比如 "templates" @Bean(name = "beetlConfig") public BeetlGroupUtilConfiguration getBeetlGroupUtilConfiguration() { BeetlGroupUtilConfiguration beetlGroupUtilConfiguration = new BeetlGroupUtilConfiguration(); //获取Spring Boot 的ClassLoader ClassLoader loader = Thread.currentThread().getContextClassLoader(); if(loader==null){ loader = BeetlConf.class.getClassLoader(); } beetlGroupUtilConfiguration.setConfigProperties(extProperties);//额外的配置,可以覆盖默认配置,一般不需要 ClasspathResourceLoader cploder = new ClasspathResourceLoader(loader, templatesPath); beetlGroupUtilConfiguration.setResourceLoader(cploder); beetlGroupUtilConfiguration.init(); //如果使用了优化编译器,涉及到字节码操作,需要添加ClassLoader beetlGroupUtilConfiguration.getGroupTemplate().setClassLoader(loader); return beetlGroupUtilConfiguration; } @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; } } ``` 注意:这里并没有配置后缀,因此controller代码里必须显式的加上后缀 ~~~java //return "/hello" 错误用法 return "hello.html" ~~~ > 注意,beetl-framework-starter目前只包含Beetl集成,不在包含BeetlSQL #### Jodd集成 需要配置web.xml,将所有请求交给jodd处理,参考:[http://jodd.org/doc/madvoc/setup.html](http://jodd.org/doc/madvoc/setup.html) ```xml <filter> <filter-name>madvoc</filter-name> <filter-class>jodd.madvoc.MadvocServletFilter</filter-class> <init-param> <param-name>madvoc.webapp</param-name> <param-value>test.MyWebApplication</param-value> </init-param> <init-param> <param-name>madvoc.configurator</param-name> <param-value>test.MyAutomagicMadvocConfigurator</param-value> </init-param> </filter> <filter-mapping> <filter-name>madvoc</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` MyWebApplication 和 MyAutomagicMadvocConfigurator 需要自己参照如下例子写一个,前者用来设置beetl作为视图渲染,后者配置Jodd不要扫描beetl struts集成里引用的struts类 ```java public class MyAutomagicMadvocConfigurator extends AutomagicMadvocConfigurator { public MyAutomagicMadvocConfigurator(){ super(); //不扫描beetl 里jar文件里的action和result,否则,会扫描StrutsResultSupport不相干的class this.rulesJars.exclude("**/*beetl*.jar"); } } ``` ```java 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 即可。 ```java @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](https://git.oschina.net/xiandafu/beetl-jodd-sample) 有完整例子 #### JFinal4 集成方案 ~~~xml <dependency> <groupId>com.ibeetl</groupId> <artifactId>jfinal4-beetl-integration</artifactId> <version>1.0</version> </dependency> ~~~ ```java public class DemoConfig extends JFinalConfig { public void configConstant(Constants me) { PropKit.use("a_little_config.txt"); // 加载少量必要配置,随后可用PropKit.get(...)获取值 me.setDevMode(PropKit.getBoolean("devMode", false)); JFinalBeetlRenderFactory rf = new JFinalBeetlRenderFactory(); rf.config(); me.setRenderFactory(rf); GroupTemplate gt = rf.groupTemplate; //根据gt可以添加扩展函数,格式化函数,共享变量等, } ``` 业务逻辑代码: ```java public void modify(){ int artId = getParaToInt(0, -1); setAttr("title", "修改文章"); List<Cate> cateLists = Cate.getAllCate(); //模板里访问cateLists,atr, setAttr("cateLists", cateLists); setAttr("art", Article.dao.findById(artId)); render("/modify.html"); } ``` BeetlRenderFactory 默认使用FileResourceLoader ,其根目录位于WebRoot目录下,如果你需要修改到别的目录,可以设置配置文件,如 ```properties RESOURCE.root= /WEB-INF/template/ ``` [https://git.oschina.net/xiandafu/beetl-jfinal-sample](https://git.oschina.net/xiandafu/beetl-jfinal-sample) 有完整例子,采用jfinal+beetl写的一个博客系统 https://git.oschina.net/xiandafu/jfinal_beet_beetsql_btjson 同上,但DAO部分采用了BeetlSql > 如果你需要jfinal3,jfinal2集成,可以告诉我,或者参考jfinal4集成自己做一个,jfinal每次大版本升级并没有集成兼容 #### Nutz集成 Nutz集成提供了 BeetlViewMaker ,实现了 ViewMaker方法,如下代码 ```java @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<UserProfile> list = dao.query(UserProfile.class, null, pager); ctx.set("pager", pager); ctx.set("list", list); return ctx; } ``` ```xml <html> <head> <title>Beetl&Nutz</title> </head> <body> <p>总共 ${list.~size}<p/> <% for(user in list){ %> <p>hello,${user.nickname};<p/> <% } %> <p>当前页${pager.pageNumber},总共${pager.pageCount}页<p/> </body> </html> ``` #### Struts2集成 需要在struts2配置文件里添加result-types做如下配置 ```xml <package name="default" namespace="/" extends="struts-default"> <!-- .... --> <result-types> <result-type name="beetl" class="org.beetl.ext.struts2.Struts2BeetlActionResult" default="true" > <param name="contentType">text/html; charset=UTF-8</param> </result-type> </result-types> <action name="HelloWorld" class="com.beetl.struts.HelloWorld"> <result>/hello.html</result> </action> <action name="Ajax" class="com.beetl.struts.AjaxHtml"> <result>/table.html#table</result> </action> <!-- .... --> </package> ``` 该类会根据struts配置文件获取模板,如上例的hello.html,并将formbean的属性,以及request属性作为全局变量传递给模板 [https://git.oschina.net/xiandafu/beetl-struts2-sample](https://git.oschina.net/xiandafu/beetl-struts2-sample) 有完整例子 > Struts2.5 本身做了包名调整,因此自从Beetl2.8.0以后,只支持Struts2.5.x以上版本,这个版本安全漏洞少.... :) #### 整合ajax的局部渲染技术 越来越多web网站依赖于ajax,如table的翻页,流行方式是浏览器发出ajax请求,后台处理后返回一个json,浏览器端将json数据拆开,拼成一条一条的行数据,然后生成dom节点,追加到表格里。 作为另外一种可选技术,beetl支持局部渲染技术,允许后台处理返回的是一个完成的html片段,这样,前端浏览器可以直接将这个html片段追加到表格里。在我做的性能测试里,俩种方式性能差别不大([http://bbs.ibeetl.com/ajax//](http://bbs.ibeetl.com/ajax/)) 比如模板index.html有很多动态内容,有动态生成的菜单,有右侧的top10,也有核心区域的表格,大概内容如下 ```javascript <#menu/> <#top10> ....</#top10> <div id="table-container" > <% //ajax片段开始 #ajax userTable: { %> <table> <tr><td width=100>id</td><td width=100>姓名</td></tr> <% for(user in users){ %> <tr><td>${user.id}</td><td>${user.name}</td></tr> <% } %> </table> 当前页面<span id="current">${page!1}</span><span style="width:20px"></span> <a href="#"><span class="page">next</span></a> <a href="#" ><span class="page">pre</span></a> <% //ajax片段结尾 } %> ``` \#ajax 用于告诉告诉模板引擎,此处是个局部渲染标记,标记为"userTable",对于正常渲染视图"index.html"页面,#ajax标记没什么用处,table仍能得到正常渲染。如果渲染的视图是index.html#userTable,则模板只会渲染#ajax标记得模板片段,其他部分将忽略。关于完整例子,可以参考[https://git.oschina.net/xiandafu/beetlajax](https://git.oschina.net/xiandafu/beetlajax) 后台代码如下: ```javascript render("/index.html#userTable"); ``` 只需要在模板路径后加上#就表示渲染的并非是整个模板,而是模板的一部分,这一部分由#后面的标记来标示 ajax 片段渲染也支持默认情况下不渲染,仅仅做为一个片段使用,如一个页面有许多后台交互操作,并返回相应的html片段,可以将这些html片段也放到同一个模板里,使用ajax norender,表示渲染整个模板的时候默认并不需要渲染此ajax片段 ```javascript <% <html> </html> #ajax norender success: { %> <div id="success"> 操作成功 </div> <% } %> #ajax norender failure: { %> <div id="failure"> 操作失败 </div> <% } %> ``` 这样,此页面默认情况下并没有输出success,和 failure片段 >注意,Ajax片段本质上是从模版的ajax标记处开始渲染,因此,ajax需要的变量在模版里也必须是全局变量,如果你只是个局部变量,beetl会报出找不到变量,即使你binding了这个变量,beetl也认为这个是局部变量,如 > >```javascript ><% >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,两者是一样的,比如 ~~~java <% #fragment part2:{ println("part2"); } %> ~~~ #### 在页面输出错误提示信息 2.2.3版本以后,新增加org.beetl.ext.web.WebErrorHandler,可以在web开发的时候在页面输出提示信息,在产品模式下在后台输出提示信息(通过配置属性ESOURCE.autoCheck= true来认为是开发模式),仅仅需要配置如下: ```properties ERROR_HANDLER = org.beetl.ext.web.WebErrorHandler ```