Beetl2.7.16中文文档(1)之基础语法

Beetl2.7.16中文文档

Beetl作者:李家智 <xiandafu@126.com>

1. 什么是Beetl

Beetl目前版本是2.7.16,相对于其他java模板引擎,具有功能齐全,语法直观,性能超高,以及编写的模板容易维护等特点。使得开发和维护模板有很好的体验。是新一代的模板引擎。总得来说,它的特性如下:

  • 功能完备:作为主流模板引擎,Beetl具有相当多的功能和其他模板引擎不具备的功能。适用于各种应用场景,从对响应速度有很高要求的大网站到功能繁多的CMS管理系统都适合。Beetl本身还具有很多独特功能来完成模板编写和维护,这是其他模板引擎所不具有的。
  • 非常简单:类似Javascript语法和习俗,只要半小时就能通过半学半猜完全掌握用法。拒绝其他模板引擎那种非人性化的语法和习俗。同时也能支持html 标签,使得开发CMS系统比较容易
  • 超高的性能:Beetl 远超过主流java模板引擎性能(引擎性能5-6倍与freemaker,2倍于JSP。参考附录),而且消耗较低的CPU。
  • 易于整合:Beetl能很容易的与各种web框架整合,如Spring MVC,JFinal,Struts,Nutz,Jodd,Servlet等。
  • 支持模板单独开发和测试,即在MVC架构中,即使没有M和C部分,也能开发和测试模板。
  • 扩展和个性化:Beetl支持自定义方法,格式化函数,虚拟属性,标签,和HTML标签. 同时Beetl也支持自定义占位符和控制语句起始符号也支持使用者完全可以打造适合自己的工具包。

关于性能

通过与主流模板引擎Freemarker,Vecloity以及JSP对比,Beetl6倍于Freemarker,2倍于JSP。这是因为宏观上,通过了优化的渲染引擎,IO的二进制输出,字节码属性访问增强,微观上,通过一维数组保存上下文Context,静态文本合并处理,通过重复使用字节数组来防止java频繁的创建和销毁数组,还使用模板缓存,运行时优化等方法。详情参考附录

独特功能

Beetl有些功能是发展了10多年的模板引擎所不具备的,这些功能非常利于模板的开发和维护,如下

  1. 自定义占位符和控制语句起始符号,这有利于减小模板语法对模板的倾入性,比如在html模板中,如果定义控制语句符号是``,那么,大部分模板文件都能通过浏览器打开。有的使用者仅仅采用了单个符号@ (或者单个符号“”)以及回车换号作为控制语句起始符号,这又能提高开发效率
  2. 可单独测试的模板。无需真正的控制层和模型层,Beetl的模板就可以单独开发和测试
  3. 同时支持较为松散的MVC和严格的MVC,如果在模板语言里嵌入计算表达式,复杂条件表达式,以及函数调用有干涉业务逻辑嫌疑,你可以禁止使用这些语法。
  4. 强大的安全输出,通过安全输出符号!,能在模板变量,变量属性引用,for循环,占位符输出,try-catch中等各个地方提供安全输出,保证渲染正常。
  5. 模板变量:运行将模板的某一部分输出像js那样赋值给一个变量,稍后再处理。利用模板变量能完成非常复杂的页面布局(简单的布局可使用include,layout标签函数)
  6. 类型推测,能在运行的时候推测模板变量类型,从而优化性能,也可以通过注解的方法显示的说明模板变量属性(这是非必须的,但有助于IDE自动提示功能)
  7. 可插拔的设计,错误信息提示,模板引擎缓存机制,模板资源管理,本地调用的安全管理器,严格MVC限制,模板引擎本身都有默认的实现,但又完全可以自定义以适合特定需求
  8. 增强的语法,如for-elsefor, select-case,安全输出符号!,省略的三元表达式 等,这些语法特别适合模板开发
  9. 局部渲染技术,结合现在js的ajax技术。
  10. 性能超高,具有最快的模板解释引擎,同时,又有较低的CPU消耗。5-6倍于国内使用的Freemaker。适合各类模板应用,如代码生成工具,CMS系统,普通网站,超高访问量的门户系统,和富客户端JS框架整合的后台管理应用

小白如何开始

  • 需要通读基本用法,大部分都是讲解语法,而语法跟js很接近,所以可以快速预览,但Beetl是针对模板设计, 所以像安全输出,标签和html标签,全局变量,临时变量和共享变量,布局技术,以及直接调用java代码等还需要认真读一遍。
  • 如果从事web开发,还需要阅读web集成里的第一节“web提供的全局变量”,如果web里还使用ajax技术,可以阅读“整合ajax的局部渲染技术”。
  • 包含有spring,jfinal,jodd,struts 等demo可以作为参考学习用https://git.oschina.net/xiandafu 任何问题,都可以在ibeetl.com 社区上提问。目前答复率是100%,提问需要详细说明自己的期望,出错信息,附上代码或者图片

联系作者

作者:闲.大赋 (李家智)等(参考附录查看代码贡献者)

QQ技术交流群:219324263

邮件:xiandafu@126.com

Beetl社区:bbs.ibeetl.com

源码主页:https://github.com/javamonkey/beetl2.0

在线体验和代码分享 http://ibeetl.com/beetlonline/

2. 基本用法

2.1. 安装

如果使用maven,请使用如下坐标

<dependency>
        <groupId>com.ibeetl</groupId>
        <artifactId>beetl</artifactId>
        <version>2.7.16</version>
</dependency>

如果非maven工程,直接下载http://git.oschina.net/xiandafu/beetl2.0/attach_files

2.2. 从GroupTemplate开始

StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader();
Configuration cfg = Configuration.defaultConfiguration();
GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
Template t = gt.getTemplate("hello,${name}");
t.binding("name", "beetl");
String str = t.render();
System.out.println(str);

