📌一、概述
1. ProGuard简介
背景:
ProGuard 是一个免费的 Java 类文件的压缩,优化,混肴器。它删除没有用的类,字段,方法与属性。使字节码最大程度地优化,使用简短且无意义的名字来重命名类、字段和方法 。
使用场景:
我们在工程应用中经常会遇到核心代码不希望给别人抄袭,但系统是用java开发的,无法避免被反编译的情况,这样可以用代码混淆的方式来解决。
使用原因:
版本一直在更新维护,功能也比较多。
2. 相关资料
官网地址: Java Obfuscator and Android App Optimizer | ProGuard
官方手册: ProGuard
中文手册: ProGuard 最全混淆规则说明_proguard混淆规则_程序员_jasen的博客-优快云博客
中文语法基础: 语法基础——Proguard语法基础_-keepattributes innerclasses_Dij__柯南的博客-优快云博客
Springboot中的使用: Spring boot使用ProGuard实现代码混淆_proguard springboot_Blueeyedboy521的博客-优快云博客
二、配置说明
1. 配置要点
建议逐个java包定义混淆规则,这样思路更清晰
- repository(dao)层需要保存包名和类名,因为Mybatis的xml文件中引用了dao层的接口 ;
- controller层注意在使用@PathVariable、@RequestParam时需要显式声明参数名 ;
- dao层用于映射数据库表的类和controller层映射前台参数的类,都需要保留类成员 ;
- 修改spring的bean命名策略,改成按类的全限定名来命名。
- -entity和dto中的字段要保留命名,否则无法执行拷贝动作
- -service最好用@Service(“xxxService”)指定名字,否则会模糊成a,导致依赖问题
- 多模块依赖的模块最好不要模糊,否则无法依赖正常
2. 使用方案
- 使用proguard.cfg
- 直接在pom文件里配置插件(推荐,快速方便)
3. 在pom里配置的例子
<build>
<plugins>
<!-- ProGuard混淆插件-->
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<version>2.4.0</version>
<executions>
<execution>
<!-- 混淆时刻,这里是打包的时候混淆-->
<phase>package</phase>
<goals>
<!-- 使用插件的什么功能,当然是混淆-->
<goal>proguard</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- 是否将生成的PG文件安装部署-->
<attach>true</attach>
<!-- 是否混淆-->
<obfuscate>true</obfuscate>
<!-- 指定生成文件分类 -->
<attachArtifactClassifier>pg</attachArtifactClassifier>
<options>
<!-- JDK目标版本1.8-->
<option>-target 1.8</option>
<!-- 不做收缩(删除注释、未被引用代码)-->
<!-- proguard会对代码进行优化压缩,他会删除从未使用的类或者类成员变量等,shrink这个功能一般最好别用,所以这里添加了<option>-dontshrink</option>,我就遇到过启动jar的时候不支持压缩jar的问题-->
<option>-dontshrink</option>
<!-- 不做优化(变更代码实现逻辑)-->
<option>-dontoptimize</option>
<!-- 不路过非公用类文件及成员-->
<option>-dontskipnonpubliclibraryclasses</option>
<option>-dontskipnonpubliclibraryclassmembers</option>
<!--不用大小写混合类名机制-->
<option>-dontusemixedcaseclassnames</option>
<!-- 优化时允许访问并修改有修饰符的类和类的成员 -->
<option>-allowaccessmodification</option>
<!-- 确定统一的混淆类的成员名称来增加混淆-->
<option>-useuniqueclassmembernames</option>
<!-- 不混淆所有包名-->
<option>-keeppackagenames</option>
<option>-keepdirectories</option>
<!-- 需要保持的属性:异常,注解等-->
<option>-keepattributes
Exceptions,InnerClasses,Signature,Deprecated,SourceFile,*Annotation*,Synthetic,EnclosingMethod
</option>
<option>-keep class javax.annotation.**</option>
<option>-dontwarn javax.crypto.**</option>
<option>-keep class javax.crypto.**</option>
<option>-keepnames class com.redotsoft.dmp.**</option>
<!-- 此选项将保存接口中的所有原始名称(不混淆)-->
<option>-keepnames interface ** { *; }</option>
<!-- 此选项将保存所有软件包中的所有原始接口文件(不进行混淆)-->
<option> -keep interface * extends * { *; }</option>
<!-- 保留参数名,因为控制器,或者Mybatis等接口的参数如果混淆会导致无法接受参数,xml文件找不到参数-->
<option>-keepparameternames</option>
<!-- 保留枚举成员及方法-->
<option>-keepclassmembers enum * { *; }</option>
<!-- 不混淆所有类,保存原始定义的注释 -->
<option>-keepclassmembers class * {
@com.baomidou.mybatisplus.annotation *;
@org.springframework.context.annotation.Bean *;
@org.springframework.beans.factory.annotation.Autowired *;
@org.springframework.beans.factory.annotation.Value *;
@org.springframework.stereotype.Service *;
@org.springframework.stereotype.Component *;
}</option>
<!-- 不混淆当前包中的public方法、变量等等 -->
<option>-keep class com.redotsoft.dmp.** {
public *; }</option>
</options>
<outjar>${project.build.finalName}-pg.jar</outjar>
<libs>
<lib>${java.home}/lib/rt.jar</lib>
</libs>
<!-- 对什么东西进行加载,这里仅有classes成功,毕竟你也不可能对配置文件及JSP混淆吧-->
<injar>classes</injar>
<!-- 输出目录-->
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
4. 配置项要点说明
- -keepattributes 保持不会混淆的属性
可配置项:Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod
如果你想混淆方法内部的变量名,则需要去掉LocalVariable*Table这个属性
- - keepnames 指定要保留名称的类和类成员,前提是在压缩阶段未被删除。仅用于模糊处理
如果你不想混淆所有类的名称(比如你要暴露给父项目调用),需要这么写
-keepnames class com.redotsoft.dmp.**
- - keep 指定需要保留的类和类成员(作为公共类库,应该保留所有可公开访问的public方法)
如果你想混淆所有的私有内部方法、变量,又希望所有的public公共方法、变量保持不被混淆,需要这么写
-keep class com.redotsoft.dmp.** {public *; }
关于keep命令的一些使用示例
- 保持了类ClassOneOne里面所有public 修饰的成员和方法
-keepclassmembernames class com.dev.demo.one.ClassOneOne {
public *;
}
- 保持了ClassOne里面public 修饰的构造函数
-keep class com.dev.demo.ClassOne {
public <init>();
}
- 保持了类ClassTwoTwo里面public修饰的参数为int的构造函数
-keep class com.dev.demo.two.ClassTwoTwo {
public <init>(int);
}
- 保持了类ClassTwoThree 里面public修饰的方法和private修饰的成员变量
-keepclassmember class com.dev.demo.two.ClassTwoThree {
public <methods>;
private <fields>;
}
- 保持了ClassTwoThree的子类以及里面的成员和方法
-keep class * extends com.dev.demo.two.ClassTwoThree {*;}
- 保持了前缀为ClassOne的类以及里面的成员和方法
-keepnames class com.dev.demo.one.ClassOne{;}
- 保持了ClassTwoTwo的内部类ClassTwoTwoInner里面的成员和方法
-keep class com.dev.demo.two.ClassTwoTwo$ClassTwoTwoInner{*;}
💭三、语法目录
Input/Output Options 输入输出选项
-include filename
递归引入目录的配置文件
-basedirectory directoryname
-injars class_path
指定应用程序要处理的jars包(或者wars、ears、zip、或者目录结构),它们里面的class文件会被处理并被写入到输出jars里面。它们里面的任何非class文件会被直接复制过去但是不会处理。(需要注意过滤调一些IDE自动生成的文件);
-outjars class_path
指定输出jars(wars、ears、zip、目录结构)的名称;由-injars 指定的被处理的jars将被写入到指定的输出jars里。如果不指定outjars将不会有class文件被写入。
-libraryjars class_path 不混淆指定的jar库(android 项目中一般不混淆引入的第三方类库)
-skipnonpubliclibraryclasses 不混淆指定jars中的非public calsses
-dontskipnonpubliclibraryclasses 不忽略指定jars中的非public calsses (默认选项)和上面的选手想对
-dontskipnonpubliclibraryclassmembers
不忽略指定类库的public类成员(变量和方法),默认情况下,ProGuard会忽略他们
-keepdirectories [ directory_filter] 指定要保持的目录结构,默认情况下会删除所有目录以减小jar的大小。
-target version
指定java版本号。 版本号可以是1.0,1.1,1.2,1.3,1.4,1.5(或仅5),1.6(或仅6)或1.7(或仅7)中的一个。 默认情况下,类文件的版本号保持不变。 例如,您可能想要将类文件升级到Java 6,通过更改其版本号并对其进行预验证。
-forceprocessing 强制处理输入(-injars)jars。即使输出jars是最新的。通过指定的输入,输出和配置文件或者目录的时间戳判断是否最新。
Keep Options 保留选项
-keep [, modifier,...] class_specification
指定需要保留的类和类成员(作为公共类库,应该保留所有可公开访问的public方法)
-keepclassmembers [, modifier,...] class_specification
指定需要保留的类成员:变量或者方法
-keepclasseswithmembers [, modifier,...] class_specification
指定保留的类和类成员,条件是所指定的类成员都存在(既在压缩阶段没有被删除的成员,效果和keep差不多)
-keepnames class_specification
[-keep allowshrinking class_specification 的简写]
指定要保留名称的类和类成员,前提是在压缩阶段未被删除。仅用于模糊处理
-keepclassmembernames class_specification
[-keepclassmembers allowshrinking class_specification 的简写]
指定要保留名称的类成员,前提是在压缩阶段未被删除。仅用于模糊处理
-keepclasseswithmembernames class_specification
[-keepclasseswithmembers allowshrinking class_specification 的简写]
指定要保留名称的类成员,前提是在压缩阶段后所指定的类成员都存在。仅用于模糊处理
-printseeds [ filename]
指定详尽列出由各种-keep选项匹配的类和类成员。 列表打印到标准输出或给定文件。 该列表可用于验证是否真的找到了预期的类成员,特别是如果您使用通配符。 例如,您可能想要列出所有应用程序或您保存的所有小程序。
Keep选项概述对比(Overview of Keep Options)
作用范围 | 保持所指定类、成员 | 所指定类、成员在压缩阶段没有被删除,才能被保持 |
类和类成员 | -keep | -keepnames |
仅类成员 | -keepclassmembers | -keepclassmembernames |
类和类成员(前提是成员都存在) | -keepclasseswithmembers | -keepclasseswithmembernames |
通用通配符
通配符 | 意义 |
? | 匹配名称当中的任意一个字符 |
* | 匹配名称中的任意部分,但是不包括目录的分隔符、包分隔符 |
** | 匹配名称中的任意部分,可以包含任意数量的目录分隔符、包分隔符 |
类描述通配符
通配符 | 意义 |
? | 匹配单个字符 |
* | 匹配类名中的任何部分,但不包含包分隔符 |
** | 匹配类名中的任何部分,并且可以包含包分隔符 |
% | 匹配java中的基本数据类型(int, boolean, long, float,double等) |
... | 匹配任意参数列表 |
* | 匹配所有类型,包括初始类型和非初始类型,数组和非数组 |
< init > | 匹配任何构造器 |
< ifield> | 匹配任何字段名 |
< imethod> | 匹配任何方法 |
$ | 指内部类 |
🚩四、关于打包和发布
- 需要注意的是,使用ProGuard混淆打包,并不会替换正常的jar包,而是会多出一个代码混淆后的jar包
- 如果要使用混淆后的jar包进行发布
1. 将原版jar包中的META-INF文件夹拖到混淆后的jar包里去
2. 将混淆后的jar包重命名为原版名称
3. 手动上传至私库(不要用maven的deploy命令上传,这样做会同时上传两个jar包并导致版本混乱)