Tio Boot DocsTio Boot Docs
Home
  • java-db
  • api-table
  • Enjoy
  • Tio Boot Admin
  • ai_agent
  • translator
  • knowlege_base
  • ai-search
  • 案例
Abount
  • Github
  • Gitee
Home
  • java-db
  • api-table
  • Enjoy
  • Tio Boot Admin
  • ai_agent
  • translator
  • knowlege_base
  • ai-search
  • 案例
Abount
  • Github
  • Gitee
  • 01_tio-boot 简介

    • tio-boot:新一代高性能 Java Web 开发框架
    • tio-boot 入门示例
    • Tio-Boot 配置 : 现代化的配置方案
    • tio-boot 整合 Logback
    • tio-boot 整合 hotswap-classloader 实现热加载
    • 自行编译 tio-boot
    • 最新版本
    • 开发规范
  • 02_部署

    • 使用 Maven Profile 实现分环境打包 tio-boot 项目
    • Maven 项目配置详解:依赖与 Profiles 配置
    • tio-boot 打包成 FastJar
    • 使用 GraalVM 构建 tio-boot Native 程序
    • 使用 Docker 部署 tio-boot
    • 部署到 Fly.io
    • 部署到 AWS Lambda
    • 到阿里云云函数
    • 使用 Deploy 工具部署
    • 胖包与瘦包的打包与部署
    • 使用 Jenkins 部署 Tio-Boot 项目
    • 使用 Nginx 反向代理 Tio-Boot
    • 使用 Supervisor 管理 Java 应用
  • 03_配置

    • 配置参数
    • 服务器监听器
    • 内置缓存系统 AbsCache
    • 使用 Redis 作为内部 Cache
    • 静态文件处理器
    • 基于域名的静态资源隔离
    • DecodeExceptionHandler
  • 04_原理

    • 生命周期
    • 请求处理流程
    • 重要的类
  • 05_json

    • Json
    • 接受 JSON 和响应 JSON
    • 响应实体类
  • 06_web

    • 概述
    • 文件上传
    • 接收请求参数
    • 接收日期参数
    • 接收数组参数
    • 返回字符串
    • 返回文本数据
    • 返回网页
    • 请求和响应字节
    • 文件下载
    • 返回视频文件并支持断点续传
    • http Session
    • Cookie
    • HttpRequest
    • HttpResponse
    • Resps
    • RespBodyVo
    • /zh/06_web/19.html
    • 全局异常处理器
    • 异步
    • 动态 返回 CSS 实现
    • 返回图片
    • Transfer-Encoding: chunked 实时音频播放
    • Server-Sent Events (SSE)
    • 接口访问统计
    • 接口请求和响应数据记录
    • 自定义 Handler 转发请求
    • 使用 HttpForwardHandler 转发所有请求
    • 跨域
    • 添加 Controller
    • 常用工具类
    • HTTP Basic 认证
    • WebJars
    • JProtobuf
  • 07_validate

    • 数据紧校验规范
    • 参数校验
  • 08_websocket

    • 使用 tio-boot 搭建 WebSocket 服务
    • WebSocket 聊天室项目示例
  • 09_java-db

    • java‑db
    • 操作数据库入门示例
    • SQL 模板
    • 数据源配置与使用
    • ActiveRecord
    • Model
    • 生成器与 Model
    • Db 工具类
    • 批量操作
    • 数据库事务处理
    • Cache 缓存
    • Dialect 多数据库支持
    • 表关联操作
    • 复合主键
    • Oracle 支持
    • Enjoy SQL 模板
    • Java-DB 整合 Enjoy 模板最佳实践
    • 多数据源支持
    • 独立使用 ActiveRecord
    • 调用存储过程
    • java-db 整合 Guava 的 Striped 锁优化
    • 生成 SQL
    • 通过实体类操作数据库
    • java-db 读写分离
    • Spring Boot 整合 Java-DB
    • like 查询
    • 常用操作示例
    • Druid 监控集成指南
    • SQL 统计
  • 10_api-table

    • ApiTable 概述
    • 使用 ApiTable 连接 SQLite
    • 使用 ApiTable 连接 Mysql
    • 使用 ApiTable 连接 Postgres
    • 使用 ApiTable 连接 TDEngine
    • 使用 api-table 连接 oracle
    • 使用 api-table 连接 mysql and tdengine 多数据源
    • EasyExcel 导出
    • EasyExcel 导入
    • TQL(Table SQL)前端输入规范
    • ApiTable 实现增删改查
    • 数组类型
    • 单独使用 ApiTable
  • 11_aop

    • JFinal-aop
    • Aop 工具类
    • 配置
    • 配置
    • 独立使用 JFinal Aop
    • @AImport
    • 原理解析
  • 12_cache

    • Caffine
    • Jedis-redis
    • hutool RedisDS
    • Redisson
    • Caffeine and redis
    • CacheUtils 工具类
    • 使用 CacheUtils 整合 caffeine 和 redis 实现的两级缓存
    • 使用 java-db 整合 ehcache
    • 使用 java-db 整合 redis
    • Java DB Redis 相关 Api
    • redis 使用示例
  • 13_认证和权限

    • hutool-JWT
    • FixedTokenInterceptor
    • 使用内置 TokenManager 实现登录
    • 用户系统
    • 重置密码
    • 匿名登录
    • Google 登录
    • 权限校验注解
    • Sa-Token
    • sa-token 登录注册
    • StpUtil.isLogin() 源码解析
    • 短信登录
    • 移动端微信登录实现指南
    • 移动端重置密码
  • 14_i18n

    • i18n
  • 15_enjoy

    • tio-boot 整合 Enjoy 模版引擎文档
    • 引擎配置
    • 表达式
    • 指令
    • 注释
    • 原样输出
    • Shared Method 扩展
    • Shared Object 扩展
    • Extension Method 扩展
    • Spring boot 整合
    • 独立使用 Enjoy
    • tio-boot enjoy 自定义指令 localeDate
    • PromptEngine
    • Enjoy 入门示例-擎渲染大模型请求体
    • Enjoy 使用示例
  • 16_定时任务

    • Quartz 定时任务集成指南
    • 分布式定时任务 xxl-jb
    • cron4j 使用指南
  • 17_tests

    • TioBootTest 类
  • 18_tio

    • TioBootServer
    • tio-core
    • 内置 TCP 处理器
    • 独立启动 UDPServer
    • 使用内置 UDPServer
    • t-io 消息处理流程
    • tio-运行原理详解
    • TioConfig
    • ChannelContext
    • Tio 工具类
    • 业务数据绑定
    • 业务数据解绑
    • 发送数据
    • 关闭连接
    • Packet
    • 监控: 心跳
    • 监控: 客户端的流量数据
    • 监控: 单条 TCP 连接的流量数据
    • 监控: 端口的流量数据
    • 单条通道统计: ChannelStat
    • 所有通道统计: GroupStat
    • 资源共享
    • 成员排序
    • ssl
    • DecodeRunnable
    • 使用 AsynchronousSocketChannel 响应数据
    • 拉黑 IP
    • 深入解析 Tio 源码:构建高性能 Java 网络应用
  • 19_aio

    • ByteBuffer
    • AIO HTTP 服务器
    • 自定义和线程池和池化 ByteBuffer
    • AioHttpServer 应用示例 IP 属地查询
    • 手写 AIO Http 服务器
  • 20_netty

    • Netty TCP Server
    • Netty Web Socket Server
    • 使用 protoc 生成 Java 包文件
    • Netty WebSocket Server 二进制数据传输
    • Netty 组件详解
  • 21_netty-boot

    • Netty-Boot
    • 原理解析
    • 整合 Hot Reload
    • 整合 数据库
    • 整合 Redis
    • 整合 Elasticsearch
    • 整合 Dubbo
    • Listener
    • 文件上传
    • 拦截器
    • Spring Boot 整合 Netty-Boot
    • SSL 配置指南
    • ChannelInitializer
    • Reserve
  • 22_MQ

    • Mica-mqtt
    • EMQX
    • Disruptor
  • 23_tio-utils

    • tio-utils
    • HttpUtils
    • Notification
    • 邮箱
    • JSON
    • 读取文件
    • Base64
    • 上传和下载
    • Http
    • Telegram
    • RsaUtils
    • EnvUtils 使用文档
    • 系统监控
    • 毫秒并发 ID (MCID) 生成方案
  • 24_tio-http-server

    • 使用 Tio-Http-Server 搭建简单的 HTTP 服务
    • tio-boot 添加 HttpRequestHandler
    • 在 Android 上使用 tio-boot 运行 HTTP 服务
    • tio-http-server-native
    • handler 常用操作
  • 25_tio-websocket

    • WebSocket 服务器
    • WebSocket Client
  • 26_tio-im

    • 通讯协议文档
    • ChatPacket.proto 文档
    • java protobuf
    • 数据表设计
    • 创建工程
    • 登录
    • 历史消息
    • 发消息
  • 27_mybatis

    • Tio-Boot 整合 MyBatis
    • 使用配置类方式整合 MyBatis
    • 整合数据源
    • 使用 mybatis-plus 整合 tdengine
    • 整合 mybatis-plus
  • 28_mongodb

    • tio-boot 使用 mongo-java-driver 操作 mongodb
  • 29_elastic-search

    • Elasticsearch
    • JavaDB 整合 ElasticSearch
    • Elastic 工具类使用指南
    • Elastic-search 注意事项
    • ES 课程示例文档
  • 30_magic-script

    • tio-boot 整合 magic-script
  • 31_groovy

    • tio-boot 整合 Groovy
  • 32_firebase

    • 整合 google firebase
    • Firebase Storage
    • Firebase Authentication
    • 使用 Firebase Admin SDK 进行匿名用户管理与自定义状态标记
    • 导出用户
    • 注册回调
    • 登录注册
  • 33_文件存储

    • 文件上传数据表
    • 本地存储
    • 使用 AWS S3 存储文件并整合到 Tio-Boot 项目中
    • 存储文件到 腾讯 COS
  • 34_spider

    • jsoup
    • 爬取 z-lib.io 数据
    • 整合 WebMagic
    • WebMagic 示例:爬取学校课程数据
    • Playwright
    • Flexmark (Markdown 处理器)
    • tio-boot 整合 Playwright
    • 缓存网页数据
  • 36_integration_thirty_party

    • tio-boot 整合 okhttp
    • 整合 GrpahQL
    • 集成 Mailjet
    • 整合 ip2region
    • 整合 GeoLite 离线库
    • 整合 Lark 机器人指南
    • 集成 Lark Mail 实现邮件发送
    • Thymeleaf
    • Swagger
    • Clerk 验证
  • 37_dubbo

    • 概述
    • dubbo 2.6.0
    • dubbo 2.6.0 调用过程
    • dubbo 3.2.0
  • 38_spring

    • Spring Boot Web 整合 Tio Boot
    • spring-boot-starter-webflux 整合 tio-boot
    • Tio Boot 整合 Spring Boot Starter
    • Tio Boot 整合 Spring Boot Starter Data Redis 指南
  • 39_spring-cloud

    • tio-boot spring-cloud
  • 40_mysql

    • 使用 Docker 运行 MySQL
    • /zh/42_mysql/02.html
  • 41_postgresql

    • PostgreSQL 安装
    • PostgreSQL 主键自增
    • PostgreSQL 日期类型
    • Postgresql 金融类型
    • PostgreSQL 数组类型
    • PostgreSQL 全文检索
    • PostgreSQL 查询优化
    • 获取字段类型
    • PostgreSQL 向量
    • PostgreSQL 优化向量查询
    • PostgreSQL 其他
  • 43_oceanbase

    • 快速体验 OceanBase 社区版
    • 快速上手 OceanBase 数据库单机部署与管理
    • 诊断集群性能
    • 优化 SQL 性能指南
    • /zh/43_oceanbase/05.html
  • 50_media

    • JAVE 提取视频中的声音
    • Jave 提取视频中的图片
    • /zh/50_media/03.html
  • 51_asr

    • Whisper-JNI
  • 54_native-media

    • java-native-media
    • JNI 入门示例
    • mp3 拆分
    • mp4 转 mp3
    • 使用 libmp3lame 实现高质量 MP3 编码
    • Linux 编译
    • macOS 编译
    • 从 JAR 包中加载本地库文件
    • 支持的音频和视频格式
    • 任意格式转为 mp3
    • 通用格式转换
    • 通用格式拆分
    • 视频合并
    • VideoToHLS
    • split_video_to_hls 支持其他语言
    • 持久化 HLS 会话
  • 55_telegram4j

    • 数据库设计
    • /zh/55_telegram4j/02.html
    • 基于 MTProto 协议开发 Telegram 翻译机器人
    • 过滤旧消息
    • 保存机器人消息
    • 定时推送
    • 增加命令菜单
    • 使用 telegram-Client
    • 使用自定义 StoreLayout
    • 延迟测试
    • Reactor 错误处理
    • Telegram4J 常见错误处理指南
  • 56_telegram-bots

    • TelegramBots 入门指南
    • 使用工具库 telegram-bot-base 开发翻译机器人
  • 60_LLM

    • 简介
    • AI 问答
    • /zh/60_LLM/03.html
    • /zh/60_LLM/04.html
    • 增强检索(RAG)
    • 结构化数据检索
    • 搜索+AI
    • 集成第三方 API
    • 后置处理
    • 推荐问题生成
    • 连接代码执行器
    • 避免 GPT 混乱
    • /zh/60_LLM/13.html
  • 61_ai_agent

    • 数据库设计
    • 示例问题管理
    • 会话管理
    • 历史记录
    • 对接 Perplexity API
    • 意图识别与生成提示词
    • 智能问答模块设计与实现
    • 文件上传与解析文档
    • 翻译
    • 名人搜索功能实现
    • Ai studio gemini youbue 问答使用说明
    • 自建 YouTube 字幕问答系统
    • 自建 获取 youtube 字幕服务
    • 通用搜索
    • /zh/61_ai_agent/15.html
    • 16
    • 17
    • 18
    • 在 tio-boot 应用中整合 ai-agent
    • 16
  • 62_translator

    • 简介
  • 63_knowlege_base

    • 数据库设计
    • 用户登录实现
    • 模型管理
    • 知识库管理
    • 文档拆分
    • 片段向量
    • 命中测试
    • 文档管理
    • 片段管理
    • 问题管理
    • 应用管理
    • 向量检索
    • 推理问答
    • 问答模块
    • 统计分析
    • 用户管理
    • api 管理
    • 存储文件到 S3
    • 文档解析优化
    • 片段汇总
    • 段落分块与检索
    • 多文档解析
    • 对话日志
    • 检索性能优化
    • Milvus
    • 文档解析方案和费用对比
    • 离线运行向量模型
  • 64_ai-search

    • ai-search 项目简介
    • ai-search 数据库文档
    • ai-search SearxNG 搜索引擎
    • ai-search Jina Reader API
    • ai-search Jina Search API
    • ai-search 搜索、重排与读取内容
    • ai-search PDF 文件处理
    • ai-search 推理问答
    • Google Custom Search JSON API
    • ai-search 意图识别
    • ai-search 问题重写
    • ai-search 系统 API 接口 WebSocket 版本
    • ai-search 搜索代码实现 WebSocket 版本
    • ai-search 生成建议问
    • ai-search 生成问题标题
    • ai-search 历史记录
    • Discover API
    • 翻译
    • Tavily Search API 文档
    • 对接 Tavily Search
    • 火山引擎 DeepSeek
    • 对接 火山引擎 DeepSeek
    • ai-search 搜索代码实现 SSE 版本
    • jar 包部署
    • Docker 部署
    • 爬取一个静态网站的所有数据
    • 网页数据预处理
    • 网页数据检索与问答流程整合
  • 65_java-linux

    • Java 执行 python 代码
    • 通过大模型执行 Python 代码
    • MCP 协议
    • Cline 提示词
    • Cline 提示词-中文版本
  • 66_manim

    • 简介
    • Manim 开发环境搭建
    • 生成场景提示词
    • 生成代码
    • 完整脚本示例
    • 语音合成系统
    • Fish.audio TTS 接口说明文档与 Java 客户端封装
    • 整合 fishaudio 到 java-uni-ai-server 项目
    • 执行 Python (Manim) 代码
    • 使用 SSE 流式传输生成进度的实现文档
    • 整合全流程完整文档
    • HLS 动态推流技术文档
    • manim 分场景生成代码
    • 分场景运行代码及流式播放支持
    • 分场景业务端完整实现流程
    • Maiim布局管理器
    • 仅仅生成场景代码
    • 使用 modal 运行 manim 代码
    • Python 使用 Modal GPU 加速渲染
    • Modal 平台 GPU 环境下运行 Manim
    • Modal Manim OpenGL 安装与使用
    • 优化 GPU 加速
    • 生成视频封面流程
    • Java 调用 manim 命令 执行代码 生成封面
    • Manim 图像生成服务客户端文档
    • manim render help
    • 显示 中文公式
    • manimgl
    • EGL
    • /zh/66_manim/30.html
    • /zh/66_manim/31.html
    • 成本核算
    • /zh/66_manim/33.html
  • 70_tio-boot-admin

    • 入门指南
    • 初始化数据
    • token 存储
    • 与前端集成
    • 文件上传
    • 网络请求
    • 图片管理
    • /zh/70_tio-boot-admin/08.html
    • Word 管理
    • PDF 管理
    • 文章管理
    • 富文本编辑器
  • 71_tio-boot

    • /zh/71_tio-boot/01.html
    • Swagger 整合到 Tio-Boot 中的指南
    • HTTP/1.1 Pipelining 性能测试报告
  • 80_性能测试

    • 压力测试 - tio-http-serer
    • 压力测试 - tio-boot
    • 压力测试 - tio-boot-native
    • 压力测试 - netty-boot
    • 性能测试对比
    • TechEmpower FrameworkBenchmarks
    • 压力测试 - tio-boot 12 C 32G
  • 99_案例

    • 封装 IP 查询服务
    • tio-boot 案例 - 全局异常捕获与企业微信群通知
    • tio-boot 案例 - 文件上传和下载
    • tio-boot 案例 - 整合 ant design pro 增删改查
    • tio-boot 案例 - 流失响应
    • tio-boot 案例 - 增强检索
    • tio-boot 案例 - 整合 function call
    • tio-boot 案例 - 定时任务 监控 PostgreSQL、Redis 和 Elasticsearch
    • Tio-Boot 案例:使用 SQLite 整合到登录注册系统
    • tio-boot 案例 - 执行 shell 命令