Beetl的核心是GroupTemplate,创建GroupTemplate需要俩个参数,一个是模板资源加载器,一个是配置类,模板资源加载器Beetl内置了6种,分别是

  • StringTemplateResourceLoader:字符串模板加载器,用于加载字符串模板,如本例所示
  • FileResourceLoader:文件模板加载器,需要一个根目录作为参数构造,传入getTemplate方法的String是模板文件相对于Root目录的相对路径
  • ClasspathResourceLoader:文件模板加载器,模板文件位于Classpath里
  • WebAppResourceLoader:用于webapp集成,假定模板根目录就是WebRoot目录,参考web集成章
  • MapResourceLoader : 可以动态存入模板
  • CompositeResourceLoader 混合使用多种加载方式

代码第5行将变量name传入模板里,其值是“Beetl”。 代码第6行是渲染模板,得到输出,template提供了多种获得渲染输出的方法,如下

  • tempalte.render() 返回渲染结果,如本例所示
  • template.renderTo(Writer) 渲染结果输出到Writer里
  • template.renderTo(OutputStream ) 渲染结果输出到OutputStream里
  1. 关于如何使用模板资源加载器,请参考下一节
  2. 如何对模板进行配置,请参考下一节

2.3. 模板基础配置

Beetl提供不但功能齐全,而且还有很多独特功能,通过简单的配置文件,就可以定义众多的功能,默认情况下,Configuration类总是会先加载默认的配置文件(位于/org/beetl/core/beetl-default.properties,作为新手,通常只需要关注3,4,5,6行定界符的配置,以及12行模板字符集的配置就可以了,其他配置会在后面章节陆续提到)下,其内容片断如下:

#默认配置
ENGINE=org.beetl.core.engine.FastRuntimeEngine
DELIMITER_PLACEHOLDER_START=${
DELIMITER_PLACEHOLDER_END=}
DELIMITER_STATEMENT_START=<%
DELIMITER_STATEMENT_END=%>
DIRECT_BYTE_OUTPUT = FALSE
HTML_TAG_SUPPORT = true
HTML_TAG_FLAG = #
HTML_TAG_BINDING_ATTRIBUTE = var
NATIVE_CALL = TRUE
TEMPLATE_CHARSET = UTF-8
ERROR_HANDLER = org.beetl.core.ConsoleErrorHandler
NATIVE_SECUARTY_MANAGER= org.beetl.core.DefaultNativeSecurityManager
MVC_STRICT = FALSE

#资源配置,resource后的属性只限于特定ResourceLoader
RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader
#classpath 根路径
RESOURCE.root= /
#是否检测文件变化,开发用true合适,但线上要改为false
RESOURCE.autoCheck= true
#自定义脚本方法文件的Root目录和后缀
RESOURCE.functionRoot = functions
RESOURCE.functionSuffix = html
#自定义标签文件Root目录和后缀
RESOURCE.tagRoot = htmltag
RESOURCE.tagSuffix = tag
#####  扩展 ##############
## 内置的方法
FN.date = org.beetl.ext.fn.DateFunction
......
##内置的功能包
FNP.strutil = org.beetl.ext.fn.StringUtil
......
##内置的默认格式化函数
FTC.java.util.Date = org.beetl.ext.format.DateFormat
.....
## 标签类
TAG.include= org.beetl.ext.tag.IncludeTag

第2行配置引擎实现类,默认即可.

第3,4行指定了占位符号,默认是${ },也可以指定为其他占位符。

第5,6行指定了语句的定界符号,默认是<% %>,也可以指定为其他定界符号

第7行指定IO输出模式,默认是FALSE,即通常的字符输出,在考虑高性能情况下,可以设置成true。详细请参考高级用法

第8,9行指定了支持HTML标签,且符号为#,默认配置下,模板引擎识别<#tag ></#tag>这样的类似html标签,并能调用相应的标签函数或者模板文件。你也可以指定别的符号,如bg: 则识别<bg:

第10行 指定如果标签属性有var,则认为是需要绑定变量给模板的标签函数

第11行指定允许本地Class直接调用

第12行指定模板字符集是UTF-8

第13行指定异常的解析类,默认是ConsoleErrorHandler,他将在render发生异常的时候在后台打印出错误信息(System.out)。

第14行指定了本地Class调用的安全策略

第15行配置了是否进行严格MVC,通常情况下,此处设置为false.

第18行指定了默认使用的模板资源加载器,注意,在beetl与其他MVC框架集成的时候,模板加载器不一定根据这个配置,比如spring,他的RESOURCE_LOADER以spring的配置为准

第20到22行配置了模板资源加载器的一些属性,如设置根路径为/,即Classpath的顶级路径,并且总是检测模板是否更改

第23行配置了自定义的方法所在的目录以及文件名后缀。beetl既支持通过java类定义方法,也支持通过模板文件来定义方法

第26行配置了自定义的html标签所在的目录以及文件名后缀。beetl既支持通过java类定义标签,也支持通过模板文件来定义标签

第31行注册了一个date方法,其实现类是org.beetl.ext.fn.DateFunction

第34行注册了一个方法包strutil,其实现类org.beetl.ext.fn.StringUtil,此类的每个public方法都将注册为beetl的方法

第37行注册了一个日期格式化函数

第40行注册了一个include标签函数

模板开发者可以创建一个beetl.properties的配置文件,此时,该配置文件将覆盖默认的配置文件属性,比如,你的定界符考虑是`` ,则在beetl.properties加入一行即可,并将此配置文件放入Classpath根目录下即可。 Configuration.defaultConfiguration()总是先加载系统默认的,然后再加载Beetl.properties的配置属性,如果有重复,用后者代替前者的配置

#自定义配置
DELIMITER_STATEMENT_START=<!--:
DELIMITER_STATEMENT_END=-->

2.4.0 新功能:beetl 支持通过模板本生来完成函数,即模板函数,或者通过模板来实现HTML标签(而不用写java代码),可以beetl.properties为这种应用设置的不同的语句定界符来跟常规模板做区分,如下

FUNCTION_TAG_LIMITER=<% ; %>

