Apache Texen 在 Ubuntu 22.04 集成使用指南

在 Ubuntu 22.04 从零安装 JDK 17 与 Ant、下载 Texen 1.0 依赖 jar,跑通行星 HTML 与实体代码(POJO+SQL)两个生成示例,并沉淀编码与目录创建等实测注意事项。

Apache TexenApache TexenJavaUbuntuUbuntu 22.04Apache2026/10/16

Apache Texen 在 Ubuntu 22.04 集成使用指南

目标:在 Ubuntu 22.04 服务器上从零搭建 Apache Texen 1.0 的文本生成环境:安装 JDK 17 与 Ant、从 Maven Central 下载依赖 jar、编写控制模板与工人模板,跑通「行星 HTML 页面」与「实体代码生成(POJO + 建表 SQL)」两个完整示例,并沉淀编码、目录创建等实测坑点。全部命令与回显已在 Ubuntu 22.04.5 LTS 实测通过。

一、环境信息

本文实测环境与组件版本:

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • JDK:OpenJDK 17.0.20.1(apt 包 openjdk-17-jdk)
  • 构建工具:Apache Ant 1.10.12(apt 包 ant,包版本 1.10.12-1)
  • 下载工具:GNU Wget 1.21.2
  • 生成框架:Apache Texen 1.0(org.apache.texen:texen:1.0,17159 字节)
  • 模板引擎:推荐组合 Velocity 1.7 + commons-collections 3.2.2 + commons-lang 2.6;对照组合 Velocity Engine 2.4.1 + commons-lang3 3.17.0 + slf4j-api 1.7.36(均含 commons-collections 3.2.2)
  • 工作目录:/root/texen-demo/