指令

Enjoy Template Engine 一如既往地坚持极简设计,核心只有 #if、#for、#switch、#set、#include、#define、#(…) 这七个指令,便实现了传统模板引擎几乎所有的功能,用户如果有任意一门程序语言基础,学习成本几乎为零。

如果官方提供的指令无法满足需求,还可以极其简单地在模板语言的层面对指令进行扩展,在 com.jfinal.template.ext.directive 包下面就有五个扩展指令,Active Record 的 sql 模块也针对 sql 管理功能扩展了三个指令,参考这些扩展指令的代码,便可无师自通,极为简单。

注意,Enjoy 模板引擎指令的扩展是在词法分析、语法分析的层面进行扩展,与传统模板引擎的自定义标签类的扩展完全不是一个级别,前者可以极为全面和自由的利用模板引擎的基础设施,在更加基础的层面以极为简单直接的代码实现千变万化的功能。参考 Active Record 的 sql 管理模块,则可知其强大与便利。

1、输出指令#( )

与几乎所有 java 模板引擎不同,Enjoy Template Engine 消灭了插值指令这个原本独立的概念,而是将其当成是所有指令中的一员,仅仅是指令名称省略了而已。因此,该指令的定界符与普通指令一样为小括号,从而不必像其它模板引擎一样引入额外的如大括号般的定界符。