分号分割开,如果配置文件没有FUNCTION_TAG_LIMITER=,则模板函数,html标签使用同DELIMITER_STATEMENT_STARTDELIMITER_STATEMENT_END

2.4. 模板资源加载器

资源加载器是根据String值获取Resource实例的工场类,同时资源加载器还要负责响应模板引擎询问模板是否变化的调用。对于新手来说,无需考虑模板资源加载器如何实现,只需要根据自己场景选择系统提供的三类模板资源加载器即可

2.4.1. 字符串模板加载器

在创建GroupTemplate过程中,如果传入的是StringTemplateResourceLoader,则允许通过调用gt.getTemplate(String template)来获取模板实例对象,如2.1所示

2.4.2. 文件资源模板加载器

更通常情况下,模板资源是以文件形式管理的,集中放在某一个文件目录下(如webapp的模板根目录就可能是WEB-INF/template里),因此,可以使用FileResourceLoader来加载模板实例,如下代码:

String root = System.getProperty("user.dir")+File.separator+"template";
FileResourceLoader resourceLoader = new FileResourceLoader(root,"utf-8");
Configuration cfg = Configuration.defaultConfiguration();
GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
Template t = gt.getTemplate("/s01/hello.txt");
String str = t.render();
System.out.println(str);

第1行代码指定了模板根目录,即位于项目工程下的template目录 第2行构造了一个资源加载器,并指定字符集为UTF-8 (也可不指定,因为配置文件默认就是UTF-8); 第5行通过模板的相对路径/s01/hello.txt来加载模板

2.4.3. Classpath资源模板加载器

还有种常情况下,模板资源是打包到jar文件或者同Class放在一起,因此,可以使用ClasspathResourceLoader来加载模板实例,如下代码:

ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader("org/beetl/sample/s01/");
Configuration cfg = Configuration.defaultConfiguration();
GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
Template t = gt.getTemplate("/hello.txt");
String str = t.render();
System.out.println(str);

第1行代码指定了模板根目录,即搜索模板的时候从根目录开始,如果new ClasspathResourceLoader("template/"),则表示搜索template下的模板。此处用空构造函数,表示搜索路径是根路径,且字符集默认字符集UTF-8.

第4行通过模板的相对路径org/beetl/sample/s01/hello.txt来加载模板

2.4.4. WebApp资源模板加载器

WebAppResourceLoader 是用于web应用的资源模板加载器,默认根路径是WebRoot目录。也可以通过制定root属性来设置相对于WebRoot的的模板根路径,从安全角考虑,建议放到WEB-INF目录下

如下是Jfinal集成 里初始化GroupTemplate的方法

Configuration cfg = Configuration.defaultConfiguration();
WebAppResourceLoader resourceLoader = new WebAppResourceLoader();
groupTemplate = new GroupTemplate(resourceLoader, cfg);

WebAppResourceLoader 假定 beetl.jar 是位于 WEB-INF/lib 目录下,因此,可以通过WebAppResourceLoader类的路径来推断出WebRoot路径从而指定模板根路径。所有线上环境一般都是如此,如果是开发环境或者其他环境不符合此假设,你需要调用resourceLoader.setRoot() 来指定模板更路径

2.4.5. 自定义资源模板加载器

有时候模板可能来自文件系统不同目录,或者模板一部分来自某个文件系统,另外一部分来自数据库,还有的情况模板可能是加密混淆的模板,此时需要自定义资源加载,继承ResouceLoader才能实现模板功能,这部分请参考高级部分

2.5. 定界符与占位符号

Beetl模板语言类似JS语言和习俗,只需要将Beetl语言放入定界符号里即可,如默认的是<% %> ,占位符用于静态文本里嵌入占位符用于输出,如下是正确例子

<%
var a = 2;
var b = 3;
var result = a+b;
%>
hello 2+3=${result}

千万不要在定界符里使用占位符号,因为占位符仅仅嵌在静态文本里,如下例子是错误例子

<%
var a = "hi";
var c = ${a}+"beetl"; //应该是var c = a+"beetl"
%>

每次有人问我如上例子为啥不能运行的时候,我总是有点憎恶velocity 带来的这种非人性语法

定界符和占位符 通常还有别的选择,如下定界符

  • @ 和回车换行 (此时,模板配置DELIMITER_STATEMENT_END= 或者 DELIMITER_STATEMENT_END=null 都可以)
  • #: 和回车换行
  • <? 和 ?>

占位符--#{ }-##

你也可以与团队达成一致意见来选择团队喜爱的定界符号和占位符号。

定界符号里是表达式,如果表达式跟定界符或者占位符有冲突,可以在用 “\” 符号,如

@for(user in users){
email is ${user.name}\@163.com
@}
${[1,2,3]} //输出一个json列表
${ {key:1,value:2 \}  } //输出一个json map,} 需要加上\

2.6. 注释

Beetl语法类似js语法,所以注释上也同js一样: 单行注释采用//

多行注视采用/**/

<%
/*此处是一个定义变量*/
var a = 3; //定义一个变量.

/* 以下内容都将被注释
%>

<% */ %>

第2行是一个多行注释

第3行是一个单行注释

第5行到第8行采用的是多行注释,因此里面有内容也是注释,模板将不予处理

2.7. 临时变量定义

在模板中定义的变量成为临时变量,这类似js中采用var 定义的变量,如下例子

<%
var a = 3;
var b = 3,c = "abc",d=true,e=null;
var f = [1,2,3];
var g = {key1:a,key2:c};
var i = a+b;
%>

2.8. 全局变量定义

全局变量是通过template.binding传入的变量,这些变量能在模板的任何一个地方,包括子模板都能访问到。如java代码里

template.binding("list",service.getUserList());

//在模板里
<%
for(user in list){
%>
hello,${user.name};
<% } %>

2.9. 共享变量

共享变量指在所有模板中都可以引用的变量,可通过groupTemplate.setSharedVars(Map<String, Object> sharedVars)传入变量,这些变量能用在 所有模板 的任何一个地方

