3. Beetl
写在开头,Beetl是由《Spring Boot 2精髓》作者所开发并维护的后端模板引擎,主要用于渲染视图模板。
关于模板引擎,博主了解过的主要是JSP 和 FreeMarker,视图渲染技术的了解并不多。
这里谈一下自己对于Web开发的理解:基于现在的Web开发环境,前后端分离开发的思想,相对后端来讲,很多时候是面向接口编程,拿当下火热的前端渐进式组件框架Vue来讲,于后端的交互无异于请求数据并返回,前端框架进行数据解析和视图渲染,后端进行接口数据的获取以及返回。
借此阅读技术书籍以及结合案例Demo的机会,来比较系统的了解一下这个后端模板引擎:Beetl
关于模板介绍,可以移步:http://ibeetl.com/ ,这里就不赘述了,直接上手实操。
-
依赖引入
在Spring Boot中,beetl-framework-starter 将自动配置以 btl 结尾的所有视图,将自动使用 Beetl 渲染响应的 resources/templates 目录下的视图文件<dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl-framework-starter</artifactId> <version>1.1.57.RELEASE</version> </dependency>
-
设置定界符和占位符
记得当初使用JSP、ftl在页面上写脚本的时候,定界符和占位符的概念就了解过了。
有图有真相:JavaWeb时期用过的JSP
SSM时期用过的FreeMarker
默认配置:
<% %> 作为定界符,${ }作为占位符可以通过配置文件来设置定界符和占位符,需要在 resources 目录下创建 beetl.properties 文件
案例中给出了一种配置方式:占位符使用:’${’ 和 ‘}’,定界符使用:’@’ 和 ‘回车换行’。DELIMITER_PLACEHOLDER_START=${ DELIMITER_PLACEHOLDER_END=} DELIMITER_STATEMENT_START=@ DELIMITER_STATEMENT_END=
-
配置Beetl
Beetl为了提高渲染性能,会在模板渲染之后对语法解析结果进行缓存,每次渲染前都会对模板文件进行更新监测,因此这个地方会存在一次I/O操作,线上系统可以取消这个更新监测。application.properties文件中添加:beetl-beetlsql.dev = false
关于Web项目中 target目录 没有同步更新最新目录文件以及资源的问题:IDEA不像Eclipse会自动将新保存的文件或目录及其他资源更新到target目录中,必须在pom.xml中设置
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.*</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.*</include> </includes> </resource> </resources> </build>
接下来,maven clean、build就可以了,这个问题和mapper.xml文件无法加载的问题一致,原因都是在resource文件夹不被加入build行列。
-
GroupTemplate
Beetl的核心是GroupTemplate,是一个重量级对象,实际使用的时候建议使用单例模式创建,创建GroupTemplate需要两个参数,一个是模板资源加载器,一个是配置类。模板资源加载器Beetl内置了6种,分别是:
模板资源加载器 说明 StringTemplateResourceLoader 字符串模板加载器,用于加载字符串模板,如本例所示 FileResourceLoader 文件模板加载器,需要一个根目录作为参数构造,传入getTemplate方法的String是模板文件相对于Root目录的相对路径 ClasspathResourceLoader 文件模板加载器,模板文件位于Classpath里 WebAppResourceLoader 用于webapp集成,假定模板根目录就是WebRoot目录,参考web集成章 MapResourceLoader 可以动态存入模板 CompositeResourceLoader 混合使用多种加载方式 GroupTemplate创建过程:
// 模板资源加载器 StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader(); // 配置类 Configuration cfg = Configuration.defaultConfiguration(); // 单例模式创建 GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
配置类:通过自动注入的方式注入GroupTemplate,@PostConstruct作用于Config方法上,会在Spring启动阶段调用此方法,完成对GroupTemplate的拓展。
@Configuration public class BeetlExtConfig { @Autowired GroupTemplate groupTemplate; @Autowired ApplicationContext applicationContext; @PostConstruct public void config() { } }
以下内容为Beetl的基础语法相关内容,该部分内容会进行提纲式的笔记整理,不会详细讲述语法相关内容。
-
变量
- 全局变量:在模板或者子模板中使用
- request内置对象通过attribute在模板中均可直接通过name来引用。
- Session提供了Session会话,这里指的是HTTPSession。
- parameter,读取用户提交参数。
- ctxPath,Web应用中的ContextPath。在Spring Boot中,应用默认是 “/”。
- servlet,WebVariable的实例,包含 HTTPSession、HTTPServletRequest、HTTPServletResponse三个属性。模板中可以通过 request、response、session 来引用。
- 所有groupTemplate的共享变量
- 局部变量:只能在当前模板中使用
- 变量类型同JavaScript类型一致,在Beetl中,任何与高精度数据进行算数运算后的结果会转换为BIgDecimal类型,对应Java中的BigDecimal类型。
- 共享变量
-
类似全局变量,可以在任何模板中使用,需要通过groupTemplate的API进行添加。
定义:在注入GroupTemplate对象的配置类中,config方法中进行设置: groupTemplate.getSharedVars().put("key", value); 使用:在任意末班中,通过占位符进行使用: ${key}
-
- 模板变量
-
定义:变量的内容是模板对应的输出
@var a = 1 ; @var template = { <span>${a}</span〉 @} 输出:${template}
template是个变量,内容位于${ }中,内容是1。
模板变量可以用在后面的任何地方,Beetl使用模板变量来完成继承布局方式。
-
- 全局变量:在模板或者子模板中使用
-
表达式
- 算数表达式
-
支持 +、-、*、/、% 等表达式,变量类型与相应的Java类型一致。
-
需要用到高精度数代替Double时,需要在数字后面添加 “h”,对应Java中BigDecimal类型。
例如:@var a = 123.25785236h;
-
为保证浮点计算准确,含有高精度数表达式的计算过程全部按照高精度进行计算
-
- 逻辑表达式
-
支持 >、<、==、!=、>=、<=、!、&&、||、?等条件表达式
-
三元表达式只考虑true情况时,可以进行简化
例如: $var a = 1; ${a == 1? "bingo" : ""} ${a == 1? "bingo"} 上述两行代码效果一致,在简化的三元表达式中,false情况时,自动赋值为null。 Beetl占位符对null值不做输出。
-
- 算数表达式
-
控制语句
- 循环语句
- for…in [elsefor]、for(exp; exp; exp)、while(exp)
-
for…in 循环中可以得到循环的上下文信息,会自动创建一个变量名+LP后缀的变量,提供循环的上下文信息
例如: @for(user in userList!){ <span>${userLP.index}</span> @}
上面的例子中在表达式:user in userList 后添加了 “!”,来进行安全输出。即若user不存在,或者为空,不要报错,不进入循环体。此外上下文对象userLP还有其他相关信息:
-
- for…in [elsefor]、for(exp; exp; exp)、while(exp)
属性/方法 说明 userLP.index 当前索引值,从1开始 userLP.size 集合长度 userLP.first 是否是第一个 userLP.last 是否是最后一个 userLP.even 索引是否是偶数 userLP.odd 索引是奇数 - 条件语句
- if…else [else if]
- 加强版switch case:select-case
-
允许case中使用逻辑表达式
-
不需要每个case都break,默认遇到符合条件的case执行后退出
<% var b = 1; select { case b < 1, b > 10: print("out of range"); default: print("error") } %>
-
- try…catch
-
捕获模板异常
<% try{ callOtherSystemView(); } catch (error) { print(err.message); } %>
-
- 循环语句
-
函数调用
常用函数 说明 举例 print/pringln 输出对象,若对象为空,则不输出 has 判断是否具有这个全局变量 if(has(userList)){…} isEmpty 判断变量或者表达式是否为空 debug 在控制台打印变量或者表达式,附带所在文件模板路径信息 date 日期函数,获得当前日期 trim 截取一个日期或者数字类型并返回字符串 trim(126.18, 1) -> 126.18
trim(date(),‘yyyy’) -> 2018parseInt 将字符串或者number转换为对应类型 parseLong、parseDouble global 返回全局变量值,参数是字符串 var user = global(“user_” + i) cookie 返回指定的cookie对象 var userCookie = cookie(“user”) strutil.* 字符串系列函数 详情参考Beetl使用手册 array.* 集合相关函数 shiro.* 安全框架shiro的相关方法封装 shiroExt类中有说明,并非内置函数 spring.* Spring框架中可以使用的函数 spel表达式 reg.* 正则表达式相关函数 这里说一下用到过的Shiro.hasPermission()方法吧,首先Shiro安全框架是集成进项目中来的,并配置了ShiroConfig配置类。权限控制的方式上采用基于URL的形式进行控制,在视图层,使用Beetl提供的ShiroExt中的hasPermission方法,对模板中的URL字符串进行权限认证,ShiroExt在这一过程中只是做了封装操作,获取到权限URL参数、和当前Subject,并将参数交给Shiro框架进行处理。
/** * 验证当前用户是否拥有指定权限,使用时与lacksPermission 搭配使用 * * @param permission 权限名 * @return 拥有权限:true,否则false */ public boolean hasPermission(String permission) { return getSubject() != null && permission != null && permission.length() > 0 && getSubject().isPermitted(permission); } /** * 获取当前 Subject * * @return Subject */ protected static Subject getSubject() { return SecurityUtils.getSubject(); } // isPermitted是接口Subject下的一个实现方法 boolean isPermitted(String permission); // 视图层使用 @if(shiro.hasPermission("/cta/add")){ <#button name="添加" icon="fa-plus" clickFun="Cta.openAddCta()"/> @}
-
格式化函数
允许在占位符输出的时候指定格式化函数来格式化输出
格式化格式:${exp, formatName=“可选参数”}-
日期格式化(同SimpleDateFormat)
${date(), dataFormat = "yyyy-MM-dd HH:mm"}
-
数字格式化(同NumberFormat)
${0.345, numberFormat = "#.%"} 输出:34.5%
为什么会这样?详情请移步:https://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html
-
-
以Java方式进行调用
可以在模板中以Java方式调用表达式,使用时必须在表达式前用"@"符号标识-
定界符中使用
<% var size = @a.size(); %>
-
占位符中使用
${ @user.getUserName() }
-
需要注意的是:
-
对于直接Java调用,GroupTemplate是可以配置进制此功能的。
-
可以通过安全管理器配置Beetl不允许调用那些类。默认java.lang.Runtime 和 java.lang.Process不允许被调用。
-
按照Java规范书写类名、方法名以及属性名
-
内部类访问机制:outerClass$innerClass
-
表达式是Java风格,参数依然是Beetl表达式。
${@user.sayHello(user.name)} user.sayHello 是Java调用 user.name 依然是Beetl表达式
-
-
-
标签函数
-
功能等同于 jsp tag
-
layout:布局标签函数
@layout("/inc/layout.html", {title:'主题'}){ <p>Hello Beetl</p> @}
layout标签会把标签体{ }部分内容渲染出来之后,传给layout指定的模板页面,默认接收变量名为layoutContent,同时携带title变量进行传递。
layout.html <title>${title}</title> <div> ${layoutContent} </div>
-
include:包含标签函数
@include("/inc/header.html", {title:"page header"}){...} <div> // main content </div> @include("/inc/footer.html"){}
include标签第一个参数是公共模板的路径,其后可以接受一个map参数,map中的,每一项值都会传递给子模板作为子模板的全局变量。
-
-
安全输出
Beetl在变量表达式后面使用符号 “!” 来提醒Beetl此变量可能不存在,表达式将返回 “!” 后的表达式值,若没有表达式,则返回null。@ var user = null; ${user.name!"无此人姓名信息"}
当user为null时,或者user.name为null时,返回 “无此人姓名信息”