#(…) 输出指令的使用极为简单,只需要为该指令传入前面 6.4 节中介绍的任何表达式即可,指令会将这些表达式的求值结果进行输出,特别注意,当表达式的值为 null 时没有任何输出,更不会报异常。所以,对于 #(value) 这类输出不需要对 value 进行 null 值判断,如下是代码示例:

#(value)
#(object.field)
#(object.field ??)
#(a > b ? x : y)
#(seoTitle ?? "JFinal 俱乐部")
#(object.method(), null)

如上图所示,只需要对输出指令传入表达式即可。注意上例中第一行代码 value 参数可以为 null,而第二行代码中的 object 为 null 时将会报异常,此时需要使用第三行代码中的空合安全取值调用运算符:object.field ??

此外,注意上图最后一行代码中的输出指令参数为一个逗号表达式,逗号表达式的整体求值结果为最后一个表达式的值,而输出指令对于 null 值不做输出,所以这行代码相当于是仅仅调用了 object.method() 方法去实现某些操作。

输出指令可以自由定制,只需要继承 OutputDirectiveFactory 类并覆盖其中的 getOutputDirective 方法,然后在 configEngine(Engine me)方法中,通过 me. setOutputDirectiveFactory(…) 切换即可。

2、#if 指令

直接举例:

#if(cond)
  ...
#end

如上所示,if 指令需要一个 cond 表达式作为参数,并且以 #end 为结尾符,cond 可以为 6.3 章节中介绍的所有表达式,包括逗号表达式,当 cond 求值为 true 时,执行 if 分支之中的代码。

if 指令必然支持 #else if 与 #else 分支块结构,以下是示例:

#if(c1)
  ...
#else if(c2)
  ...
#else if (c3)
  ...
#else
  ...
#end

由于#else if、#else 用法与 java 语法完全一样,在此不在赘述。(注意:jfinal 3.3 之前的版本 #else if 之间不能有空格字符需要写成:#elseif,否则会报异常:Can not match the #end of directive #if )

3、#for 指令

Enjoy Template Engine 对 for 指令进行了极为人性化的扩展,可以对任意类型数据进行迭代输出,包括支持 null 值迭代。以下是代码示例:

// 对 List、数组、Set 这类结构进行迭代
#for(x : list)
  #(x.field)
#end

// 对 Map 进行迭代
#for(x : map)
  #(x.key)
  #(x.value)
#end

上例代码中的第一个 for 指令是对 list 进行迭代,用法与 java 语法完全一样。

第二个 for 指令是对 map 进行迭代,取值方式为 item.key 与 item.value。该取值方式是 enjoy 对 map 迭代的增强功能,可以节省代码量。仍然也可以使用传统的 java map 迭代方式:#for( x : map.entrySet() ) #(x.key) #(x.value) #end