//.....
GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
Map<String,Object> shared = new HashMap<String,Object>();
shared.put("name", "beetl");
gt.setSharedVars(shared);
Template t = gt.getTemplate("/org/beetl/sample/s0208/t1.txt");
String str = t.render();
System.out.println(str);
t = gt.getTemplate("/org/beetl/sample/s0208/t2.txt");
str = t.render();
System.out.println(str);
//t1.txt
hi,${name}
//t2.txt
hello,${name}

2.10. 模板变量

模板变量是一种特殊的变量,即可以将模板中任何一段的输出赋值到该变量,并允许稍后在其他地方使用,如下代码

<%
var content = {
        var c = "1234";
        print(c);
%>
模板其他内容:

<% }; %>

第2行定义了一个模板变量content = { …} ; 此变量跟临时变量一样,可以在其他地方使用,最常见的用法是用于复杂的布局。请参考高级用法布局

2.11. 引用属性

属性引用是模板中的重要一部分,beetl支持属性同javascript的支持方式一样,如下

  1. Beetl支持通过”.”号来访问对象的的属性,如果javascript一样。如果User对象有个getName()方法,那么在模板中,可以通过${xxx.name}来访问
  2. 如果模板变量是数组或者List类,这可以通过[] 来访问,如${userList[0]}
  3. 如果模板变量是Map类,这可以通过[]来访问,如${map[“name”]},如果key值是字符串类型,也可以使用${map.name}.但不建议这么使用,因为会让模板阅读者误以为是一个Pojo对象
  4. Beetl也支持Generic Get方式,即如果对象有一个public Object get(String key)方法,可以通过”.”号或者[]来访问,譬如 ${activityRecord.name}或者${activityRecord[“name”] }都将调用activityRecord的 get(String key)方法。如果对象既有具体属性,又有Generic get(这种模型设计方式是不值得鼓励),则以具体属性优先级高.
  5. Beetl也可以通过[]来引用属性,如${user[“name”]} 相当于${user.name}.这跟javascript保持一致。但建议不这么做,因为容易让阅读模板的人误认为这是一个Map类型
  6. Beetl 还可以定义额外的对象属性,而无需更改java对象,这叫着虚拟属性,如,对于所有集合,数组,都有共同的虚拟属性size.虚拟属性是“.~”+虚拟属性名
template.binding("list",service.getUserList());
template.binding("pageMap",service.getPage());

//在模板里
总共 ${list.~size}
<%
for(user in list){
%>
hello,${user.name};
<% } %>

当前页${pageMap['page']},总共${pageMap["total"]}

2.12. 属性赋值

Beetl2.7.0 开始支持对象赋值,如:

<%
var user = ....
user.name="joelli";
user.friends[0] = getNewUser();
user.map["name"] = "joelli";
%>

2.13. 算数表达式

Beetl支持类似javascript的算术表达式和条件表达式,如+ - * / % 以及(),以及自增++,自减--

<%
var a = 1;
var b = "hi";
var c = a++;
var d = a+100.232;
var e = (d+12)*a;
var f = 122228833330322.1112h
%>

Beetl里定义的临时变量类型默认对应的java类型是Int型或者double类型,对于模板常用情况,已经够了.如果需要定义长精度类型(对应java的BigDecimal),则需要在数字末尾加上h以表示这是长精度BigDecimal,其后的计算和输出以及逻辑表达式都将按照长精度类型来考虑。

2.14. 逻辑表达式

Beetl支持类似Javascript,java的条件表达式 如>,<,==,!=,>= , <= 以及 !, 还有&&和 || ,还有三元表达式等,如下例子

<%
var a = 1;
var b="good";
var c = null;

if(a!=1&&b=="good"&&c==null){
        ......
}
%>

三元表达式如果只考虑true条件对应的值的话,可以做简化,如下俩行效果是一样的。

<%
 var  a = 1 ;
%>
${a==1?"ok":''}
${a==1?"ok"}

2.15. 循环语句

Beetl支持丰富的循环方式,如for-in,for(exp;exp;exp),以及while循环,以及循环控制语句break;continue; 另外,如果没有进入for循环体,还可以执行elsefor指定的语句。

2.15.1. for-in

for-in循环支持遍历集合对象,对于List和数组来说以及Iterator,对象就是集合对象,对于Map来说,对象就是Map.entry,如下俩个例子

<%
for(user in userList){
        print(userLP.index);
        print(user.name);
}
%>

第三行代码userLP是Beetl隐含定义的变量,能在循环体内使用。其命名规范是item名称后加上LP,他提供了当前循环的信息,如

  • **userLP.index **当前的索引,从1开始
  • **userLP.size **集合的长度
  • userLP.first 是否是第一个
  • userLP.last 是否是最后一个
  • userLP.even 索引是否是偶数
  • userLP.odd 索引是否是奇数

如何记住后缀是LP,有俩个诀窍,英语棒的是Loop的缩写,拼音好的是老婆的拼音缩写,这可以让程序员每次写到这的时候都会想想老婆(不管有没有,哈哈)

如下是Map使用例子

<%
for(entry in map){
        var key = entry.key;
        var value = entry.value;
        print(value.name);
}
%>
2.15.2. for(exp;exp;exp)

对于渲染逻辑更为常见的是经典的for循环语句,如下例子

<%
var a = [1,2,3];
for(var i=0;i<a.~size;i++){
        print(a[i]);
}
%>
2.15.3. while

对于渲染逻辑同样常见的有的while循环语句,如下例子

<%
var i = 0;
while(i<5){
        print(i);
        i++;
}
%>
2.15.4. elsefor

不同于通常程序语言,如果没有进入循环体,则不需额外的处理,模板渲染逻辑更常见情况是如果没有进入循环体,还需要做点什么,因此,对于for循环来说,还有elsefor 用来表达如果循环体没有进入,则执行elsefor 后的语句

<%
var list = [];
for(item in list){

}elsefor{
        print("未有记录");
}
%>

2.16. 条件语句

2.16.1. if else

同js一样,支持if else,如下例子

