ProGuard使用简介
背景简介:
ProGuard是一个压缩、优化和混淆Java字节码文件的免费的工具,它可以删除无用的类、字段、方法和属性。可以删除没用的注释,最大限度地优化字节码文件。它还可以使用简短的无意义的名称来重命名已经存在的类、字段、方法和属性,增加项目被反编译的难度。
下载地址:http://proguard.sourceforge.net/
用法简介
命令:
java -jar proguard.jar
options ...
常用选项说明:
Input/Output 选项
-include
filename
简写:@
filename
递归读取指定的文件名的配置选项,文件名支持
示例:-include lib/annotations.pro;
该名称可以包含Java系统属性(或Ant属性,使用Ant时),由尖括号,“<”和“>”分隔。
例如,<java.home> /lib/rt.jar会自动扩展为类似/usr/local/java/jdk/jre/lib/rt.jar。
同样,<user.home>扩展到用户的主目录,而<user.dir>被扩展为当前工作目录
-injars
class_path
指定即将被混淆的jar文件(或者aars, wars, ears, zips, apks, 或者文件目录)的路径
示例:
-in.jar(!images/**) #匹配jar包中所有非images目录下的文件
-injars project(.class) #匹配project目录下的所有class文件
-outjars
class_path
指定混淆后输出的jar文件(或者aars, wars, ears, zips, apks, 或者文件目录)的路径
指定输出路径时,必须避免覆盖输入文件。
-libraryjars
class_path
指定injar依赖的库文件,可以是jar文件(或者aars, wars, ears, zips, apks, 或者文件目录),这些文件不会包含到outjars中。
-skipnonpubliclibraryclasses
配置此选项将在读取library jar时跳过non-public classes ,会让proguard在执行过程中效率更快,如果跳过的非公共类对injars文件有影响,proguard会打出警告。
-dontskipnonpubliclibraryclasses
指定不去忽略非公共的库类。
-dontskipnonpubliclibraryclassmembers
指定不去忽略非公共的库类的成员。
-keepdirectories
[
directory_filter
]
保留输出jar文件(或者aars, wars, ears, zips, apks, 或者文件目录)的目录信息,默认情况下,proguard为了减小文件大小,目录信息会被删除。单数如果class需要以类似“mypackage.MyClass.class.getResource("")”的方法查找,则必须保留相应包下的目录信息。例如:-keepdirectories mydirectory/**表示保留mydirectory目录及其子目录下的所有类的目录信息。
Keep 选项
-keep
[
,modifier
,...]
class_specification
指定不不混淆的类,成员或方法
-keepclassmembers {modifier} {class_specification}
保护指定类的成员,如果此类受到保护他们会保护的更好
-keepclasseswithmembers {class_specification}
保护指定的类和类的成员,但条件是所有指
定的类和类成员是要存在。
-keepnames {class_specification}
保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除)
-keepclassmembernames {class_specification}
保护指定的类的成员的名称(如果他们没有在压缩步骤中删除)
-keepclasseswithmembernames {class_specification}
保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)
-printseeds {filename}
列出类和类的成员-keep选项的清单,标准输出到给定的文件
Shrinking 选项
-dontshrink
不压缩输入的类文件,不配置的话默认压缩,除了在keep选项中设置的类和成员外,所有的类和类成员都将被移除。
-printusage {filename}
在压缩有效的情况下,将输入类文件中的僵尸代码列表打印到标准输出文件中。
-whyareyoukeeping {class_specification}
配置该选项后会在压缩步时,打印出配置类或成员被保留的原因的细节
Optimization 选项
-dontoptimize
不优化输入的类文件,默认开启优化
-optimizationpasses
n
制定优化执行的次数,默认一次,多次优化执行时,发现没有可改进的,则优化结束。
-assumenosideeffects {class_specification}
指定不被调用的的方法,优化时会删除该方法。例如:
指定System.currentTimeMillis()方法,不被调用的情况下可以删除。
-allowaccessmodification
优化时允许访问并修改有修饰符的类和类的成员
Obfuscation 选项
-dontobfuscate
不混淆输入的类文件
-printmapping {filename}
将混淆前和混淆后的类和类成员打印到制定文件中
-applymapping {filename}
重用映射文件,一般用于增量混淆,例如:
-injars proguardgui.jar
-outjars proguardgui_out.jar
-libraryjars proguard.jar
-libraryjars <java.home>/lib/rt.jar
-applymapping proguard.map
-keep public class proguard.gui.ProGuardGUI {
public static void main(java.lang.String[]);
}
-obfuscationdictionary {filename}
使用给定文件中的关键字作为要混淆方法的名称
-overloadaggressively
混淆时应用侵入式重载
-useuniqueclassmembernames
确定统一的混淆类的成员名称来增加混淆
-flattenpackagehierarchy {package_name}
重新包装所有重命名的包并放在给定的单一包中
-repackageclass {package_name}
重新包装所有重命名的类文件中放在给定的单一包中
-dontusemixedcaseclassnames
混淆时不会产生形形色色的类名
-keepattributes {attribute_name,...}
保护给定的可选属性,例如
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
LineNumberTable:保留方法的行号
LocalVariableTable:保留局部变量的名称和类型
SourceFile:保留被编译源文件的名称,在堆栈追踪时会显示
Deprecated:表明方法,成员或类已经过期
Signature: 保留类,字段或方法的签名,编译器可能需要这些信息来正确编译使用泛型的编译库类。代码可以通过反射来访问此签名
*Annotation*:保留被注解过的类或成员
InnerClasses:保留内部类信息
Exceptions:保留类捕获的异常
-renamesourcefileattribute
[string]
指定一个字符串常量被放入的SourceFile属性的类文件(和SourceDir属性)。请注意,属性必须存在,该属性必须显式的使用-keepattributes指令被保留。例如,你想保留异常,源文件行数信息时,设置-renamesourcefileattribute SourceFile。只有当混淆时适用。
-keepparameternames
保留参数的名称和方法,该选项可以保留调试级别的属性。
Overview of Keep
Options
-keep 选项之间的关联:
保留 | 不被删除或者重命名 | 不被重命名 |
Classes and class members | -keep | -keepnames |
Class members only | -keepclassmembers | -keepclassmembernames |
Classes and class members, if class members present | -keepclasseswithmembers | -keepclasseswithmembernames |
如果你不知道使用哪个选项,你应该简单地使用-keep。这将确保指定的类和类成员在压缩步骤不会被删除,并在混淆步骤不被重命名。
注意:
· 当你值指定了一个类,没有指定其下的的成员,那么,Proguard只会类和器无参数构造函数,这个类下未被指定的成员变量任然会被删除,优化,混淆。
· 如果你只指定一个方法,proguard只会保留其方法,但是方法中的代码任然会白优化和调整。
Class Specifications
[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname [extends|implements [@annotationtype] classname]
[{
[@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> | (fieldtype fieldname);
[@annotationtype] [!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> | <init>(argumenttype,...) | classname(argumenttype,...) | (returntype methodname(argumenttype,...));
[@annotationtype] [[!]public|private|protected|static ... ] *;
...
}]
说明:
"[]”表示它里面的内容是可选的;
"…"表示其前面的参数可以有任意多个;
"|"表示分隔两个备选项;
"()"只是为了将写好的属于一组的规范括起来;
"class"关键字代表任意接口或类;
"interface"关键字限制只能匹配接口;
"enum"关键字限制只能匹配枚举类;
"!"加载关键字前代表不匹配此类或接口;
"classname"必须指定全称,例如:java.lang.string;
"$"指定内部类是需要用到"$",例如java.lang.Thread$state
指定classname时可以用正则表达式:
"?":匹配任意单个字符(不包括包名分隔符),例如,"com.test? "匹配"com.test1", "com.test2",但是不匹配"com.test12";
"*":匹配类名的任意字符,例如,"mypackage.*Test*"匹配 "mypackage.Test"
"mypackage.YourTestApplication", 但是不匹配 "mypackage. subpackage.MyTest".
通常情况下"mypackage.*" 匹配所有在"mypackage"包下的类, 但不匹配在其
子包下的类;
"**":匹配类名的任意部分,并且可以包含包分隔符的任意个数,例如:"**.Test"
匹配除了根包下的所有包内的 Test类. "mypackage.**" 所有在mypackage包
及其子包下的所有类。
"extends","implements" 关键字通常使用通配符来限制类,指定只有继承或实现关键字后的类才匹配;
"@"限制只有被指定annotationtype注解的类或者类成员才匹配,annotationtype指定类似于classname,例如:@org.springframework.beans.factory.annotation.Autowired;
属性和方法的规范和java语言很类似,唯一不同的是方法的参数列表在指定时只需要指定类型,不需要包含参数名,其常用匹配符如下:
<init> 匹配任意构造函数
<fields> 匹配任意属性
<methods> 匹配任意方法
* 匹配任属性或方法
以上匹配符都不包含返回类型,只有<init>匹配符包含参数列表。
属性和方法也可以使用正则表发是匹配,例如:
% | 匹配所有原始类型,例如 ("boolean", "int", 不包括 "void"). |
? | 匹配任意单个自读. |
* | 匹配任意单个字符(不包括包名分隔符) |
** | 匹配类名的任意部分,或许会包含任意个数的包名分隔符 |
*** | 匹配任意类型(原始或者给原始类型,数组或非数组类型) |
... | 匹配任意数量,任意类型的参数.
|
注意:
- ?, *, 和** 不会匹配primitive types. *** 可以匹配任意长度的数组. 例如, "** get*()" 匹配 "java.lang.Object getObject()", 但是不匹配 "float getFloat()", 也不匹配"java.lang.Object[] getObjects()".
支持Ant编译
版本要求
Jdk1.8+;
ant 1.8+
官方说明:
ProGuard can be run as a task in the Java-based build tool Ant (version 1.8 or higher)
配置文件示例
build.xml
<!-- This Ant build file illustrates how to process applications,
by including a ProGuard-style configuration file.
Usage: ant -f applications1.xml -->
<project name="Applications" default="obfuscate" basedir="../..">
<target name="obfuscate">
<taskdef resource="proguard/ant/task.properties"
classpath="lib/proguard.jar" />
<proguard configuration="examples/applications.pro" />
</target>
</project>
applications.pro
#
# This ProGuard configuration file illustrates how to process applications.
# Usage:
# java -jar proguard.jar @applications.pro
#
# Specify the input jars, output jars, and library jars.
-injars in.jar
-outjars out.jar
-libraryjars <java.home>/lib/rt.jar
#-libraryjars junit.jar
#-libraryjars servlet.jar
#-libraryjars jai_core.jar
#...
# Save the obfuscation mapping to a file, so you can de-obfuscate any stack
# traces later on. Keep a fixed source file attribute and all line number
# tables to get line numbers in the stack traces.
# You can comment this out if you're not interested in stack traces.
-printmapping out.map
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
# Preserve all annotations.
-keepattributes *Annotation*
# You can print out the seeds that are matching the keep options below.
#-printseeds out.seeds
# Preserve all public applications.
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
}
# Preserve all native method names and the names of their classes.
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
# Preserve the special static methods that are required in all enumeration
# classes.
-keepclassmembers,allowoptimization enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# Explicitly preserve all serialization members. The Serializable interface
# is only a marker interface, so it wouldn't save them.
# You can comment this out if your application doesn't use serialization.
# If your code contains serializable classes that have to be backward
# compatible, please refer to the manual.
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# Your application may contain more items that need to be preserved;
# typically classes that are dynamically created using Class.forName:
# -keep public class mypackage.MyClass
# -keep public interface mypackage.MyInterface
# -keep public class * implements mypackage.MyInterface
支持Maven编译
Maven支持proguard需要用到第三方插件,proguard官方推荐:
网址:http://mavenproguard.sourceforge.net/
版本要求
Maven 3.1
Jdk版本根据依赖的proguard版本决定
配置文件示例
在pom.xml文件中加入proguard插件配置:
<plugin>
<groupId>com.idfconnect.devtools</groupId>
<artifactId>idfc-proguard-maven-plugin</artifactId>
<version>1.0.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>obfuscate</goal>
</goals>
</execution>
</executions>
<configuration>
<inputFile>${project.build.outputDirectory}</inputFile>
<libraryJarPaths>
<libraryJarPath>${java.home}/lib/rt.jar</libraryJarPath>
</libraryJarPaths>
<excludeManifests>false</excludeManifests>
<excludeMavenDescriptor>false</excludeMavenDescriptor>
<outputArtifacts>
<outputArtifact>
< file>${project.build.finalName}.${project.packaging}</file>
</outputArtifact>
</outputArtifacts>
</configuration>
<dependencies>
<dependency>
<groupId>net.sf.proguard</groupId>
<artifactId>proguard-base</artifactId>
<version>4.9</version>
</dependency>
</dependencies>
</plugin>
Pro文件示例:
除了plugin之外的配置,还有一个.pro的配置文件(存放在${basedir}/src/main/config/${project.artifactId}-maven.pro)。
如果需要指定配置文件目录,则通过以下配置实现:
<proguardIncludeFile>
${basedir}/src/main/config/${project.artifactId}-maven.pro
</proguardIncludeFile>
Pro文件示例:
#保留调试信息(异常信息源码行数)
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
#混淆时不要形成混合大小写类名
-dontusemixedcaseclassnames
#保留调试级别的属性
-keepparameternames
# 保留注解信息,签名信息,异常信息,内部类信息
-keepattributes *Annotation*,Signature,Exceptions,InnerClasses
#指定混淆时方法和属性名替换字典文件
-obfuscationdictionary shakespeare.txt
# 保留所有共有和保护类型的属性和方法
-keep public class * {
public protected *;
}
#保留枚举类方法
-keepclassmembers,allowoptimization enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#保留所有实现序列化的类的素有属性
-keepclassmembers class * implements java.io.Serializable {
private <fields>;
}
#保留com.coship.oms.web包下所有条件了@Autowired注解的素有属性
-keepclassmembers class com.coship.oms.web.** {
@org.springframework.beans.factory.annotation.Autowired <fields>;
}
配置异常及解决
1. spring java.lang.IllegalArgumentException: Name for argument type [java.lang.String] 的错误
原因:
这个错误主要是因为action的参数标注默认是debug级别,比如
@RequestMapping(value = "/security/login", method = RequestMethod.POST)
public ModelAndView login(@RequestParam String userName, @RequestParam String password,
HttpServletRequest request) {
此时userName的级别时debug级别,而在proguard编译时是忽略了这些标注,导致请求时就会找不到userName的参数。
解决方法:
编译脚本中添加-keepparameternames保留调试属性
2. 找不到使用@Autowired注入的service类
混淆时 proguard会对jar包进行优化,以期减少其大小。默认情况下,proguard会删除jar中的目录元素,导致ClassLoader().getResource()方法找不到对应的资源。只需要在使用时 加上 -keepdirectories 选项即可。