Beetl2.7.16中文文档(3)之Web集成

2017-06-10  本文已影响786人  西漠

4. Web集成

4.1. Web提供的全局变量

Web集成模块向模板提供web标准的变量,做如下说明

你可以在模板任何地方访问这些变量

如果你需要扩展更多属性,你也可以配置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<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;
        }
}

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集成

需要做如下配置即可

<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,可以调用如下代码

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等,也允许配置多个视图解析器等功能

<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有很多属性,列举如下

@Service
@Scope("prototype")
public class TestTag extends Tag {
}

如下配置,指定了三个视图解析器,一个用于beetl页面渲染,一个用于cms,采用了beetl技术,另外一个是一些遗留的页面采用jsp

<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到底应该交给哪个视图解析器来做。

如果你想更改此规则,你只能增加canHandle方法指定你的逻辑了。详情参考org.springframework.web.servlet.view.UrlBasedViewResolver.canHandle

对于仅仅需要redirect和forward的那些请求,需要加上相应的前缀

其他集成需要注意的事项:

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

<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类

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<Cate> 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<UserProfile> list = dao.query(UserProfile.class, null, pager);
        ctx.set("pager", pager);
        ctx.set("list", list);
        return ctx;
}
<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>

需要注意的是,如果使用了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做如下配置

<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 有完整例子

郑重申明

鉴于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>
<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

后台代码如下:

render("/index.html#userTable");

只需要在模板路径后加上#就表示渲染的并非是整个模板,而是模板的一部分,这一部分由#后面的标记来标示

ajax 片段渲染也支持默认情况下不渲染,仅仅做为一个片段使用,如一个页面有许多后台交互操作,并返回相应的html片段,可以将这些html片段也放到同一个模板里,使用ajax norender,表示渲染整个模板的时候默认并不需要渲染此ajax片段

<%
<html>

</html>
#ajax norender success: {
%>
<div id="success"> 操作成功
</div>

<%
}
%>

#ajax norender failure: {
%>
<div id="failure"> 操作失败
</div>

<%
}
%>

这样,此页面默认情况下并没有输出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. 常用内置方法
5.1.2. 字符串相关方法

strutil方法对参数均不做空指针检测,你可自定义方法来覆盖这些内置的方法

5.1.3. 数组相关方法
5.1.4. 正则表达式相关方法
5.1.5. Spring 相关函数

Spring函数并没有内置,需要注册,如下

<bean name="beetlGroupUtilConfiguration" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init">
        <property name="functions">
                <map>
                        <!-- 定义SpEL方法 -->
                        <entry key="spel">
                                <bean class="org.beetl.ext.spring.SpELFunction"/>
                        </entry>
                </map>
                </property>
                 <property name="functionPackages">
                <map>
                        <entry key="sputil">
                                <bean class="org.beetl.ext.spring.UtilsFunctionPackage"/>
                        </entry>
                </map>
        </property>
</bean>

spel(spelString, rootObject) SpEL方法传入一个Spring SpEL表达式以获取表达式结果,方法建议以函数的方式定义在BeetlGroupUtilConfiguration的functions中

spelString: SpEL表达式字符串,必传(否则返回null) rootObject: 作为spel的根对象(对应#root),可以是一个Map或Bean对象,默认取空Map。由于Beetl运行上下文无法直接获取模版局部变量的变量名,建议局部变量采用自定义Map的方式传入

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中,可以更改

5.1.7. shiro

参考文档 https://my.oschina.net/xiandafu/blog/143109

5.2. 内置格式化方法

5.3. 内置标签函数

5.4. 性能优化的秘密

Beetl2.0目前只完成了解释引擎,使用解释引擎好处是可以适用于各种场景,性能测试表明,Beetl2.0引擎是Freemaker的4-6倍,跟最好的编译引擎性能相比,也相差只有30%百分点。为什么Beetl能跑的如此之快呢,简单的说,有如下策略

相关文章

5.5. Eclipse 插件

选中您要安装的Beetl Eclipse Plugin,按提示依次Next,直至Finish重启Eclipse即可.

使用说明:

  1. 工程属性里有个beetl属性,可以指定定界符号等,默认是<%%> ${}。也可以指定模板根目录(可选,不必手工填写,在模板单击定位里会提示你选择)
  2. ctrl-2 定位到下一个beetl 块
  3. ctrl-3 定位到上一个beetl块
  4. ctrl-4 将普通文件以beetl editor方式打开,并保持同步编辑
  5. ctrl-5 静态文本全部折叠和打开静态文本折叠
  6. 可以ctrl+单击字符串定位到字符串对应的模板文件,第一次使用的时候,需要选择模板根目录,随后,也可以在project属性的beetl配置里配置模板根目录
  7. alt-/ 进行上下文提示。也可以键入此快速输入定界符号和占位符号
  8. alt-shift-p 从{ 快速移动到 匹配的},或者反之亦然。如果只单击{ 则会框选住匹配的} 而光标不移动
  9. 选中任何id,都能全文框选住同样的id。
  10. ctrl-/ 单行注释,或者取消注释
  11. 通常eclipse具有的快捷操作方式,beetl仍然予以保留不变
  12. 具备一定的错误提示,目前只提示第一个发现的错误。
  13. 双击{ } 可以选中之间的内容

5.6. 性能测试对比

测试用例一 https://github.com/javamonkey/ebm

beetl1beetl1

测试用例二 http://git.oschina.net/kiang/teb

beetl2beetl2

测试用例三 https://github.com/javamonkey/template-benchmark

beetl3beetl3
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 开发团队

作者

助手

代码捐助者

文档校验

上一篇下一篇

猜你喜欢

热点阅读