<%
var a =true;
var b = 1;
if(a&&b==1){

}else if(a){

}else{

}
%>
2.16.2. switch-case

同js一样,支持switch-case,如下例子

<%
var b = 1;
switch(b){
        case 0:
                print("it's 0");
                break;
        case 1:
                print("it's 1");
                break;
        default:
                print("error");
}
%>

switch变量可以支持任何类型,而不像js那样只能是整形

2.16.3. select-case

select-case 是switch case的增强版。他允许case 里有逻辑表达式,同时,也不需要每个case都break一下,默认遇到合乎条件的case执行后就退出。

<%
var b = 1;
select(b){
        case 0,1:
                print("it's small int");
        case 2,3:
                print("it's big int");
        default:
                print("error");
}
%>

select 后也不需要一个变量,这样case 后的逻辑表达式将决定执行哪个case.其格式是

<%
select {
        case boolExp,orBoolExp2:
                doSomething();
}
%>
<%
var b = 1;
select{
        case b<1,b>10:
                print("it's out of range");
                break;
        case b==1:
                print("it's 1");
                break;
        default:
                print("error");
}
%>

2.17. try-catch

通常模板渲染逻辑很少用到try-catch 但考虑到渲染逻辑复杂性,以及模板也有不可控的地方,所以提供try catch,在渲染失败的时候仍然能保证输出正常

<%
try{
        callOtherSystemView()
}catch(error){
        print("暂时无数据");
}
%>

error代表了一个异常,你可以通过error.message 来获取可能的错误信息

也可以省略catch部分,这样出现异常,不做任何操作

2.18. 虚拟属性

虚拟属性也是对象的属性,是虚拟的,非模型对象的真实属性,这样的好处是当模板需要额外的用于显示的属性的时候但又不想更改模型,便可以采用这种办法 如beetl内置的虚拟属性.~size 针对了数组以及集合类型。

${user.gender}
${user.~genderShowName}

~genderShowName 是虚拟属性,其内部实现根据boolean变量gender来显示性别

如何完成虚拟属性,请参考高级用法

2.19. 函数调用

Beetl内置了少量实用函数,可以在Beetl任何地方调用。如下例子是调用date 函数,不传参数情况下,返回当前日期

<%
var date = date();
var len = strutil.length("cbd");
println("len="+len);
%>

注意函数名支持namespace方式,因此代码第3行调用的函数是strutil.length

定义beetl的方法非常容易,有三种方法

  • 实现Function类的call方法,并添加到配置文件里,或者显示的通过代码注册registerFunction(name,yourFunction)
  • 可以直接调用registerFunctionPackage(namespace,yourJavaObject),这时候yourJavaObject里的所有public方法都将注册为Beetl方法,方法名是namespace+"."+方法名
  • 可以直接写模板文件并且以html作为后缀,放到root/functions目录下,这样此模板文件自动注册为一个函数,其函数名是该模板文件名。

详情请参考高级用法

Beetl内置函数请参考附录,以下列出了常用的函数

  • 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();

2.20. 安全输出

安全输出是任何一个模板引擎必须重视的问题,否则,将极大困扰模板开发者。Beetl中,如果要输出的模板变量为null,则beetl将不做输出,这点不同于JSP,JSP输出null,也不同于Freemarker,如果没有用!,它会报错.

模板中还有俩种情况会导致模板输出异常

  • 有时候模板变量并不存在(譬如子模板里)
  • 模板变量为null,但输出的是此变量的一个属性,如${user.wife.name}

针对前俩种种情况,可以在变量引用后加上!以提醒beetl这是一个安全输出的变量。

如${user.wife.name! },即使user不存在,或者user为null,或者user.wife为null,或者user.wife.name为null beetl都不将输出

可以在!后增加一个常量(字符串,数字类型等),或者另外一个变量,方法,本地调用,作为默认输出,譬如:

${user.wife.name!”单身”},如果user为null,或者user.wife为null,或者user.wife.name为null,输出”单身”

譬如

${user.birthday!@System.constants.DefaultBir}, 表示如果user为null,或者user. birthday为null,输出System.constants.DefaultBir

还有一种情况很少发生,但也有可能,输出模板变量发生的任何异常,如变量内部抛出的一个异常

这需要使用格式${!(变量)},这样,在变量引用发生任何异常情况下,都不作输出,譬如

${!(user.name)},,beetl将会调用user.getName()方法,如果发生异常,beetl将会忽略此异常,继续渲染

值得注意的是,在变量后加上!不仅仅可以应用于占位符输出(但主要是应用于占位符输出),也可以用于表达式中,如:

<%
var k = user.name!'N/A'+user.age!;
%>
<%
${k}
%>

如果user为null,则k值将为N/A

在有些模板里,可能整个模板都需要安全输出,也可能模板的部分需要安全输出,使用者不必为每一个表达式使用!,可以使用beetl的安全指示符号来完成安全输出 如:

<%
DIRECTIVE SAFE_OUTPUT_OPEN;
%>
${user.wife.name}
模板其他内容,均能安全输出……
<%
//关闭安全输出。
DIRECTIVE SAFE_OUTPUT_CLOSE;
%>

Beetl不建议每一个页面都使用DIRECTIVE SAFE_OUTPUT_OPEN,这样,如果如果真有不期望的错误,不容易及时发现,其次,安全输出意味着beetl会有额外的代码检测值是否存在或者是否为null,性能会略差点。所以建议及时关闭安全输出(这不是必须的,但页面所有地方是安全输出,可能不容易发现错误)

在for-in 循环中 ,也可以为集合变量增加安全输出指示符号,这样,如果集合变量为null,也可以不进入循环体,如:

<%
var list = null;
for(item in list!){

}elsefor{
        print("no data");
}
%>
2.20.1. 变量是否存在
<%
if(has(flag)){
        print("flag变量存在,可以访问")
}
%>

如果需要判断变量是否存在,如果存在,还有其他判断条件,通常都这么写

<%
if(has(flag)&&flag==0){
        //code
}
%>