💡 技术拓展:Texen 内部固定使用 Velocity 作为渲染引擎(默认搭配 Velocity 1.7,实测环境亦如此),不提供引擎替换选项。「模板引擎」指把模板与数据上下文合并渲染成最终文本的组件,Java 生态常见对比——Velocity($obj/#foreach,简单直观)、FreeMarker(${obj}/<#list>,内建函数丰富)、Thymeleaf(原生 HTML 属性,Spring Boot 页面常用)、Mustache({{obj}},逻辑无关跨语言)。只想"裸用"Velocity 而不需要 Ant 驱动的多文件编排,可直接使用 Velocity Engine 本身(见《Apache Velocity 快速入门与集成示例》)。

二、前置条件

2.1 安装 JDK 17、Ant 与 wget

apt-get install -y openjdk-17-jdk ant wget

已安装状态下执行回显(实测,为三条安装命令各自回显的节选汇总,首次安装时会显示解包与配置过程):

openjdk-17-jdk is already the newest version (17.0.20.1+1-1~22.04).
ant is already the newest version (1.10.12-1).
wget is already the newest version (1.21.2-2ubuntu1.5).
0 upgraded, 0 newly installed, 0 to remove and 2 not upgraded.

版本验证(实测):

java -version && javac -version && ant -version && wget --version | head -1
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
javac 17.0.20.1
Apache Ant(TM) version 1.10.12 compiled on January 17 1970
GNU Wget 1.21.2 built on linux-gnu.

2.2 建立工作目录

mkdir -p /root/texen-demo && cd /root/texen-demo && pwd
/root/texen-demo

三、依赖引入

Texen 不是服务,无安装包,集成方式就是把 4 个 jar 放进工程 lib 目录、在 build.xml 里声明类路径。本节先给推荐组合(Velocity 1.7 全兼容),再给对照组合(Velocity 2.4.1,仅用于兼容性评估)。

3.1 下载推荐组合的 4 个 jar

cd /root/texen-demo
mkdir -p lib
cd lib
wget https://repo1.maven.org/maven2/org/apache/texen/texen/1.0/texen-1.0.jar
wget https://repo1.maven.org/maven2/org/apache/velocity/velocity/1.7/velocity-1.7.jar
wget https://repo1.maven.org/maven2/commons-collections/commons-collections/3.2.2/commons-collections-3.2.2.jar
wget https://repo1.maven.org/maven2/commons-lang/commons-lang/2.6/commons-lang-2.6.jar

单独一条 wget 的回显(实测节选,其余三条输出格式相同):

--2026-10-16 03:23:58--  https://repo1.maven.org/maven2/org/apache/texen/texen/1.0/texen-1.0.jar
Resolving repo1.maven.org (repo1.maven.org)... 104.18.18.12, 104.18.19.12, 2606:4700::6812:120c, ...
Connecting to repo1.maven.org (repo1.maven.org)|104.18.18.12|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 17159 (17K) [application/java-archive]
Saving to: 'texen-1.0.jar'
     0K .......... ......                                     100% 1.35M=0.01s
2026-10-16 03:23:59 (1.35 MB/s) - 'texen-1.0.jar' saved [17159/17159]

下载完成后用 sha256 核对文件与完整性(实测值):

sha256sum /root/texen-demo/lib/*.jar
eeeae917917144a68a741d4c0dff66aa5c5c5fd85593ff217bced3fc8ca783b8  commons-collections-3.2.2.jar
50f11b09f877c294d56f24463f47d28f929cf5044f648661c0f0cfbae9a2f49c  commons-lang-2.6.jar
d3ccc091d3d0efc5f18571063ff74d1f8e1b3cb519606437a7892c4026a47545  texen-1.0.jar
ec92dae810034f4b46dbb16ef4364a4013b0efb24a8c5dd67435cae46a290d8e  velocity-1.7.jar

四个文件大小分别为 17159、449505、588337、284220 字节(texen、velocity、commons-collections、commons-lang)。

3.2 下载对照组合(Velocity Engine 2.4.1)

如需评估升级到 Velocity 2.x 的可行性,另备一套 jar(放在独立工程 lib 下均可):

wget https://repo1.maven.org/maven2/org/apache/velocity/velocity-engine-core/2.4.1/velocity-engine-core-2.4.1.jar
wget https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.17.0/commons-lang3-3.17.0.jar
wget https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar

对照组合完整 jar 清单(实测):commons-collections-3.2.2.jar、commons-lang3-3.17.0.jar、slf4j-api-1.7.36.jar、texen-1.0.jar、velocity-engine-core-2.4.1.jar。

3.3 依赖矩阵实测结论

组合结果说明
texen 1.0 + velocity 1.7 + commons-collections 3.2.2 + commons-lang 2.6全功能可用推荐组合,本文示例默认使用
texen 1.0 + velocity-engine-core 2.4.1 + commons-lang3 + slf4j-api部分可用不用 contextProperties 可跑通;$strings 仅剩 4 个 formatFileString 静态方法
texen 1.0 + velocity-engine-core 2.4.1 + contextProperties 属性必然失败触发 NoSuchMethodError(见 5.3 节实测)

💡 技术拓展:JVM 项目的依赖引入方式不止"手工下 jar"一种——手工 jar + Ant(wget 放入 lib、build.xml 声明路径,适合 Ant 工程与离线环境,本文采用);Maven(pom.xml 声明 org.apache.texen:texen:1.0,可经 maven-antrun 桥接);Gradle(dependencies 声明同一坐标)。apt 无 Texen 系统包。Texen 的运行入口是 Ant 任务,故本文采用手工 jar + Ant 的原生方式。

四、核心 API/配置

4.1 注册 TexenTask

每个 build.xml 需要先声明类路径并注册任务:<path id="texen.classpath"> 内用 <fileset dir="lib" includes="*.jar"/> 把 3.1 节下载的全部 jar 纳入类路径;再用 <taskdef name="texen" classname="org.apache.texen.ant.TexenTask" classpathref="texen.classpath"/> 把 TexenTask 注册为 <texen> 任务名。完整写法见 5.1 节 build.xml(可直接复制)。

4.2 <texen> 任务属性速览

四个必填属性:controlTemplate(控制模板文件名)、templatePath(模板目录,useClasspath=false 时必填)、outputDirectory(产物目录)、outputFile(报告文件名)。常用可选项:inputEncoding/outputEncoding(字符集,建议显式设为 UTF-8)、contextProperties(属性文件注入)、useClasspath(模板改由 classpath 加载)。全部 11 个属性的逐项实测行为(含缺失报错原文)见《Apache Texen 常用 CLI 命令与工具详解》。

4.3 控制模板驱动模型

控制模板里最关键的一个调用:$generator.parse("HtmlTemplate.vm", $outputFile, "planet", $planet)——第 1 参是工人模板名(相对 templatePath 或 classpath),第 2 参是输出文件名(相对 outputDirectory),第 3/4 参把"变量名=变量值"注入工人模板上下文(工人模板内用 $planet 访问)。两参数形态 $generator.parse("about.txt.vm", "about.txt") 也存在:渲染结果直接写入输出文件。控制模板自身渲染的输出不会丢弃,而是写入 outputFile 指定的报告文件;工人模板上下文继承控制模板的全部变量(实测有效)。

4.4 contextProperties 属性注入

在 build.xml 中加 contextProperties="build.properties" 后,属性文件的键值会注入模板上下文。实测三类自动转换:数字串转 Integer(maxDepth = 3)、true/false 转 Boolean(verbose = true)、键名以 file.contents 结尾时把整个文件内容作为字符串注入且键名去掉该后缀(license.file.contents = LICENSE.txt → $license,实测 license 文本出现在每个生成文件的头部)。

五、集成示例

三个示例均放在 /root/texen-demo/ 下,可独立执行;示例一、二使用推荐组合(lib 下 4 个 jar)。每个示例给出全部文件内容,逐字可复现。

5.1 示例一:行星 HTML 页面生成

目标:一个控制模板循环 3 个行星名,调用工人模板生成 3 个 HTML 页面。工程目录 /root/texen-demo/v17-html/(lib/ 放 3.1 节 4 个 jar,templates/ 放两个模板),以下为全部文件内容。

build.xml(完整文件):

<?xml version="1.0" encoding="UTF-8"?>
<project name="HtmlGenerator" default="main" basedir=".">
  <description>Apache Texen 官方示例:行星 HTML 页面生成器(Velocity 1.7)</description>

  <!-- Texen 运行所需 jar:texen-1.0.jar + velocity-1.7.jar + commons-collections-3.2.2.jar + commons-lang-2.6.jar -->
  <path id="texen.classpath">
    <fileset dir="lib" includes="*.jar"/>
  </path>

  <!-- 注册 TexenTask -->
  <taskdef name="texen" classname="org.apache.texen.ant.TexenTask" classpathref="texen.classpath"/>

  <target name="main" description="生成 Earth/Mars/Venus 三个 HTML 页面">
    <echo message="+------------------------------------------+"/>
    <echo message="|           Generating HTML pages!         |"/>
    <echo message="+------------------------------------------+"/>
    <texen
      controlTemplate="Control.vm"
      outputDirectory="output"
      templatePath="templates"
      outputFile="generation.report"/>
  </target>

  <target name="clean" description="清空 output 目录">
    <delete dir="output"/>
  </target>
</project>

templates/Control.vm(控制模板,完整文件):

#*
 * Control.vm —— 控制模板:驱动整个生成过程。
 * 遍历行星列表,逐个调用 HtmlTemplate.vm 生成 HTML 页面。
 *#
#set ($Planets = ["Earth", "Mars", "Venus"])
#foreach ($planet in $Planets)
#set ($outputFile = $strings.concat([$planet, ".html"]))
$generator.parse("HtmlTemplate.vm", $outputFile, "planet", $planet)
#end

templates/HtmlTemplate.vm(工人模板,完整文件):

#*
 * HtmlTemplate.vm —— 工人模板:由控制模板调用,产出单个 HTML 页面。
 *#
#set ($bgcolor = "#ffffff")
<html>
<head>
<title>
Everything you wanted to know about $planet!
</title>
</head>
<body bgcolor="$bgcolor">
$planet is a great place to live!
</body>
</html>

执行生成(实测):

cd /root/texen-demo/v17-html && ant main

回显(实测节选,3 行 [echo] 横幅略):

Buildfile: /root/texen-demo/v17-html/build.xml

main:
    [texen] templates
    [texen] Generating to file /root/texen-demo/v17-html/output/generation.report

BUILD SUCCESSFUL
Total time: 0 seconds

核对产物(实测):

ls output/ && cat output/Earth.html
Earth.html
Mars.html
Venus.html
generation.report

<html>
<head>
<title>
Everything you wanted to know about Earth!
</title>
</head>
(后略,Mars.html/Venus.html 结构相同,仅行星名替换)

三个 HTML 已生成;generation.report 仅 4 字节(控制模板只做编排,报告为 4 个空行,实测 cat -A 确认),报告文件行为详见 6.3 节。

5.2 示例二:实体代码生成(POJO + 建表 SQL)

目标:按内置实体清单批量生成 Java POJO 与建表 SQL,并演示 contextProperties、$strings 工具与 $files.mkdir 建目录。工程目录 /root/texen-demo/v17-model-utf8/(目录名带 -utf8 后缀表示显式配置 UTF-8 编码;lib/ 放 3.1 节 4 个 jar,templates/ 放 4 个模板,根目录放 build.properties 与 LICENSE.txt)。

build.xml(完整文件):

<?xml version="1.0" encoding="UTF-8"?>
<project name="EntityGeneratorUtf8" default="main" basedir=".">
  <description>Apache Texen 实体代码生成示例:POJO + 建表 SQL(Velocity 1.7,UTF-8 编码版)</description>

  <path id="texen.classpath">
    <fileset dir="lib" includes="*.jar"/>
  </path>

  <taskdef name="texen" classname="org.apache.texen.ant.TexenTask" classpathref="texen.classpath"/>

  <target name="main" description="按实体模型生成 POJO、建表 SQL 与 about.txt(UTF-8 编码,logFile 记录引擎日志)">
    <texen
      controlTemplate="Control.vm"
      outputDirectory="output"
      templatePath="templates"
      outputFile="generation.report"
      inputEncoding="UTF-8"
      outputEncoding="UTF-8"
      logFile="texen.log"
      contextProperties="build.properties"/>
  </target>

  <target name="clean" description="清空 output 目录">
    <delete dir="output"/>
    <delete file="texen.log"/>
  </target>
</project>

⚠️ 注意:inputEncoding/outputEncoding 两个属性不能省。若去掉它们再跑同一工程,中文模板与产物会变成问号乱码(对照实测回显见 6.1 节)。logFile 属性按字面含义应记录引擎日志,但实测并不产生文件(见 6.4 节)。

build.properties(完整文件):

# 实体生成器全局配置(TexenTask 的 contextProperties 指向本文件)
# 包名:模板里以 $appPkg 引用
appPkg = com.quzuo.demo
# 数字串属性:Texen 会转成 Integer 再放入上下文
maxDepth = 3
# true/false 属性:Texen 会转成 Boolean 再放入上下文
verbose = true
# file.contents 特殊用法:把 LICENSE.txt 整个文件内容作为 $license 注入上下文
license.file.contents = LICENSE.txt

LICENSE.txt(完整文件):

Copyright 2026 quzuo.net Demo. Licensed under the Apache License, Version 2.0.
本文件内容会通过 license.file.contents 属性注入模板上下文($license)。

templates/Control.vm(完整文件):

#*
 * Control.vm —— 控制模板:驱动 POJO 与建表 SQL 的批量生成。
 * $appPkg / $maxDepth / $verbose / $license 来自 contextProperties(build.properties)。
 *#
#set ($pkgPath = $strings.getPackageAsPath($appPkg))
## Texen 不会自动创建嵌套输出目录:$files.mkdir 相对进程启动目录生效,
## 因此路径要带上 output/ 前缀(与 build.xml 的 outputDirectory 对应)
$files.mkdir("output/${pkgPath}")
$files.mkdir("output/sql")
#set ($entities = [
  {"name": "user",       "table": "t_user",       "columns": ["id:bigint", "name:varchar", "email:varchar", "created_at:datetime"]},
  {"name": "order_item", "table": "t_order_item", "columns": ["id:bigint", "user_id:bigint", "amount:decimal", "status:integer"]}
])
#foreach ($e in $entities)
  #set ($className = $strings.removeAndHump($e.name))
  ## 生成 Java POJO 到 output/com/quzuo/demo/ 下
  #set ($outputFile = "${pkgPath}${className}.java")
  $generator.parse("JavaClass.vm", $outputFile, "entity", $e)
  ## 生成建表 SQL 到 output/sql/ 下
  #set ($outputFile = "sql/${className}.sql")
  $generator.parse("Sql.vm", $outputFile, "entity", $e)
#end
## 两参数形态的 $generator.parse:模板渲染结果直接写入指定文件
$generator.parse("about.txt.vm", "about.txt")

templates/JavaClass.vm(完整文件):

#*
 * JavaClass.vm —— 实体 POJO 模板。
 * $entity 由控制模板通过 $generator.parse 的第 3/4 参数注入。
 *#
#set ($fieldTypes = {"bigint": "Long", "varchar": "String", "datetime": "LocalDateTime", "decimal": "java.math.BigDecimal", "integer": "Integer"})
$license
package $appPkg;

import java.time.LocalDateTime;

/**
 * $entity.name 对应实体类,表名:$entity.table。
 * 由 Apache Texen 自动生成(生成时间:$now),请勿手工修改。
 */
public class $className {

#foreach ($col in $entity.columns)
#set ($parts = $strings.split($col, ":"))
#set ($fieldName = $parts[0])
#set ($fieldType = $fieldTypes.get($parts[1]))
    /** $parts[1] 类型字段 */
    private $fieldType $fieldName;

#end
#foreach ($col in $entity.columns)
#set ($parts = $strings.split($col, ":"))
#set ($fieldName = $parts[0])
#set ($fieldType = $fieldTypes.get($parts[1]))
#set ($capName = $strings.capitalizeFirstLetter($fieldName))
    public $fieldType get$capName() {
        return $fieldName;
    }

    public void set$capName($fieldType $fieldName) {
        this.$fieldName = $fieldName;
    }

#end
}
/* 生成参数演示:maxDepth=$maxDepth(属性值数字串被转成 Integer),verbose=$verbose(属性值 true/false 被转成 Boolean) */

templates/Sql.vm(完整文件):

#*
 * Sql.vm —— 建表 SQL 模板。
 * 注意:#set/#if/#end 所在行行尾的换行会被 Velocity 吞掉,
 * 因此每行 SQL 先拼到 $line 变量,再单独用 "$line" 引用输出。
 *#
-- $entity.table 建表脚本(Apache Texen 生成于 $now)
CREATE TABLE $entity.table (
#foreach ($col in $entity.columns)
#set ($parts = $strings.split($col, ":"))
#set ($line = "    $parts[0] $parts[1]")#if ($foreach.hasNext())#set ($line = "$line,")#end
$line
#end
);

templates/about.txt.vm(完整文件):

本目录由 Apache Texen 自动生成。
生成时间:$now
输出目录:$outputDirectory
包名:$appPkg

执行生成(实测):

cd /root/texen-demo/v17-model-utf8 && ant main
Buildfile: /root/texen-demo/v17-model-utf8/build.xml

main:
    [texen] templates
    [texen] Using contextProperties file: /root/texen-demo/v17-model-utf8/build.properties
    [texen] Generating to file /root/texen-demo/v17-model-utf8/output/generation.report

BUILD SUCCESSFUL
Total time: 0 seconds

核对产物清单(实测,find output -type f | sort 输出):output/ 下共 6 个文件——about.txt、generation.report、com/quzuo/demo/ 下的 OrderItem.java 与 User.java、sql/ 下的 OrderItem.sql 与 User.sql,与 5.2 节目录设计完全对应。

再验证 logFile 属性的实际效果(实测:texen.log 并未生成,回显 ls: cannot access '/root/texen-demo/v17-model-utf8/texen.log': No such file or directory,classpath 加载方式下同样无日志文件,详见 6.4 节)。

生成物 User.java 输出节选(实测首尾,license 注入、包名、类注释均来自模板与 contextProperties):


Copyright 2026 quzuo.net Demo. Licensed under the Apache License, Version 2.0.
本文件内容会通过 license.file.contents 属性注入模板上下文($license)。

package com.quzuo.demo;

import java.time.LocalDateTime;

/**
 * user 对应实体类,表名:t_user。
 * 由 Apache Texen 自动生成(生成时间:Fri Oct 16 03:04:50 GMT 2026),请勿手工修改。
 */
public class User {

    /** bigint 类型字段 */
    private Long id;

    /** varchar 类型字段 */
    private String name;

    (中段略)
}
/* 生成参数演示:maxDepth=3(属性值数字串被转成 Integer),verbose=true(属性值 true/false 被转成 Boolean) */

字段与 getter/setter 部分实测可见 private LocalDateTime created_at;(列名 created_at 直接成为字段名),访问器由模板循环产出。生成物 User.sql(实测全文,行格式正确):


-- t_user 建表脚本(Apache Texen 生成于 Fri Oct 16 03:04:50 GMT 2026)
CREATE TABLE t_user (
    id bigint,
    name varchar,
    email varchar,
    created_at datetime
);

生成物 about.txt 中 $now/$outputDirectory/$appPkg 均被正确替换(全文回显见 6.1 节修复后对照)。

5.3 示例三:Velocity Engine 2.4.1 兼容性对照

目标:验证 Texen 1.0 能否搭配当前活跃的 Velocity 2.4.1,给升级评估一个实测结论。把 3.2 节的 3 个 jar 换进 lib(或放独立工程 lib/),模板与 build.xml 与 5.1 完全相同,执行结果(实测输出节选,3 行 [echo] 横幅略):

Buildfile: /root/texen-demo/v241-html/build.xml

main:
    [texen] templates
    [texen] SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    [texen] Defaulting to no-operation (NOP) logger implementation
    [texen] See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
    [texen] Generating to file /root/texen-demo/v241-html/output/generation.report

BUILD SUCCESSFUL
Total time: 0 seconds

看似成功,产物却异常(实测):3 个 HTML 文件没有生成,output/ 下只剩报告:

ls output/
generation.report

根因:Velocity 2.4.1 删除了 org.apache.velocity.util.StringUtils 的大多数实例方法,控制模板里的 $strings.concat([$planet, ".html"]) 失效导致输出文件名变成空串,$generator.parse 两参形态把内容全部并入报告(generation.report 达 470 字节,实测可见 Earth/Mars/Venus 三个 HTML 的完整内容依次相连)。

更严重的兼容问题:在 2.4.1 下使用 contextProperties 属性会直接崩溃(实测):

BUILD FAILED
/root/texen-demo/v241-ctxprops/build.xml:19: java.lang.NoSuchMethodError: 'java.lang.String[] org.apache.velocity.util.StringUtils.split(java.lang.String, java.lang.String)'
        at org.apache.texen.ant.TexenTask.setContextProperties(TexenTask.java:305)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)

