使用 proguard 混淆后,使用 google guice 注入不再起作用

Posted

技术标签:

【中文标题】使用 proguard 混淆后,使用 google guice 注入不再起作用【英文标题】:Injection with google guice does not work anymore after obfuscation with proguard 【发布时间】:2011-01-23 02:51:37 【问题描述】:

有没有人尝试过将 google guice 的使用与混淆(特别是 proguard)结合起来? 我的代码的混淆版本不适用于 google guice,因为 guice 抱怨缺少类型参数。这个信息似乎被 proguard 所做的转换步骤删除了,即使相关类被排除在混淆之外。

堆栈跟踪如下所示:

com.google.inject.CreationException: Guice creation errors:

1) Cannot inject a Provider that has no type parameter
  while locating com.google.inject.Provider
    for parameter 0 at de.repower.lvs.client.admin.user.administration.AdminUserCommonPanel.setPasswordPanelProvider(SourceFile:499)
  at de.repower.lvs.client.admin.user.administration.AdminUserCommonPanel.setPasswordPanelProvider(SourceFile:499)
  while locating de.repower.lvs.client.admin.user.administration.AdminUserCommonPanel
    for parameter 0 at de.repower.lvs.client.admin.user.administration.b.k.setParentPanel(SourceFile:65)
  at de.repower.lvs.client.admin.user.administration.b.k.setParentPanel(SourceFile:65)
  at de.repower.lvs.client.admin.user.administration.o.a(SourceFile:38)

2) Cannot inject a Provider that has no type parameter
  while locating com.google.inject.Provider
    for parameter 0 at de.repower.lvs.client.admin.user.administration.AdminUserCommonPanel.setWindTurbineAccessGroupProvider(SourceFile:509)
  at de.repower.lvs.client.admin.user.administration.AdminUserCommonPanel.setWindTurbineAccessGroupProvider(SourceFile:509)
  while locating de.repower.lvs.client.admin.user.administration.AdminUserCommonPanel
    for parameter 0 at de.repower.lvs.client.admin.user.administration.b.k.setParentPanel(SourceFile:65)
  at de.repower.lvs.client.admin.user.administration.b.k.setParentPanel(SourceFile:65)
  at de.repower.lvs.client.admin.user.administration.o.a(SourceFile:38)

2 errors
    at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:354)
    at com.google.inject.InjectorBuilder.initializeStatically(InjectorBuilder.java:152)
    at com.google.inject.InjectorBuilder.build(InjectorBuilder.java:105)
    at com.google.inject.Guice.createInjector(Guice.java:92)
    at com.google.inject.Guice.createInjector(Guice.java:69)
    at com.google.inject.Guice.createInjector(Guice.java:59)

我尝试创建一个似乎重现问题的小示例(不使用 guice):

package de.repower.common;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

class SomeClass<S>  


public class ParameterizedTypeTest 

    public void someMethod(SomeClass<Integer> param) 
        System.out.println("value: " + param);
        System.setProperty("my.dummmy.property", "hallo");
    

    private static void checkParameterizedMethod(ParameterizedTypeTest testObject) 
        System.out.println("checking parameterized method ...");
        Method[] methods = testObject.getClass().getMethods();
        for (Method method : methods) 
            if (method.getName().equals("someMethod")) 
                System.out.println("Found method " + method.getName());
                Type[] types = method.getGenericParameterTypes();
                Type parameterType = types[0];
                if (parameterType instanceof ParameterizedType) 
                    Type parameterizedType = ((ParameterizedType) parameterType).getActualTypeArguments()[0];
                    System.out.println("Parameter: " + parameterizedType);
                    System.out.println("Class: " + ((Class) parameterizedType).getName());
                 else 
                    System.out.println("Failed: type ist not instance of ParameterizedType");
                
            
        
    

    public static void main(String[] args) 
        System.out.println("Starting ...");
        try 
            ParameterizedTypeTest someInstance = new ParameterizedTypeTest();
            checkParameterizedMethod(someInstance);
         catch (SecurityException e) 
            e.printStackTrace();
        

    


如果你在 unsbfuscated 的情况下运行此代码,输出如下所示:

Starting ...
checking parameterized method ...
Found method someMethod
Parameter: class java.lang.Integer
Class: java.lang.Integer

但运行使用 proguard 混淆的版本:

Starting ...
checking parameterized method ...
Found method someMethod
Failed: type ist not instance of ParameterizedType

这些是我用来混淆的选项:

-injars classes_eclipse\methodTest.jar
-outjars classes_eclipse\methodTestObfuscated.jar

-libraryjars 'C:\Program Files\Java\jre6\lib\rt.jar'

-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontshrink
-printusage classes_eclipse\shrink.txt
-dontoptimize
-dontpreverify
-verbose


-keep class **.ParameterizedTypeTest.class 
    <fields>;
    <methods>;


-keep class ** 
    <fields>;
    <methods>;


# Keep - Applications. Keep all application classes, along with their 'main'
# methods.
-keepclasseswithmembers public class * 
    public static void main(java.lang.String[]);


# Also keep - Enumerations. Keep the special static methods that are required in
# enumeration classes.
-keepclassmembers enum  * 
    public static **[] values();
    public static ** valueOf(java.lang.String);


# Also keep - Database drivers. Keep all implementations of java.sql.Driver.
-keep class * extends java.sql.Driver

# Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI,
# along with the special 'createUI' method.
-keep class * extends javax.swing.plaf.ComponentUI 
    public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent);


# Keep names - Native method names. Keep all native class/method names.
-keepclasseswithmembers,allowshrinking class * 
    native <methods>;


# Keep names - _class method names. Keep all .class method names. This may be
# useful for libraries that will be obfuscated again with different obfuscators.
-keepclassmembers,allowshrinking class * 
    java.lang.Class class$(java.lang.String);
    java.lang.Class class$(java.lang.String,boolean);

有没有人知道如何解决这个问题(除了将相关文件放入单独的 jar 并且不混淆它的明显解决方法)?

最好的问候, 斯蒂芬

【问题讨论】:

【参考方案1】:

在使用 proguard 很长一段时间后,我决定如何解决有关反射的问题(Guice 只是它的一个用例)。

只要没有以字符串形式输入类或方法名,反射就可以与 Proguard 一起使用。

也就是说这段代码是有效的,经过ProGuard混淆后可以工作

Class someClass = Class.forName(SomeClass.class.getName());

虽然这段代码不起作用

Class someClass = Class.forName("SomeClass");

此外,Proguard 将收缩未调用的方法和构造函数。因此,@987654321@ 方法将不起作用。不幸的是,通常的 Guice 绑定使用这种方法。

这对 Guice 代码有一些影响。

您的所有注入都必须使用 @Provides 注释方法生成,因为 ProGuard 会收缩类,因为它们的构造函数没有被显式调用。 Proguard 不得压缩模块类的代码。 ProGuard 不得缩小注释,无论它们在哪里,无论它们在哪里(可以配置,但我不记得在哪里)。

【讨论】:

哇,这是一个可怕的消息,你必须使用 @Provides 方法来阻止 ProGuard 剥离依赖项。这需要一个工具来提供帮助。我认为在 proguard 配置中粘贴一堆 -keep 行也可以(几乎同样令人讨厌)? 可能是因为添加 -keep 注释会降低重构效率,因为我们必须确保在输出 jar 中类名有效。在我看来,在纯 Java 级别上运行会使我们的代码更符合重构要求(如果存在这种情况)【参考方案2】:

在 JDK 5.0 及更高版本中编译时,需要“Signature”属性才能访问泛型类型。

使用 -keepattributes 签名修复 ParameterizedType 错误

【讨论】:

它没有解决上面示例中的问题。我仍然需要尝试解决我原来使用 google guice 的问题。 这为我在 android 上使用 no-aop 构建修复了它,我遇到了与 OP 相同的问题。我在 proguard 配置中也有以下内容。 -keepclassmembers 类 * @com.google.inject.Inject (...); 这对我也有用,与提问者的问题相同!! 我在这里发布了一个带有测试用例的错误报告:sourceforge.net/p/proguard/bugs/518 ...它的解决方案可能会解决您的问题。【参考方案3】:

使用当前版本的 Proguard (4.7),我可以通过添加以下内容来使其正常工作:-

-keepattributes *Annotation*,Signature  
-keep class com.google.inject.Binder    
-keep public class com.google.inject.Inject
 # keeps all fields and Constructors with @Inject
-keepclassmembers,allowobfuscation class * 
    @com.google.inject.Inject <fields>;
    @com.google.inject.Inject <init>(...);

除了显式保留由 Guice 创建的任何类,例如

-keep class com.example.Service

【讨论】:

此修改不需要显式添加您的@Inject 类(如其他帖子中所述容易出错):-keepclasseswithmembers,allowoptimization,allowobfuscation class * @com.google.inject.Inject &lt;methods&gt;; -keepclassmembers,allowobfuscation,allowoptimization class * @com.google.inject.Provides &lt;methods&gt;; 【参考方案4】:

以下代码对我有用,遇到了同样的问题。

-keepattributes Signature 是解决方法。

-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
#-dontobfuscate
-repackageclasses ''
-keepattributes *Annotation*
-keepattributes Signature
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService

-keepclasseswithmembernames class * 
    native <methods>;


-keepclasseswithmembernames class * 
    public <init>(android.content.Context, android.util.AttributeSet);


-keepclasseswithmembernames class * 
    public <init>(android.content.Context, android.util.AttributeSet, int);


-keepclassmembers enum * 
    public static **[] values();
    public static ** valueOf(java.lang.String);

-keepattributes Signature
-keep class * implements android.os.Parcelable 
  public static final android.os.Parcelable$Creator *;

-keep class com.google.inject.Binder
-keepclassmembers class * 
    @com.google.inject.Inject <init>(...);

-keep public class * extends android.view.View 
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
 

# I didn't need this one, maybe you need it.
#-keep public class roboguice.** 

-keepclassmembers class **.R$* 
    public static <fields>;

【讨论】:

以上是关于使用 proguard 混淆后,使用 google guice 注入不再起作用的主要内容,如果未能解决你的问题,请参考以下文章

Proguard 混淆时保持类“实现”

ProGuard 混淆、java、Google Gson 和泛型集合——如何留住成员?

通过将 proguard 规则映射文件上传到 google play 控制台去混淆生产 Android 错误

使用 ProGuard 混淆 clojure uberjars

使用 Proguard 混淆后的 SuperNotCalledException

使用 Proguard 处理 Jackson 库后混淆时出错