注意:当被迭代的目标为 null 时,不需要做 null 值判断,for 指令会自动跳过,不进行迭代。从而可以避免 if 判断,节省代码提高效率。

for 指令还支持对其状态进行获取,代码示例:

#for(x : listAaa)
  #(for.index)
  #(x.field)

  #for(x : listBbb)
     #(for.outer.index)
     #(for.index)
     #(x.field)
  #end
#end

以上代码中的 #(for.index)、#(for.outer.index) 是对 for 指令当前状态值进行获取,前者是获取当前 for 指令迭代的下标值(从 0 开始的整数),后者是内层 for 指令获取上一层 for 指令的状态。这里注意 for.outer 这个固定的用法,专门用于在内层 for 指令中引用上层 for 指令状态。

注意:for 指令嵌套时,各自拥有自己的变量名作用域,规则与 java 语言一致,例如上例中的两个#(x.field)处在不同的 for 指令作用域内,会正确获取到所属作用域的变量值。

for 指令支持的所有状态值如下示例:

#for(x : listAaa)
   #(for.size)    被迭代对象的 size 值
   #(for.index)   从 0 开始的下标值
   #(for.count)   从 1 开始的记数值
   #(for.first)   是否为第一次迭代
   #(for.last)    是否为最后一次迭代
   #(for.odd)     是否为奇数次迭代
   #(for.even)    是否为偶数次迭代

   #(for.outer)        引用上层 #for 指令状态
   #(for.outer.size)   引用上层 #for 指令被迭代对象的 size 值
#end

具体用法在上面代码中用中文进行了说明,在此不再赘述。

除了 Map、List 以外,for 指令还支持 Collection、Iterator、array 普通数组、Iterable、Enumeration、null 值的迭代,用法在形式上与前面的 List 迭代完全相同,都是 #for(id : target) 的形式,对于 null 值,for 指令会直接跳过不迭代。

此外,for 指令还支持对任意类型进行迭代,此时仅仅是对该对象进行一次性迭代,如下所示:

#for(x : article)
   #(x.title)
#end

上例中的 article 为一个普通的 java 对象,而非集合类型对象,for 循环会对该对象进行一次性迭代操作,for 表达式中的 x 即为 article 对象本身,所以可以使用 #(x.title) 进行输出。

for 指令还支持 #else 分支语句,在 for 指令迭代次数为 0 时,将执行 #else 分支内部的语句,如下是示例:

#for(blog : blogList)
   #(blog.title)
#else

您还没有写过博客,点击此处<a href="/blog/add">开博</a>

#end

以上代码中,当 blogList.size() 为 0 或者 blogList 为 null 值时,也即迭代次数为 0 时,会执行#else 分支,这种场景在 web 项目中极为常见。

最后,除了上面介绍的 for 指令迭代用法以外,还支持更常规的 for 语句形式,以下是代码示例:

#for(i = 0; i < 100; i++)
   #(i)
#end

与 java 语法基本一样,唯一的不同是变量声明不需要类型,直接用赋值语句即可,Enjoy Template Engine 中的变量是动态弱类型。

注意:以上这种形式的 for 语句,比前面的 for 迭代少了 for.size 与 for.last 两个状态,只支持如下几个状态:for.index、for.count、for.first、for.odd、for.even、for.outer

#for 指令还支持 #continue、#break 指令,用法与 java 完全一致,在此不再赘述。

4、#switch 指令(3.6 版本新增指令)

#switch 指令对标 java 语言的 switch 语句。基本用法一致,但做了少许提升用户体验的改进,用法如下:

#switch (month)
  #case (1, 3, 5, 7, 8, 10, 12)
    #(month) 月有 31 天
  #case (2)
    #(month) 月平年有28天,闰年有29天
  #default
    月份错误: #(month ?? "null")
#end

如上代码所示,#case 分支指令支持以逗号分隔的多个参数,这个功能就消解掉了 #break 指令的必要性,所以 enjoy 模板引擎是不需要 #break 指令的。

#case 指令参数还可以是任意表达式,例如:

#case (a, b, x + y, "abc", "123")

上述代码中用逗号分隔的表达式先会被求值,然后再逐一与 #switch(value) 指令中的 value 进行比较,只要有一个值与其相等则该 case 分支会被执行。

#case 支持逗号分隔的多参数,从而无需引入 #break 指令,不仅减少了代码量,而且避免了忘写 #break 指令时带来的错误隐患。还有一个与 java 语法有区别的地方是 #case、#default 指令都未使用冒号字符。

5、#set 指令

set 指令用于声明变量同时对其赋值,也可以是为已存在的变量进行赋值操作。set 指令只接受赋值表达式,以及用逗号分隔的赋值表达式列表,如下是代码示例:

#set(x = 123)
#set(a = 1, b = 2, c = a + b)
#set(array[0] = 123)
#set(map["key"] = 456)

#(x)  #(c)  #(array[0])  #(map.key)  #(map["key"])

以上代码中,第一行代码最为简单为 x 赋值为 123,第二行代码是一个赋值表达式列表,会从左到右依次执行赋值操作,如果等号右边出现表达式,将会对表达式求值以后再赋值。最后一行代码是输出上述赋值以后各变量的值,其她所有指令也可以像输出指令一样进行变量的访问。

请注意,#for、#include、#define 这三个指令会开启新的变量名作用域,#set 指令会首先在本作用域中查找变量是否存在,如果存在则对本作用域中的变量进行操作,否则继续向上层作用域查找,找到则操作,如果找不到,则将变量定义在顶层作用域中,这样设计非常有利于在模板中传递变量的值。

当需要明确指定在本层作用域赋值时,可以使用#setLocal 指令,该指令所需参数与用法与#set 指令完全一样,只不过作用域被指定为当前作用域。#setLocal 指令通常用于#define、#include 指令之内,用于实现模块化,从而希望其中的变量名不会与上层作用域发生命名上的冲突。

重要:由于赋值表达式本质也是表达式,而其它指令本质上支持任意表达式,所以 #set 指令对于赋值来说并不是必须的,例如可以在 #() 输出指令中使用赋值表达式:

#(x = 123, y = "abc", array = [1, "a", true], map = {k1:v1}, null)

以上代码在输出指令中使用了多个赋值表达式,可以实现 #set 的功能,在最后通过一个 null 值来避免输出表达式输出任何东西。类似的,别的指令内部也可以这么来使用赋值表达式。

6、#include 指令

include 指令用于将外部模板内容包含进来,被包含的内容会被解析成为当前模板中的一部分进行使用,如下是代码示例:

#include("sidebar.html")

#include 指令第一个参数必须为 String 常量,当以 ”/” 打头时将以 baseTemplatePath 为相对路径去找文件,否则将以使用 #include 指令的当前模板的路径为相对路径去找文件。

baseTemplatePath 可以在 configEngine(Engine me) 中通过 me.setBaseTemplatePath(…) 进行配置。

此外,include 指令支持传入无限数量的赋值表达式,十分有利于模块化,例如:如下名为 ”_hot_list.html” 的模板文件用于展示热门项目、热门新闻等等列表:

<div class="hot-list">
  <h3>#(title)</h3>
  <ul>
    #for(x : list)
    <li>
      <a href="#(url)/#(x.id)">#(x.title)</a>
    </li>
    #end
  </ul>
</div>

上图中的 title、list、url 是该 html 片段需要的变量,使用 include 指令分别渲染“热门项目”与“热门新闻”的用法如下:

#include("_hot_list.html", title="热门项目", list=projectList, url="/project")
#include("_hot_list.html", title="热门新闻", list=newsList, url="/news")

上面两行代码中,为“_hot_list.html”中用到的三个变量 title、list、url 分别传入了不同的值,实现了对“_hot_list.html”的模块化重用。

7、#render 指令

render 指令在使用上与 include 指令几乎一样,同样也支持无限量传入赋值表达式参数,主要有两点不同:

render 指令支持动态化模板参数,例如:#render(temp),这里的 temp 可以是任意表达式,而#include 指令只能使用字符串常量:#include(“abc.html”)

render 指令中#define 定义的模板函数只在其子模板中有效,在父模板中无效,这样设计非常有利于模块化

引入 #render 指令的核心目的在于支持动态模板参数。

8、#define 指令

#define 指令是模板引擎主要的扩展方式之一,define 指令可以定义模板函数(Template Function)。通过 define 指令,可以将需要被重用的模板片段定义成一个一个的 template function,在调用的时候可以通过传入参数实现千变万化的功能。

在此给出使用 define 指令实现的 layout 功能,首先创建一个 layout.html 文件,其中的代码如下:

#define layout()
<html>
  <head>
    <title>JFinal俱乐部</title>
  </head>
  <body>
    #@content()
  </body>
</html>
#end

以上代码中通过#define layout()定义了一个名称为 layout 的模板函数,定义以#end 结尾,其中的 #@content() 表示调用另一个名为 content 的模板函数。

特别注意:模板函数的调用比指令调用多一个@字符,是为了与指令调用区分开来。

接下来再创建一个模板文件,如下所示:

#include("layout.html")
#@layout()

#define content()
<div>
   这里是模板内容部分,相当于传统模板引擎的 nested 的部分
</div>
#end