兼容改造后的可行写法(实测通过):控制模板不用 $strings,改用 VTL 原生拼接或 String 方法,且不使用 contextProperties 属性。执行回显(实测节选;原文中另有 3 行 SLF4J NOP 提示,属 2.4.1 无绑定器的正常输出,此处略去):

Buildfile: /root/texen-demo/v241-model/build.xml

main:
    [texen] templates
    [texen] Generating to file /root/texen-demo/v241-model/output/generation.report

BUILD SUCCESSFUL
Total time: 0 seconds

⚠️ 注意:2.4.1 下即使跑通,产物细节也有差异——建表 SQL 的行间会出现空行(Velocity 2.x 换行处理与 1.7 不同,实测 User.sql 每个列定义之间多一个空行,SQL 本身仍有效)。结论:Texen 1.0 建议固定搭配 Velocity 1.7 使用。

六、注意事项(全部来自实测,可直接对照处理)

6.1 中文乱码:三处来源与修复

乱码来源一:未设置 inputEncoding/outputEncoding。同一套模板与 build.xml 去掉两个编码属性后再跑(实测对照工程,产物 about.txt):

???????????? Apache Texen ???????????????
???????????????Fri Oct 16 03:03:01 GMT 2026
???????????????/root/texen-demo/v17-model/output
?????????com.quzuo.demo