如果flag存在,而且值是0,都将执行if语句

但是,有更为简便的方法是直接用安全输出,如

<%
if(flag!0==0){
        //code
}
%>

flag!0 取值是这样的,如果flag不存在,则为0,如果存在,则取值flag的值,类似三元表达式 if((has(flag)?flag:0)==0)

2.20.2. 安全输出表达式

安全输出表达式可以包括

  • 字符串常量,如 ${user.count!"无结果"}
  • boolean常量 ${user.count!false}
  • 数字常量,仅限于正数,因为如果是负数,则类似减号,容易误用,因此,如果需要表示负数,请用括号,如${user.count!(-1)}
  • class直接调用,如${user.count!@User.DEFAULT_NUM}
  • 方法调用,如 ${user.count!getDefault() }
  • 属性引用,如 ${user.count!user.maxCount }
  • 任何表达式,需要用括号

2.21. 格式化

几乎所有的模板语言都支持格式化,Beetl也不列外,如下例子Beetl提供的内置日期格式

<% var date = date(); %>
Today is ${date,dateFormat="yyyy-MM-dd"}.
Today is ${date,dateFormat}
salary is ${salary,numberFormat="##.##"}

格式化函数只需要一个字符串作为参数放在=号后面,如果没有为格式化函数输入参数,则使用默认值,dateFormat格式化函数默认值是local

Beetl也允许为指定的java class设定格式化函数,譬如已经内置了对java.util.Date,java.sql.Date 设置了了格式化函数,因此上面的例子可以简化为

${date,“yyyy-MM-dd”}

Beetl针对日期和数字类型提供的默认的格式化函数,在org/beetl/core/beetl-default.properties里,注册了

##内置的格式化函数
FT.dateFormat =  org.beetl.ext.format.DateFormat
FT.numberFormat =  org.beetl.ext.format.NumberFormat
##内置的默认格式化函数
FTC.java.util.Date = org.beetl.ext.format.DateFormat
FTC.java.sql.Date = org.beetl.ext.format.DateFormat
FTC.java.sql.Time = org.beetl.ext.format.DateFormat
FTC.java.sql.Timestamp = org.beetl.ext.format.DateFormat
FTC.java.lang.Short = org.beetl.ext.format.NumberFormat
FTC.java.lang.Long = org.beetl.ext.format.NumberFormat
FTC.java.lang.Integer = org.beetl.ext.format.NumberFormat
FTC.java.lang.Float = org.beetl.ext.format.NumberFormat
FTC.java.lang.Double = org.beetl.ext.format.NumberFormat
FTC.java.math.BigInteger = org.beetl.ext.format.NumberFormat
FTC.java.math.BigDecimal = org.beetl.ext.format.NumberFormat
FTC.java.util.concurrent.atomic.AtomicLong = org.beetl.ext.format.NumberFormat
FTC.java.util.concurrent.atomic.AtomicInteger = org.beetl.ext.format.NumberFormat

2.22. 标签函数

所谓标签函数,即允许处理模板文件里的一块内容,功能等于同jsp tag。如Beetl内置的layout标签

index.html

<%
layout("/inc/layout.html",{title:'主题'}){
%>
Hello,this is main part
<% } %>

layout.html

title is ${title}
body content ${layoutContent}
footer

第1行变量title来自于layout标签函数的参数

第2行layoutContent 是layout标签体{}渲染后的结果

关于layout标签,参考高级主题布局

Beetl内置了另外一个标签是include,允许 include 另外一个模板文件

<%
include("/inc/header.html"){}
%>

在标签中,{} 内容将依据标签的实现而执行,layout标签将执行{}中的内容,而include标签则忽略标签体内容。

关于如何实现标签函数,请参考高级主题,如下是一个简单的的标签函数:

public class CompressTag extends Tag{
        @Override
        public void render(){
                BodyContent  content = getBodyContent();
                String content = content.getBody();
                String zip = compress(conent);
                ctx.byteWriter.write(zip);
        }
}

2.23. HTML标签

Beetl 也支持HTML tag形式的标签, 区分beetl的html tag 与 标准html tag。如设定HTML_TAG_FLAG=#,则如下html tag将被beetl解析

<#footer style=”simple”/>
<#richeditor id=”rid” path="${ctxPath}/upload" name=”rname”  maxlength=”${maxlength}”> ${html} …其他模板内容   </#richdeitor>
<#html:input  id=’aaaa’ />

如对于标签footer,Beetl默认会寻找WebRoot/htmltag/footer.tag(可以通过配置文件修改路径和后缀) ,内容如下:

<% if(style==’simple’){ %>
 请联系我 ${session.user.name}
<% }else{ %>
请联系我 ${session.user.name},phone:${session.user.phone}
<% } %>

如下还包含了自定义html标签一些一些规则

  • 可以在自定义标签里引用标签体的内容,标签体可以是普通文本,beetl模板,以及嵌套的自定义标签等。如上<#richeditor 标签体里,可用“tagBody”来引用
  • HTML自定义标签 的属性值均为字符串 如<#input value=”123” />,在input.tag文件里 变量value的类型是字符串
  • 可以在属性标签里引用beetl变量,如<#input value=”${user.age}” />,此时在input.tag里,value的类型取决于user.age
  • 在属性里引用beetl变量,不支持格式化,如<#input value=”${user.date,‘yyyy-MM-dd’ }” />,如果需要格式化,需要在input.tag文件里自行格式化
  • 在标签属性里传json变量需要谨慎,因为json包含了"}",容易与占位符混合导致解析出错,因此得使用""符号,如<#input value=”${ {age:25} }” />
  • html tag 属性名将作为 其对应模板的变量名。
  • 默认机制下,全局变量都将传给html tag对应的模板文件,这个跟include一样。当然,这机制也可以改变,对于标签来说,通常是作为一个组件存在,也不一定需要完全传送所有全局变量,而只传送(request,session,这样变量),因此需要重新继承org.beetl.ext.tag.HTMLTagSupportWrapper.并重载callHtmlTag方法。并注册为htmltag标签。具体请参考https://github.com/javamonkey/beetl2.0/blob/master/beetl-core/src/test/java/org/beetl/core/tag/HtmlTagTest.java