上图中的第一行代码表示将前面创建的模板文件 layout.html 包含进来,第二行代码表示调用 layout.html 中定义的 layout 模板函数,而这个模板函数中又调用了 content 这个模板函数,该 content 函数已被定义在当前文件中,简单将这个过程理解为函数定义与函数调用就可以了。注意,上例实现 layout 功能的模板函数、模板文件名称可以任意取,不必像 velocity、freemarker 需要记住 nested、layoutContent 这样无聊的概念。

通常作为 layout 的模板文件会在很多模板中被使用,那么每次使用时都需要#include 指令进行包含,本质上是一种代码冗余,可以在 configEngine(Engine me)方法中,通过 me.addSharedFunction("layout.html")方法,将该模板中定义的所有模板函数设置为共享的,那么就可以省掉#include(…),通过此方法可以将所有常用的模板函数全部定义成类似于共享库这样的集合,极大提高重用度、减少代码量、提升开发效率。

Enjoy Template Engine 彻底消灭掉了 layout、nested、macro 这些无聊的概念,极大降低了学习成本,并且极大提升了扩展能力。模板引擎本质是一门程序语言,任何可用于生产环境的语言可以像呼吸空气一样自由地去实现 layout 这类功能。

此外,模板函数必然支持形参,用法与 java 规则基本相同,唯一不同的是不需要指定参数类型,只需要参数名称即可,如下是代码示例:

#define test(a, b, c)
   #(a)
   #(b)
   #(c)
#end

以上代码中的模板函数 test,有 a、b、c 三个形参,在函数体内仅简单对这三个变量进行了输出,注意形参必须是合法的 java 标识符,形参的作用域为该模板函数之内符合绝大多数程序语言习惯,以下是调用该模板函数的例子代码:

#@test(123, "abc", user.name)

以上代码中,第一个参数传入的整型 123,第二个是字符串,第三个是一个 field 取值表达式,从例子可以看出,实参可以是任意表达式,在调用时模板引擎会对表达式求值,并逐一赋值给模板函数的形参。

注意:形参与实参数量要相同,如果实参偶尔有更多不确定的参数要传递进去,可以在调用模板函数代码之前使用#set 指令将值传递进去,在模板函数内部可用空合安全取值调用表达式进行适当控制,具体用法参考 jfinal-club 项目中的 _paginate.html 中的 append 变量的用法。

define 还支持 return 指令,可以在模板函数中返回,但不支持返回值。

9、模板函数调用与 #call 指令

调用 define 定义的模板函数的格式为:#@name(p1, p2…, pn),模板函数调用比指令调用多一个@字符,多出的@字符用来与指令调用区别开来。

此外,模板函数还支持安全调用,格式为:#@name?(p1, p2…, pn),安全调用只需在模板函数名后面添加一个问号即可。安全调用是指当模板函数未定义时不做任何操作。

安全调用适合用于一些模板中可有可无的内容部分,以下是一个典型应用示例:

#define layout()
<html>
  <head>
    <link rel="stylesheet" type="text/css" href="/assets/css/jfinal.css">
    #@css?()
  </head>

  <body>
    <div class="content">
      #@main()
    </div>

    <script type="text/javascript" src="/assets/js/jfinal.js"></script>
    #@js?()
  </body>
 </html>
#end

以上代码示例定义了一个 web 应用的 layout 模板,注意看其中的两处:#@css?() 与 #@js?() 就是模板函数安全调用。

上述模板中引入的 jfinal.css 与 jfinal.js 是两个必须的资源文件,对大部分模块已经满足需要,但对于有些模块,除了需要这两个必须的资源文件以外,还需要额外的资源文件,那么就可以通过#define css() 与 #define js() 来提供,如下是代码示例:

#@layout()   ### 调用 layout.html 中定义的模板函数 layout()

#define main()
   这里是 body 中的内容块
#end

#define css()
   这里可以引入额外的 css 内容
#end

#define js()
   这里可以引入额外的 js 内容
#end

以上代码中先是通过#@layout()调用了前面定义过的 layout()这个模板函数,而这个模板函数中又分别调用了#@main()、#@css?()、#@js?()这三个模板函数,其中后两个是安全调用,所以对于不需要额外的 css、js 文件的模板,则不需要定义这两个方法,安全调用在调用不存在的模板函数时会直接跳过。

#call 指令是 jfinal 3.6 版本新增指令,使用 #call 指令,模板函数的名称与参数都可以动态指定,提升模板函数调用的灵活性,用法如下:

#call(funcName, p1, p2, ..., pn)

上述代码中的 funcName 为函数名,p1、p2、pn 为被调用函数所使用的参数。如果希望模板函数不存在时忽略其调用,添加常量值 true 在第一个参数位置即可:

#call(true, funcName, p1, p2, ..., pn)

10、#date 指令

date 指令用于格式化输出日期型数据,包括 Date、Timestamp 等一切继承自 Date 类的对象的输出,使用方式极其简单:

#date(account.createAt)
#date(account.createAt, "yyyy-MM-dd HH:mm:ss")

上面的第一行代码只有一个参数,那么会按照默认日期格式进行输出,默认日期格式为:“yyyy-MM-dd HH:mm”。上面第二行代码则会按第二个参数指定的格式进行输出。

如果希望改变默认输出格式,只需要通过 engine.setDatePattern()进行配置即可。

keepPara 问题:如果日期型表单域提交到后端,而后端调用了 Controller 的 keepPara() 方法,会将这个日期型数据转成 String 类型,那么 #date(...) 指令在输出这个 keepPara 过来的 String 时就会抛出异常,对于这种情况可以指令 keep 住其类型:

