登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Java StringTemplate怎样安全拼接结构化文本

来源:17golang原创

时间:2026-11-04 17:02:11 239浏览 收藏

Java 的 StringTemplate 适合把固定文本、表达式和值组织成一个可交给处理器的输入,但“能插值”不等于“已经安全转义”。真正稳妥的做法是:先确认运行版本,再根据数据格式选择处理器;SQL 交给 PreparedStatement 做参数绑定,JSON 交给序列化库做转义,不能把 STR 当成通用安全层。

官方文档:https://docs.oracle.com/en/java/javase/22/language/string-templates.html

实践要点
  • StringTemplate 只在 Java 21/22 作为预览特性提供,Java 23 起已撤回,不能当作当前稳定 API。
  • STR 负责普通插值,RAW 暴露片段和值;校验、转义和参数绑定必须由专门处理器完成。
  • 结构化文本的安全边界不在模板语法本身,而在 JSON 序列化、SQL 参数化和失败回退策略。

一、先确认 StringTemplate 的版本与触发场景

StringTemplate 的设计是“模板片段 + 嵌入表达式 + 模板处理器”。Java 21 首次预览,Java 22 二次预览;Oracle 的迁移说明指出,这项特性原计划在 Java 23 再预览,但后来因设计尚未形成共识而撤回。因此,下面的语法只适合维护 Java 21/22 预览代码、阅读旧实验项目或评估模板处理器设计,不适合直接作为 Java 23 及以后生产代码的稳定依赖。

StringTemplate 片段、值、处理器与 Java 版本边界的静态说明图
图1:StringTemplate 处理器关系说明图,展示片段、值与处理器之间的职责边界。

在 Java 22 预览环境中,最小示例需要同时打开预览开关:

# Java 22 只用于演示预览语法,编译和运行必须保持同一版本。
javac --enable-preview --release 22 Demo.java
java --enable-preview Demo

普通文本可以使用内置的 STR:

String user = "Duke";
// STR 只把表达式结果插入文本,不负责 JSON、SQL 等格式的安全编码。
String message = STR."Hello, \{user}";

如果需求只是日志、提示语或内部展示文本,STR 足够直观;一旦结果要被解析器、数据库或下游协议消费,就应进入专门的结构化处理流程。

二、让模板处理器接管结构化输入

RAW 不会替你把输入变成安全字符串,它提供模板的 fragments() 和 values()。自定义 Processor 可以在这里做类型限制、转义、占位符替换或构造别的返回类型。这个分层很重要:模板负责表达固定结构,处理器负责决定外部输入怎样进入结构。

import java.util.List;
import static java.lang.StringTemplate.RAW;

// 只允许受控的文本片段进入展示用途;协议编码仍应交给专门库。
static String safeDisplay(StringTemplate template) {
    List values = template.values();
    for (Object value : values) {
        // 展示层先拒绝控制字符,避免把日志格式和业务文本混在一起。
        if (value instanceof String text && text.chars().anyMatch(Character::isISOControl)) {
            throw new IllegalArgumentException("输入包含控制字符");
        }
    }
    // 仅完成普通插值;这里没有声称完成 JSON 或 SQL 转义。
    return template.interpolate();
}

String name = "Duke";
String result = safeDisplay(RAW."用户:\{name}");

这里的检查只是一个展示层例子,不是通用安全实现。比如 JSON 的引号、反斜杠、换行符和 Unicode 控制字符都有自己的编码规则;自己写几条 replace 很容易漏掉边界。需要 JSON 时,应把对象交给 Jackson、JSON-B 等序列化组件,让它根据目标格式生成字符串或字节。

三、用参数绑定组合 SQL

StringTemplate 最适合展示“自定义处理器可以改变返回类型”的思路。SQL 场景中,固定 SQL 片段可以保留,动态值必须变成参数,而不是直接进入 SQL 文本。处理器可以把片段拼成问号占位符,再按照原顺序绑定值:

SQL 参数绑定与 JSON 序列化安全边界的静态说明图
图2:结构化文本安全边界说明图,展示 SQL 参数绑定与 JSON 序列化各自承担的职责。
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.StringJoiner;

final class QueryTemplate {
    private final Connection connection;

    QueryTemplate(Connection connection) {
        this.connection = connection;
    }

    PreparedStatement prepare(StringTemplate template) throws SQLException {
        // 用问号替换片段之间的值,动态数据不会成为 SQL 语法的一部分。
        StringJoiner query = new StringJoiner("?");
        template.fragments().forEach(query::add);
        PreparedStatement statement = connection.prepareStatement(query.toString());

        int index = 1;
        for (Object value : template.values()) {
            // 按类型绑定,未知类型回退为字符串参数,不拼接进 SQL。
            if (value instanceof Integer number) {
                statement.setInt(index++, number);
            } else {
                statement.setString(index++, String.valueOf(value));
            }
        }
        return statement;
    }
}

// try-with-resources 负责释放 PreparedStatement,参数值只进入绑定槽位。
try (PreparedStatement statement = new QueryTemplate(connection)
        .prepare(RAW."select id, name from users where name = \{userName}")) {
    var rows = statement.executeQuery();
    // 这里继续读取结果集;实际项目还应按驱动规范关闭 ResultSet。
}

这个示例的安全性来自“占位符 + 绑定”,不是来自 RAW 或字符串模板语法。表名、列名、排序方向这类不能作为普通参数绑定的结构,必须使用白名单映射;不能让用户输入直接决定 SQL 关键字或标识符。

四、为 JSON 和多行文本设置门禁

StringTemplate 的文本块很适合把邮件、HTML 或配置草稿写得清楚,但写得像 JSON 不代表它就是合法且安全的 JSON。下面这种形式只是视觉上整齐,恶意引号仍可能改变结构:

String userName = request.getParameter("name");
// 这是错误示范:STR 不会替 JSON 字符串自动加引号或转义。
String json = STR."{\"name\":\"\{userName}\"}";

更可靠的流程是先构造 Java 对象,再调用 JSON 库序列化;如果确实要实现自定义 Processor,也要在处理器中逐项检查值类型并使用成熟的编码器,不能把 template.interpolate() 当作最终 JSON。

上线前可以把门禁写成四条规则:

检查项允许的做法失败处理
JDK 版本只在锁定的 Java 21/22 预览环境评估Java 23+ 改回稳定字符串/序列化方案
SQL 动态值片段固定、值走 PreparedStatement拒绝原始拼接并记录调用点
JSON 数据对象交给 JSON 序列化库序列化异常进入错误分支
处理器异常保留类型检查和资源关闭返回明确错误,不输出半成品文本

如果项目必须跨多个 JDK 版本,建议把 StringTemplate 放在实验性适配层,并为稳定实现保留接口。这样即使预览特性继续变化,业务代码仍可切换到 MessageFormat、文本块、JSON 库或 JDBC 参数绑定,而不用全项目改写。

相关问题

STR 能自动防止 JSON 注入吗?

不能。STR 只做插值,JSON 的引号、反斜杠和控制字符应由 JSON 序列化器处理。

StringTemplate 能直接替代 PreparedStatement 吗?

不能。只有自定义处理器把值转换为占位符并逐项绑定时,才形成参数化查询;直接插入 SQL 文本仍然不安全。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>