如果采用模板来写html标签功能不够强大,beetl支持写标签函数(参考上一节)来实现html标签,标签函数args[0]表示标签名,这通常没有什么用处,args[1] 则是标签的属性,参数是个map,key是html tag的属性,value是其属性值,如下用java完成的html 标签用于输出属性值

public class SimpleHtmlTag extends Tag{
        @Override
        public void render(){
                String tagName = (String) this.args[0];
                Map attrs = (Map) args[1];
                String value = (String) attrs.get("attr");
                try{
                        this.ctx.byteWriter.writeString(value);
                }catch (IOException e){

                }
        }
}

如果注册gt.registerTag("simpleTag", SimpleHtmlTag.class); 则如下模板输出了attr属性值abc

<#simpleTag attr="abc"></#simpleTag>

HTML_TAG_FLAG默认为#用来区别是否是beetl的html tag,你也可以设置成其他符号,比如 "my:",这样,<my:table></my:table> 其实是一个指向table.tag的标签实现

2.24. 绑定变量的HTML标签

对于html标签(参考上一节),Beetl还 支持将标签实现类(java代码)里的对象作为临时变量,被标签体引用。此时需要实现GeneralVarTagBinding (此类是Tag的子类) 该类提供另外3个个方法 - void binds(Object… array) 子类在render方法里调用此类以实现变量绑定,绑定顺序同在模板中声明的顺序 - void bind(String name, Object value) 子类在render方法里调用此类以实现变量绑定,name是模板中声明的变量名,用此方法绑定不如binds更灵活,不再推荐 - Object getAttributeValue 获得标签的属性 - Map getAttributes 获得标签的所有属性

public class TagSample extends GeneralVarTagBinding{
        @Override
        public void render(){
                int limit = Integer.parseInt((String) this.getAttributeValue("limit"));
                for (int i = 0; i < limit; i++){
                        this.binds(i)
                        this.doBodyRender();
                }
        }
}
//在某处注册一下标签TagSample
//gt.registerTag("tag", TagSample.class);

如上例子,render方法将循环渲染标签体limit次,且每次都将value赋值为i。我们再看看模板如何写的

<#tag limit="3";value>
        ${value}
</#tag>

类似于常规html标签,需要在标签的最后的属性定义后面加上分号 ";" 此分号表示这个是一个需要在标签运行时需要绑定变量的标签。后跟上要绑定的变量列表,如上例只绑定了一个value变量,如果需要绑定多个变量,则用逗号分开,如var1,var2 上。如果后面没有变量列表,只有分号,则默认绑定到标签名同名的变量上. 如果标签有namesapce,则默认绑定订的变量名不包含namespace

注意,由于标签使用因为太长可能换行或者是文本格式化导致换行,目前beetl只允许在属性之间换行,否则,将报标签解析错误。

默认情况下,如果标签属性出现了var(可以通过配置文件改成其他属性名),也认为是绑定变量的标签,如上面的例子也可以这么写

<#tag limit="3" var="value">
        ${value}
</#tag>

var属性的值可以是个以逗号分开的变量名列表,如var="total,customer,index"

2.25. 直接调用java方法和属性

可以通过符号@来表明后面表达式调用是java风格,可以调用对象的方法,属性

${@user.getMaxFriend(“lucy”)}
${@user.maxFriend[0].getName()}
${@com.xxxx.constants.Order.getMaxNum()}
${@com.xxxx.User$Gender.MAN}
<%
var max = @com.xxxx.constants.Order.MAX_NUM;
var c =1;
var d = @user.getAge(c);
%>

可以调用instance的public方法和属性,也可以调用静态类的属性和方法 ,需要加一个 @指示此调用是直接调用class,其后的表达式是java风格的。

  • GroupTemplate可以配置为不允许直接调用Class,具体请参考配置文件.
  • 也可以通过安全管理器配置到底哪些类Beetl不允许调用,具体请参考高级用法。默认情况,java.lang.Runtime,和 java.lang.Process不允许在模板里调用。你自己的安全管理器也可以配置为不能直接访问DAO类(避免了以前jsp可以访问任意代码带来的危害)
  • 请按照java规范写类名和方法名,属性名。这样便于beetl识别到底调用的是哪个类,哪个方法。否则会抛出错误
  • 可以省略包名,只用类名。beetl将搜索包路径找到合适的类(需要设置配置“IMPORT_PACKAGE=包名.;包名.”,包名后需要跟一个“.”, 或者调用Configuration.addPkg)方法具体请参考附件配置文件说明
  • 内部类(包括枚举)访问同java一样,如User类有个内部枚举类Gender,访问是User$Gender
  • 表达式是java风格,但参数仍然是beetl表达式,比如 @user.sayHello(user.name).这里user.sayHello是java调用,user.name 仍然是beetl表达式

2.26. 严格MVC控制

如果在配置文件中设置了严格MVC,则以下语法将不在模板文件里允许,否则将报出STRICK_MVC 错误

  • 定义变量,为变量赋值,如var a = 12是非法的
  • 算术表达式 如${user.age+12}是非法的
  • 除了只允许布尔以外,不允许逻辑表达式和方法调用 如if(user.gender==1)是非法的
  • 方法调用,如${subString(string,1)}是非法的
  • Class方法和属性调用,如${@user.getName()}是非法的
  • 严格的MVC,非常有助于逻辑与视图的分离,特别当逻辑与视图是由俩个团队来完成的。如果你嗜好严格MVC,可以调用groupTemplate.enableStrict()

通过重载AntlrProgramBuilder,可以按照自己的方法控制到底哪些语法是不允许在模板引擎中出现的,但这已经超出了Beetl模板的基础使用

2.27. 指令