// keepPara() 用来 keep 住所有表单提交数据,全部转换成 String 类型
keepPara();

// 再用一次带参的 keepPara,指定 createAt 域 keep 成 Date 类型
keepPara(Date.class, "createAt");

如上所示,第二行代码用 Date.class 参数额外指定了 createAt 域 keep 成 Date 类型,那么在页面 #date(createAt) 指令就不会抛出异常了。keepModel(...)、keepBean(...) 会保持原有类型,无需做上述处理。

11、#number 指令

number 指令用于格式化输出数字型数据,包括 Double、Float、Integer、Long、BigDecimal 等一切继承自 Number 类的对象的输出,使用方式依然极其简单:

#number(3.1415926, "#.##")
#number(0.9518, "#.##%")
#number(123456789, ",###")
#number(300000, "光速为每秒,### 公里。")

上面的 #number 指令第一个参数为数字类型,第二个参数为 String 类型的 pattern。Pattern 参数的用法与 JDK 中 DecimalFormat 中 pattern 的用法完全一样。当不知道如何使用 pattern 时可以在搜索引擎中搜索关键字 DecimalFormat,可以找到非常多的资料。

#number 指令的两个参数可以是变量或者复杂表达式,上例参数中使用常量仅为了方便演示。

12、#escape 指令

escape 指令用于 html 安全转义输出,可以消除 XSS 攻击。escape 将类似于 html 形式的数据中的大于号、小于号这样的字符进行转义,例如将小于号转义成:< 将空格转义成  

使用方式与输出指令类似:

#escape(blog.content)

13、指令扩展

由于采用独创的 DKFF 和 DLRD 算法,Enjoy Template Engine 可以极其便利地在语言层面对指令进行扩展,而代码量少到不可想象的地步,学习成本无限逼近于 0。以下是一个代码示例:

public class NowDirective extends Directive {
  public void exec(Env env, Scope scope, Writer writer) {
    write(writer, new Date().toString());
  }
}

以上代码中,通过继承 Directive 并实现 exec 方法,三行代码即实现一个#now 指令,可以向模板中输出当前日期,在使用前只需通过 me.addDirective(“now”, NowDirective.class) 添加到模板引擎中即可。以下是在模板中使用该指令的例子:

今天的日期是: #now()

除了支持上述无#end 块,也即无指令 body 的指令外,Enjoy Template Engine 还直接支持包含#end 与 body 的指令,以下是示例:

public class Demo extends Directive {

  // ExprList 代表指令参数表达式列表
  public void setExprList(ExprList exprList) {
    // 在这里可以对 exprList 进行个性化控制
    super.setExprList(exprList);
  }

  public void exec(Env env, Scope scope, Writer writer) {
    write(writer, "body 执行前");
    stat.exec(env, scope, writer);  // 执行 body
    write(writer, "body 执行后");
  }

  public boolean hasEnd() {
    return true;  // 返回 true 则该指令拥有 #end 结束标记
  }
}

如上所示,Demo 继承 Directive 覆盖掉父类中的 hasEnd 方法,并返回 true,表示该扩展指令具有#end 结尾符。上例中 public void exec 方法中的三行代码,其中 stat.exec(…)表示执行指令 body 中的代码,而该方法前后的 write(…)方法分别输出一个字符串,最终的输出结果详见后面的使用示例。此外通过覆盖父类的 setExprList(…)方法可以对指令的参数进行控制,该方法并不是必须的。

通过 me.addDirective(“demo”, Demo.class)添加到引擎以后,就可以像如下代码示例中使用:

#demo()
 这里是 demo body 的内容
#end

最后的输出结果如下:

body 执行前
 这里是 demo body 的内容
body 执行后

上例中的#demo 指令 body 中包含一串字符,将被 Demo.exec(…)方法中的 stat.exec(…)所执行,而 stat.exec(…)前后的 write(…)两个方法调用产生的结果与 body 产生的结果生成了最终的结果。

重要:指令中声明的属性是全局共享的,所以要保障指令中的属性是线程安全的。如下代码以 com.jfinal.template.ext.directive.DateDirective 的代码片段为例:

public class DateDirective extends Directive {

   private Expr valueExpr;
   private Expr datePatternExpr;
   private int paraNum;

   ...
}

以上代码中有三个属性,类型是 Expr、Expr、int,其中 Expr 是线程安全的,而 int paraNum 虽然表面上看不是线程安全的,但在整个 DateDirective 类中只有构造方法对该值初始化的时候有写入操作,其它所有地方都是读操作,所以该 int 属性在这里是线程安全的。

14、常见错误

Enjoy 模板引擎的使用过程中最常见的错误就是分不清 “表达式” 与 “非表达式”,所谓表达式是指模板函数调用、指令调用时小括号里面的所有东西,例如:

#directiveName(这里所有东西是表达式)
#@functionName(这里所有东西是表达式)

上例中的两行代码分别是调用指令与调用模板函数,小括号内的东西是表达式,而表达式的用法与 Java 几乎一样,该这么来用:

#directiveName( user.name )

最常见错误的用法如下:

#directiveName ( #(user.name) )

简单来说这种错误就是在该使用表达式的地方使用指令,在表达式中永远不要出现字符 '#',而是直接使用 java 表达式。

Edit this page
Last Updated:
Contributors: Tong Li
Prev
表达式
Next
注释