本文记录一次真实工程的接入过程,并提供可手动迁移的配置。目标是在保留原有打包方式的同时,通过 Maven profile 额外生成混淆后的 Spring Boot 可执行 JAR。
一、先明确要得到什么
日常开发继续执行:
mvn clean package
需要混淆时执行:
mvn clean package -Pobfuscate
启用后生成两个可执行包:
target/
├── demo-service-1.0.0.jar
├── demo-service-1.0.0-obfuscated.jar
└── allatori/
├── allatori.xml
├── allatori-log.xml
└── lib/
这里的 demo-service-1.0.0 代表自己的 ${project.build.finalName},通常由 artifactId 和 version 组成,也可以被 <finalName> 覆盖。混淆流程只处理当前模块编译出的业务类;依赖 JAR 保持原样。
本文按单模块、jar 类型的 Spring Boot 应用编写。多模块项目应把配置放到最终可执行应用模块中;如果还要混淆其他业务模块,需要另行设计输入范围。WAR、自定义打包布局、Shade 或 Assembly 等插件已经参与构建的项目,应先核对自己的执行顺序。
二、这次接入使用的环境
| 项目 | 实际使用值 |
|---|---|
| Spring Boot | 2.5.6 |
| Maven | 3.9.15 |
| 运行 Maven 的 JDK | 17.0.18 |
| 业务代码目标字节码 | Java 8 |
| Allatori | 9.9 Demo |
| Allatori 安装位置 | D:/Allatori-9.9-Demo |
这些是本次验证的版本组合,不表示需要把自己的 Spring Boot 降级到 2.5.6。迁移时保留工程已有的 Spring Boot 版本,并检查所用 JDK、插件和 Allatori 的兼容性;尤其不要把旧版 Boot 插件直接复制到新版 Boot 工程。
先确认工具可用:
java -version
mvn -version
Test-Path 'D:/Allatori-9.9-Demo/lib/allatori.jar'
mvn -version 显示的 Java 才是后面运行 Allatori 的 Java。<java.version>1.8</java.version> 是工程编译配置的一部分,不代表 Maven 本身运行在 JDK 8 上。
Allatori 是构建工具,不需要添加到应用的 <dependencies>,也不需要随应用部署。Demo 版适合评估;本机附带许可标明仅供评估,运行时还可能出现演示提示,正式交付应使用相应授权的版本。
三、为什么要先混淆,再做 Spring Boot 重打包
Spring Boot 可执行 JAR 通常含有以下结构:
BOOT-INF/classes/ 业务类及资源
BOOT-INF/lib/ 应用依赖
META-INF/MANIFEST.MF 启动清单
本方案选择先创建一个只含本模块代码和资源的业务 JAR,让 Allatori 处理它,再交给 spring-boot:repackage 组装可执行包。这样能把业务代码与第三方依赖的处理边界保持清楚。
流程如下:
Mermaid 未开启的 Markdown 阅读器可能显示图表源码;上面的阶段顺序仍可直接阅读。
phase 表示 Maven 执行阶段,goal 表示插件要执行的动作,execution 则把具体动作绑定到阶段。Maven 在执行 package 前会先走完前面的阶段。同阶段中还需要注意最终有效 POM 的插件顺序,因此本文把创建业务 JAR 的插件放在执行 Allatori 的插件前面。Maven 生命周期说明
四、第一步:检查现有 Spring Boot 打包配置
先确认普通打包已经能生成可执行 JAR:
mvn clean package
java -jar target/demo-service-1.0.0.jar
启动验证应在测试环境进行,因为项目可能在启动时连接数据库、绑定 UDP 端口或启动定时任务。
工程通常已经在 <build><plugins> 中声明了 spring-boot-maven-plugin。使用 spring-boot-starter-parent 时,相关版本和 repackage 执行通常由父 POM 管理;如果使用的是企业父 POM,只有导入 Spring Boot BOM,并不等于已经启用了可执行包重打包。
对于尚未绑定普通重打包的工程,需要在已有 <build><plugins> 中补上对应执行。例如,下面是 Boot 2.5.6 的写法;版本应换成自己的 Boot 版本:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.6</version>
<executions>
<execution>
<id>repackage</id>
<phase>package</phase>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
如果相同执行已经存在,就合并配置,不要再添加一份不同 ID 的普通重打包执行。下面的混淆 profile 会复用这个插件,并增加另一个带 classifier 的执行。Spring Boot 2.5.6 Maven 插件说明
五、第二步:新增 Allatori 配置文件
在工程根目录建立 allatori 文件夹,与 pom.xml 平级:
工程根目录/
├── pom.xml
├── allatori/
│ └── allatori.xml
└── src/
不要把这个构建配置放到 src/main/resources,避免它被当作应用资源发布。allatori.jar 继续留在外部安装目录。
将下面内容保存到 allatori/allatori.xml:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<!-- Maven filters this template into target/allatori/allatori.xml. -->
<input>
<jar in="../@project.build.finalName@-obfuscated.jar"
out="../@project.build.finalName@-obfuscated.jar"/>
</input>
<classpath>
<jar name="lib/*.jar"/>
</classpath>
<keep-names>
<!-- Keep packages/classes for Spring scanning, MyBatis XML and protocol reflection.
Keep fields for JSON, SQL properties and name-based dependency injection.
Rename only unannotated private methods in this initial integration. -->
<class access="private+">
<field access="private+"/>
<method access="package+" parameters="keep"/>
<method template="@* *(**)" parameters="keep"/>
</class>
</keep-names>
<property name="log-file" value="allatori-log.xml"/>
<property name="string-encryption" value="enable"/>
<property name="string-encryption-type" value="fast"/>
<property name="control-flow-obfuscation" value="enable"/>
<property name="local-variables-naming" value="keep-parameters"/>
<property name="generics" value="keep"/>
<property name="inner-classes" value="keep"/>
<!-- Protocol conversion reflects over fields; retain declaration order. -->
<property name="member-reorder" value="disable"/>
<property name="synthetize-methods" value="disable"/>
<property name="synthetize-fields" value="disable"/>
<property name="finalize" value="disable"/>
<property name="update-resource-names" value="disable"/>
<property name="update-resource-contents" value="disable"/>
</config>
1. 输入输出路径为什么这样写
<jar in="../@project.build.finalName@-obfuscated.jar"
out="../@project.build.finalName@-obfuscated.jar"/>
源文件是模板;真正执行时使用的是复制到 target/allatori/allatori.xml 的文件。@project.build.finalName@ 由 Maven 先替换为实际产物名称。
Allatori 的相对路径以配置文件所在目录为基准,因此 ../ 指向 target,lib/*.jar 指向 target/allatori/lib。输入输出使用相同路径,表示覆盖前一步专门创建的待混淆包。
2. 为什么首期保留这么多名称
<class access="private+">
<field access="private+"/>
<method access="package+" parameters="keep"/>
<method template="@* *(**)" parameters="keep"/>
</class>
这组规则保留全部类和包的名称、全部字段名、非 private 方法名,以及带注解的方法名。未带注解的 private 方法允许改名。parameters="keep" 配合 keep-parameters 保留参数名称。
这样做是为了先建立可工作的混淆流程:Spring 扫描和 Bean 命名、MyBatis XML 的 namespace 与属性、JSON 字段、反射中的类名或字段名,都可能依赖原来的名称。本工程还有按实体类名后缀分支和反射字段的协议转换代码,不能直接全量改名。
这是一份保守起点,不是所有 Spring Boot 应用都能直接获得兼容性保证。SpEL、脚本调用、Java 序列化、JNI 或其他动态调用仍需结合自己的工程验证。之后可以针对内部实现逐步缩小保留范围。
3. 其他配置解决什么问题
| 配置 | 本文采用的策略 |
|---|---|
string-encryption |
启用字符串加密,增加阅读反编译结果的成本 |
control-flow-obfuscation |
启用控制流混淆 |
generics、inner-classes |
保留泛型和内部类信息 |
member-reorder |
关闭成员重排,保护依赖字段顺序的代码 |
synthetize-methods、synthetize-fields |
不额外给成员添加 synthetic 标记 |
finalize |
不把类自动改为 final,避免影响代理 |
update-resource-names、update-resource-contents |
不自动改写资源名称或内容 |
log-file |
保存重命名映射,供堆栈还原使用 |
保留名称仍然可以进行字符串和控制流混淆。混淆也不等于秘密管理,密码、令牌和私钥应继续由独立的配置或密钥机制管理。各选项的准确语法与取值以所用版本的 Allatori 官方配置文档 为准。
六、第三步:在 pom.xml 中增加混淆 profile
把下面的 <profiles> 放在 <project> 里面,与 <dependencies>、原有 <build> 平级。如果已经有 <profiles>,只合并里面的 <profile>,不要创建第二个 <profiles> 节点。
复制后首先修改两处:
-
allatori.home:自己的 Allatori 安装目录。 -
mainClass:自己的 Spring Boot 启动类全限定名,示例为com.example.DemoApplication。
下面的配置在本工程配置基础上显式补充了资源过滤分隔符,以及资源、JAR 插件版本,便于迁移到父 POM 配置不同的项目。Boot 插件版本继续由工程已有的版本管理或上一节的插件声明提供。
<profiles>
<profile>
<id>obfuscate</id>
<properties>
<allatori.home>D:/Allatori-9.9-Demo</allatori.home>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>prepare-allatori-config</id>
<phase>process-resources</phase>
<goals><goal>copy-resources</goal></goals>
<configuration>
<outputDirectory>${project.build.directory}/allatori</outputDirectory>
<useDefaultDelimiters>false</useDefaultDelimiters>
<delimiters><delimiter>@</delimiter></delimiters>
<resources>
<resource>
<directory>allatori</directory>
<filtering>true</filtering>
<includes><include>allatori.xml</include></includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<id>prepare-allatori-classpath</id>
<phase>process-resources</phase>
<goals><goal>copy-dependencies</goal></goals>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/allatori/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>prepare-obfuscated-jar</id>
<phase>prepare-package</phase>
<goals><goal>jar</goal></goals>
<configuration>
<classifier>obfuscated</classifier>
<!-- Always rebuild: Allatori modifies this jar in place. -->
<forceCreation>true</forceCreation>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>run-allatori</id>
<phase>prepare-package</phase>
<goals><goal>exec</goal></goals>
<configuration>
<executable>${java.home}/bin/java</executable>
<arguments>
<argument>-Xmx512m</argument>
<argument>-jar</argument>
<argument>${allatori.home}/lib/allatori.jar</argument>
<argument>${project.build.directory}/allatori/allatori.xml</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>repackage-obfuscated</id>
<phase>package</phase>
<goals><goal>repackage</goal></goals>
<configuration>
<classifier>obfuscated</classifier>
<mainClass>com.example.DemoApplication</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
这五个插件各自承担一个具体任务:
| 插件 | 执行阶段 | 作用 |
|---|---|---|
maven-resources-plugin |
process-resources |
复制模板并替换构建变量 |
maven-dependency-plugin |
process-resources |
复制 compile/runtime 依赖作为分析 classpath,不含测试依赖 |
maven-jar-plugin |
prepare-package |
创建带 obfuscated 分类的独立业务 JAR |
exec-maven-plugin |
prepare-package |
启动外部 Java 进程运行 Allatori |
spring-boot-maven-plugin |
package |
将混淆后的业务 JAR 重打包为可执行 JAR |
为什么 classifier 必须一致
maven-jar-plugin 和混淆包的 spring-boot-maven-plugin 都使用:
<classifier>obfuscated</classifier>
前者创建这个分类的产物,后者读取同一分类的产物再重打包。allatori.xml 里的 -obfuscated.jar 也要与它匹配。只改一处可能导致文件找不到,或重打包的不是混淆结果。
为什么 forceCreation 不能删
Allatori 会就地覆盖待混淆 JAR。若不强制重新创建,连续执行不带 clean 的打包时,JAR 插件可能复用上次产物,造成重复混淆或把已经重打包的文件再次作为输入。
<forceCreation>true</forceCreation>
这保证每次先从原始编译结果重新创建业务包,再执行混淆。
exec 插件实际运行了什么
以本文环境为例,它大致等价于:
java -Xmx512m -jar D:/Allatori-9.9-Demo/lib/allatori.jar target/allatori/allatori.xml
-Xmx512m 只限制混淆工具进程的最大堆内存,不会成为部署应用的 JVM 参数。大型项目如果出现内存不足,可针对这个值调整。
${java.home}/bin/java 使工具使用 Maven 当前 JDK。Windows 上通常可以正常解析对应可执行程序;跨平台构建仍应在实际构建机器验证。
七、第四步:打包、检查并运行
1. 先检查纯构建链路
mvn clean package -Pobfuscate -DskipTests
这条命令会编译并打包,但跳过测试执行。它适合先确认路径和插件配置,不能代替业务验证。
2. 再执行包含测试的打包
mvn clean package -Pobfuscate
检查构建日志中的顺序:
prepare-allatori-config
prepare-allatori-classpath
prepare-obfuscated-jar
run-allatori
Obfuscation completed
repackage
repackage-obfuscated
BUILD SUCCESS
日志之间还会出现正常的编译、测试和普通 JAR 创建步骤。如果工程使用了 testFailureIgnore=true,即使存在测试失败也可能继续打包,要额外检查 target/surefire-reports;建议发布构建不要忽略测试失败。
3. 检查配置替换结果
打开 target/allatori/allatori.xml,输入路径应类似:
<jar in="../demo-service-1.0.0-obfuscated.jar"
out="../demo-service-1.0.0-obfuscated.jar"/>
如果仍然存在 @project.build.finalName@ 或 ${project.build.finalName},先排查资源过滤,不要继续把问题当成 Allatori 的类加载故障。
4. 运行最终混淆包
java -jar target/demo-service-1.0.0-obfuscated.jar
使用自己的实际文件名,并在测试环境提供正确的数据库及外部资源配置。部署文件是最终 .jar;Spring Boot 可能生成的 .jar.original 是重打包前的备份文件。
5. 验证确实处理了业务代码
可以用 jar tf 检查包结构:
jar tf target/demo-service-1.0.0-obfuscated.jar
应包含业务类、依赖及 Boot 启动器。Boot 2.5.6 本次产物的清单中,Main-Class 为 org.springframework.boot.loader.JarLauncher,Start-Class 为应用启动类;其他 Boot 版本可能不同,应按自己的版本判断。
再查看 target/allatori/allatori-log.xml。本文规则保留类名和字段名,所以出现 0 classes renamed、0 fields renamed 不代表混淆没有生效。应结合映射、业务 class 字节码变化以及反编译结果判断;包体积变化本身也不足以证明混淆正确。
八、一个容易忽略的事实:普通单元测试通常测不到混淆后的代码
Maven 的 test 阶段发生在 prepare-package 之前。因此默认单元测试使用的是 target/classes 里的原始编译结果,即使 mvn clean package -Pobfuscate 的测试全通过,也不能证明最终混淆包的运行行为正确。
还需要针对最终产物做验证:
| 验证项 | 建议检查内容 |
|---|---|
| 应用启动 | Bean 创建、自动注入、代理及配置绑定 |
| HTTP 接口 | 参数名称、校验、返回 JSON 结构 |
| 数据访问 | Mapper XML 解析、参数绑定、结果属性映射 |
| 反射逻辑 | 类名字符串、字段名称、注解及必要的成员顺序 |
| 协议或文件格式 | 对同一组固定输入比较混淆前后的结果 |
| 第三方依赖 | 依赖数量、文件内容和版本是否符合预期 |
本次接入于 2026-10-26 完成了以下验证:
-
普通打包和启用混淆的完整打包成功,现有测试 1 项通过。
-
对最终混淆包检查:217 个业务 class 的内容发生变化,仍为 Java 8 目标字节码;101 个依赖 JAR 的内容与普通包一致。
-
从混淆包提取业务类后,使用独立 Java 进程,将它们放在 classpath 前部执行验证,避免误用原始业务 class。
-
混淆后的类通过 TXA 组包/解析、现有 BSI/PWI 回归、Jackson/Fastjson 序列化,以及内存 SQLite 上的 MyBatis 插入、查询和属性绑定验证。
这些结果只覆盖本次实际检查的路径。完整服务启动、串口、UDP 和外部系统联调仍需在对应测试环境进行。
九、这次接入遇到的坑与排查方式
1. 配置里写了变量,Allatori 却把变量当成文件名
首次接入时,模板使用 ${project.build.finalName},复制时却没有被替换,Allatori 因而尝试寻找带有这段字面量的文件。
原因是本工程 Spring Boot 父 POM 使用 @...@ 进行资源过滤。修正为 @project.build.finalName@ 后构建通过。本文可迁移模板还显式声明了 @ 分隔符,减少对父 POM 默认值的依赖。
排查时优先检查 复制后的配置文件,只看源模板看不出替换是否成功。
2. 只写 Allatori 调用,忘记创建待混淆 JAR
prepare-package 时普通默认 JAR 尚未在 package 阶段生成。因此不能在前面的阶段直接读取尚不存在的普通产物。本文专门通过 maven-jar-plugin 提前创建独立的分类产物。
3. 只有输入 JAR,没有依赖 classpath
Allatori 在分析继承、接口和方法关系时需要了解引用的类型。先检查 target/allatori/lib 是否存在所需依赖;缺类警告不能仅通过关闭日志解决。
runtime 范围复制不包含测试依赖或 provided 依赖。如果应用确实需要 provided/system 类型的库参与混淆分析,应有针对性补充 Allatori classpath,并分别确认部署环境如何提供这些库。
4. 混淆后 JSON 字段、MyBatis 或注入失败
先检查保留规则是否被自行缩减,以及运行时依赖的注解、泛型、参数名称是否仍在。对本来通过属性名访问的实体字段全量改名,很容易使 SQL 或序列化行为变化。
5. 协议结果变化,但应用仍能启动
能创建 Bean 不代表协议代码正确。本工程有按类名后缀判断类型、按字段名读取值的行为,因此不仅保留类名和字段名,还关闭了成员重排。这类问题适合用固定报文做混淆前后对比。
6. Windows PowerShell 下 Maven 把属性误认为生命周期阶段
对含点号或路径的 Maven 属性参数加引号,尤其是在命令经由脚本或工具转发时:
mvn clean package -Pobfuscate "-Dallatori.home=D:/Allatori-9.9"
若错误中出现 Unknown lifecycle phase 且后面是属性名的一部分,先检查命令行参数的传递方式。
7. 不同工程插件顺序被父 POM 或其他 profile 改变
生成最终有效 POM 后再判断:
mvn help:effective-pom -Pobfuscate "-Doutput=effective-pom.xml"
重点检查业务 JAR 创建与 Allatori 调用的顺序,以及普通和混淆重打包是否各执行一次。effective-pom.xml 是本地排查文件,不需要随业务发布。
十、发布、持续集成与回退
安装位置通过 Maven 属性覆盖,Windows 示例为:
mvn clean package -Pobfuscate "-Dallatori.home=D:/Allatori-9.9"
Linux 构建机可使用相同方式指定路径:
mvn clean package -Pobfuscate -Dallatori.home=/opt/allatori
CI 构建机需要能读取该安装目录,并具备适用的授权。无须把工具二进制提交进业务仓库。构建缓存可以保存依赖下载,但最终输入产物仍应由本次构建生成。
每次发布归档以下内容:
-
最终混淆 JAR。
-
对应的
allatori-log.xml。 -
本次源码提交、配置版本、JDK 和工具版本信息。
映射日志和产物必须一一对应,不能用下一次构建的映射还原上一次的堆栈。映射文件默认位于 target,下次 clean 会将其删除;发布流水线应在清理前归档到内部构建产物存储,且不要公开分发映射。
需要回到普通构建时,直接使用:
mvn clean package
这会停用混淆 profile,并清除上次构建留下的混淆产物。业务代码无需为切换打包方式而修改。
十一、下次接入时按这个顺序手动操作
-
确认原工程已经能生成并运行普通 Spring Boot 可执行 JAR。
-
安装 Allatori,确认
lib/allatori.jar路径和 Maven 实际使用的 JDK。 -
在应用模块根目录新增
allatori/allatori.xml,复制第五节完整配置。 -
将第六节 profile 合并到 POM 的
<profiles>中。 -
修改工具路径和启动类,确认现有 Boot 插件的版本及普通
repackage执行。 -
检查 XML 里的
-obfuscated与两处 classifier 一致,保留forceCreation。 -
执行
mvn clean package -Pobfuscate,核对实际配置替换结果和插件执行顺序。 -
对最终混淆包做接口、数据访问、反射及协议行为验证。
-
归档混淆包和本次映射文件,再进行发布。
参考资料
-
本机安装包 Maven 示例:
D:/Allatori-9.9-Demo/tutorial/10-maven/mouse-gestures/。