指令格式为: DIRECTIVE 指令名 指令参数(可选) Beetl目前支持安全输出指令,分别是

  • DIRECTIVE SAFE_OUTPUT_OPEN ; 打开安全输出功能,此指令后的所有表达式都具有安全输出功能,
  • DIRECTIVE SAFE_OUTPUT_CLOSE ; 关闭安全输出功能。详情参考安全输出
  • DIRECTIVE DYNAMIC varName1,varName2 …指示后面的变量是动态类型,Beetl应该考虑为Object. 也可以省略后面的变量名,则表示模板里所有变量都是Object
<% DIRECTIVE DYNAMIC idList;
for(value in idList) .....

DYNAMIC 通常用在组件模板里,因为组件模板可以接收任何类型的对象。如列表控件,可以接收任何含有id和 value属性的对象。

  1. 注意 DYNAMIC 后的变量名也允许用引号,这主要是兼容Beetl1.x版本
  2. Beetl1.x 指令都是大写,当前版本也允许小写,如 directive dynamic idList

2.28. 类型声明

Beetl 本质上还是强类型的模板引擎,即模板每个变量类型是特定的,在模板运行过程中,beetl 会根据全局变量自动推测出模板中各种变量和表达式类型。 也可以通过类型申明来说明beetl全局变量的类型,如下格式

<%
/**
*@type (List<User> idList,User user)
*/
for(value in idList) .....

类型申明必须放到多行注释里,格式是@type( … ),里面的申明类似java方法的参数申明。正如你看到的类型申明是在注释里,也就表明了这在Beetl模板引擎中不是必须的,或者你只需要申明一部分即可,之所以提供可选的类型说明,是因为

  • 提高一点性能
  • 最重要的是,提高了模板的可维护性。可以让模板维护者知道变量类型,也可以让未来的ide插件根据类型声明来提供属性提示,重构等高级功能

需要注意的是,如果在类型声明里提供的是类名,而不是类全路径,这样必须在配置文件里申明类的搜索路径((需要设置配置IMPORT_PACKAGE=包名.;包名.,或者调用Configuration.addPkg)),默认的搜索路径有java.util. 和 java.lang.

类型声明本用于eclipse插件用来提示,但eclipse插件暂时没有时间去做,所以类型申明现在不推荐使用

2.29. 错误处理

Beetl能较为详细的显示错误原因,包括错误行数,错误符号,错误内容附近的模板内容,以及错误原因,如果有异常,还包括异常和异常信息。 默认情况下,仅仅在控制台显示,如下代码:

<%
var a = 1;
var b = a/0;
%>

运行此模板后,错误提示如下

>>DIV_ZERO_ERROR:0 位于3行 资源:/org/beetl/sample/s0125/error1.txt
1|<%
2|var a = 1;
3|var b = a/0;
4| %>
<%
var a = 1;
var b = a
var c = a+2;
%>

运行此模板后

>>缺少符号(PARSER_MISS_ERROR):缺少输入 ';' 在 'var' 位于4行 资源:/org/beetl/sample/s0125/error2.txt
1|<%
2|var a = 1;
3|var b = a
4|var c = a+2;
5| %>
  1. 默认的错误处理器仅仅像后台打印错误,并没有抛出异常,如果需要在render错误时候抛出异常到控制层,则可以使用org.beetl.core.ReThrowConsoleErrorHandler。不仅打印异常,还抛出BeetlException
  2. 可以自定义异常处理器,比如把错误输出到 作为渲染结果一部分输出,或者输出更美观的html内容等,具体参考高级用法
  3. 可以在配置文件不设置异常,这样Beetl引擎将不处理异常,用户可以在外部来处理(可以在外部调用ErrorHandler子类来显示异常)

2.30. Beetl小工具

BeetlKit 提供了一些便利的方法让你立刻能使用Beetl模板引擎。提供了如下方法

  • public static String render(String template, Map<String, Object> paras) 渲染模板,使用paras参数,渲染结果作为字符串返回
  • public static void renderTo(String template, Writer writer, Map<String, Object> paras) 渲染模板,使用paras参数
  • public static void execute(String script, Map<String, Object> paras) 执行某个脚本
  • public static Map execute(String script, Map<String, Object> paras, String[] locals) 执行某个脚本,将locals指定的变量名和模板执行后相应值放入到返回的Map里
  • public static Map executeAndReturnRootScopeVars(String script) 执行某个脚本,返回所有顶级scope的所有变量和值
  • public static String testTemplate(String template, String initValue) 渲染模板template,其变量来源于intValue脚本运行的结果,其所有顶级Scope的变量都将作为template的变量
String template = "var a=1,c=2+1;";
Map result = executeAndReturnRootScopeVars(template);
System.out.println(result);
//输出结果是{c=3, a=1}

BeetlKit 不要用于线上系统。仅仅作为体验Beetl功能而提供的,如果需要在线上使用这些功能,请参考该类源码自行扩展

2.31. 琐碎功能

  • 对齐:我发现别的模板语言要是做到对齐,非常困难,使用Beetl你完全不用担心,比如velocty,stringtemlate,freemarker例子都出现了不对齐的情况,影响了美观,Beetl完全无需担心输出对齐
  • Escape:可以使用\ 做escape 符号,如$monkey$ 将作为一个普通的文本,输出为$monkey$.再如为了在后加上美元符号(占位符恰好又是美元符号)可以用这俩种方式hello,it’s $money$$, 或者Hello,it’s $money+"$"$ 。如果要输出\符号本生,则需要用俩个\,这点与javascript,java 语义一致.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 4. Web集成 4.1. Web提供的全局变量 Web集成模块向模板提供web标准的变量,做如下说明 reque...
    西漠阅读 4,374评论 0 0
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,293评论 18 399
  • 3. 高级功能 3.1. 配置GroupTemplate Beetl建议通过配置文件配置GroupTemplate...
    西漠阅读 2,688评论 0 0
  • 这几天大风降温,这世纪寒流真不是一般般,冷的我在寒风中像个小孩子似的嚎嚎大哭,是我自己对生活不好,也不能怪生活。...
    寒之忧伤阅读 288评论 2 0