Spring Boot + Maven 接入 Allatori:从普通打包到可选混淆发布

本文记录一次真实工程的接入过程,并提供可手动迁移的配置。目标是在保留原有打包方式的同时,通过 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> 节点。

复制后首先修改两处:

  1. allatori.home:自己的 Allatori 安装目录。

  2. 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,并清除上次构建留下的混淆产物。业务代码无需为切换打包方式而修改。

十一、下次接入时按这个顺序手动操作

  1. 确认原工程已经能生成并运行普通 Spring Boot 可执行 JAR。

  2. 安装 Allatori,确认 lib/allatori.jar 路径和 Maven 实际使用的 JDK。

  3. 在应用模块根目录新增 allatori/allatori.xml,复制第五节完整配置。

  4. 将第六节 profile 合并到 POM 的 <profiles> 中。

  5. 修改工具路径和启动类,确认现有 Boot 插件的版本及普通 repackage 执行。

  6. 检查 XML 里的 -obfuscated 与两处 classifier 一致,保留 forceCreation。

  7. 执行 mvn clean package -Pobfuscate,核对实际配置替换结果和插件执行顺序。

  8. 对最终混淆包做接口、数据访问、反射及协议行为验证。

  9. 归档混淆包和本次映射文件,再进行发布。

参考资料

相关推荐
不一样的故事1261 小时前
Prompt 工程(Prompt Engineering)
前端·人工智能·python
我命由我123451 小时前
6-2.Python 异常处理 - 异常捕获(捕获指定异常、常见异常类型、异常信息)
java·运维·开发语言·python·http·https·java-ee
zh路西法1 小时前
【模型式强化学习】(三):MBPO——短轨迹生成与模型数据驱动的策略优化
python·强化学习·pe·sac·pets·mbpo·mbrl
Angeldream1231 小时前
第 9 章 医学影像项目实战
大数据·图像处理·人工智能·python·医学影像处理
ZhengEnCi1 小时前
一亿条数据取前100条完全指南:从全量排序到分块流式最小堆的4种解法实测
python·算法·排序算法
2601_967979121 小时前
AccessControl 5.4 macOS Python 3.9 版本安装包
python·macos·security·accesscontrol·permissions
染指11101 小时前
157.Agent-LangGraph-工具(实现ReAct Agent模式)
开发语言·人工智能·python·langchain·agents
程序猿乐锅1 小时前
【从0-1学习Python | 第九篇】继承
开发语言·后端·python·学习·fastapi
青少儿编程课堂1 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:位运算实战——二进制权限与最大异或解析
c++·python·算法·bfs·信息学竞赛