把 inputEncoding="UTF-8" outputEncoding="UTF-8" 加回 build.xml 再跑(实测,即 5.2 工程的产物):

本目录由 Apache Texen 自动生成。
生成时间:Fri Oct 16 03:04:50 GMT 2026
输出目录:/root/texen-demo/v17-model-utf8/output
包名:com.quzuo.demo

乱码来源二:运行环境的 LANG 为空时,JVM 默认编码是 ASCII(file.encoding=ANSI_X3.4-1968),contextProperties 的 file.contents 读文件会注入乱码。实测 license 行字节对照:

=== LANG 为空时 license 行字节(EF BF BD 重复,即 U+FFFD 替换符)===
0000000 357 277 275 357 277 275 357 277 275 357 277 275 357 277 275 357
=== LANG=C.UTF-8 时 license 行 ===
本文件内容会通过 license.file.contents 属性注入模板上下文($license)。

修复方法(实测有效):执行 ant 前 export LANG=C.UTF-8。乱码来源三(ant 控制台回显自身乱码,-projecthelp 中文 description 变问号)同由 LANG 为空引起,此修复同样有效。

6.2 嵌套输出目录不会自动创建

Texen 只保证 outputDirectory 本身存在,模板若直接向 output/com/quzuo/demo/User.java 写文件会失败(实测,模板去掉 $files.mkdir 后复现):非 debug 模式报 Exception thrown by 'generator.parse'. For more information consult the velocity log, or invoke ant with the -debug flag.;加 -debug 可见根因 Caused by: java.io.FileNotFoundException: /root/texen-demo/v17-mkdir-repro/output/com/quzuo/demo/User.java (No such file or directory),出栈在 org.apache.texen.Generator.getWriter(完整实测栈见《Apache Texen 常用 CLI 命令与工具详解》4.4)。

解决方案(5.2 节 Control.vm 已采用):生成前先在控制模板里建目录:

$files.mkdir("output/${pkgPath}")
$files.mkdir("output/sql")

💡 说明:$files.mkdir 的路径相对 ant 进程的启动目录(执行 ant 命令所在目录)解析,不带 output/ 前缀时会建到工程根目录下,导致目录建了但产物路径仍对不上。其内部实现是 mkdirs(),多级目录一次可建。

6.3 控制模板渲染结果会写入报告文件

outputFile 指定的 generation.report 不是空占位:控制模板自身渲染的所有输出都会写进去。实测两种表现——控制模板只做编排时报告近乎为空(5.1 节 report 仅 4 字节);控制模板渲染失效时工人内容也会并入报告(5.3 节 v241-html 的 report 达 470 字节,含三个 HTML 全文);工人模板若用两参数 $generator.parse("X.vm") 形态调用(不给输出文件名),其渲染内容同样全部并入报告。编排型控制模板务必保证 #set/#foreach 行首行尾干净,避免杂质写进报告。

6.4 logFile 属性无实际效果

logFile="texen.log"(5.2 工程配置)执行后并未产生日志文件(实测回显见 5.2 节末尾);classpath 加载方式(logFile="texen-cp.log")同样无日志文件产生(实测见《Apache Texen 常用 CLI 命令与工具详解》4.11)。需要排障信息时用 ant -debug,不要依赖 logFile。

6.5 Velocity 1.7 的换行吞噬

VTL 中 #set/#if/#end 所在行行尾的换行会被 Velocity 1.7 吞掉。模板里直接书写多行 SQL 时,列定义会挤在一行。5.2 节 Sql.vm 的写法(每行先拼到 $line 变量、再单独一行输出 $line)是实测可用的规避模式;该问题在 Velocity 2.4.1 下方向相反(多出空行,见 5.3 节)。

七、参考链接