Java 编译后从字节码中删除注释

Java 编译后从字节码中删除注释,java,android,annotations,javassist,bcel,Java,Android,Annotations,Javassist,Bcel,我们正在使用一个库,其中包含用JAXB注释注释的bean。我们使用这些类的方式不依赖于JAXB。换句话说,我们不需要JAXB,也不依赖于注释 但是,由于注释的存在,它们最终会被处理注释的其他类引用。这要求我在应用程序中捆绑JAXB,这是不允许的,因为JAXB在javax.*包中(android不允许在应用程序中包含“核心库”) 因此,考虑到这一点,我正在寻找一种从编译的字节码中删除注释的方法。我知道有一些用于操作字节码的实用程序,但这对我来说是相当陌生的。如果您能在这方面提供帮助,我们将不胜感激

我们正在使用一个库,其中包含用JAXB注释注释的bean。我们使用这些类的方式不依赖于JAXB。换句话说,我们不需要JAXB,也不依赖于注释

但是,由于注释的存在,它们最终会被处理注释的其他类引用。这要求我在应用程序中捆绑JAXB,这是不允许的,因为JAXB在
javax.*
包中(android不允许在应用程序中包含“核心库”)


因此,考虑到这一点,我正在寻找一种从编译的字节码中删除注释的方法。我知道有一些用于操作字节码的实用程序,但这对我来说是相当陌生的。如果您能在这方面提供帮助,我们将不胜感激。

我推荐BCEL 6。您也可以使用ASM,但我听说BCEL更容易使用。以下是一种快速测试方法,用于进行现场决赛:

public static void main(String[] args) throws Exception {
    System.out.println(F.class.getField("a").getModifiers());
    JavaClass aClass = Repository.lookupClass(F.class);
    ClassGen aGen = new ClassGen(aClass);
    for (Field field : aGen.getFields()) {
        if (field.getName().equals("a")) {
            int mods = field.getModifiers();
            field.setModifiers(mods | Modifier.FINAL);
        }
    }
    final byte[] classBytes = aGen.getJavaClass().getBytes();
    ClassLoader cl = new ClassLoader(null) {
        @Override
        protected synchronized Class<?> findClass(String name) throws ClassNotFoundException {
            return defineClass("F", classBytes, 0, classBytes.length);
        }
    };
    Class<?> fWithoutDeprecated = cl.loadClass("F");
    System.out.println(fWithoutDeprecated.getField("a").getModifiers());
}
publicstaticvoidmain(字符串[]args)引发异常{
System.out.println(F.class.getField(“a”).getModifiers());
JavaClass aClass=Repository.lookupClass(F.class);
ClassGen aGen=新的ClassGen(aClass);
for(字段:aGen.getFields()){
if(field.getName().equals(“a”)){
int mods=field.getModifiers();
字段设置修饰符(mods |修饰符.FINAL);
}
}
最终字节[]classBytes=aGen.getJavaClass().getBytes();
ClassLoader cl=新的ClassLoader(null){
@凌驾
受保护的同步类findClass(字符串名称)引发ClassNotFoundException{
返回defineClass(“F”,classBytes,0,classBytes.length);
}
};
类别fwhithoutdeprecated=cl.loadClass(“F”);
System.out.println(fwhithoutdeprecated.getField(“a”).getModifiers());
}
当然,您实际上会将类作为文件写入磁盘,然后将它们放入jar中,但这更便于尝试。我手边没有BCEL 6,因此我无法修改此示例以删除注释,但我想代码应该是这样的:

public static void main(String[] args) throws Exception {
    ...
    ClassGen aGen = new ClassGen(aClass);
    aGen.setAttributes(cleanupAttributes(aGen.getAttributes()));
    aGen.getFields();
    for (Field field : aGen.getFields()) {
        field.setAttributes(cleanupAttributes(field.getAttributes()));
    }
    for (Method method : aGen.getMethods()) {
        method.setAttributes(cleanupAttributes(method.getAttributes()));
    }
    ...
}

private Attribute[] cleanupAttributes(Attribute[] attributes) {
    for (Attribute attribute : attributes) {
        if (attribute instanceof Annotations) {
            Annotations annotations = (Annotations) attribute;
            if (annotations.isRuntimeVisible()) {
                AnnotationEntry[] entries = annotations.getAnnotationEntries();
                List<AnnotationEntry> newEntries = new ArrayList<AnnotationEntry>();
                for (AnnotationEntry entry : entries) {
                    if (!entry.getAnnotationType().startsWith("javax")) {
                        newEntries.add(entry);
                    }
                }
                annotations.setAnnotationTable(newEntries);
            }
        }
    }
    return attributes;
}
publicstaticvoidmain(字符串[]args)引发异常{
...
ClassGen aGen=新的ClassGen(aClass);
aGen.setAttributes(cleanupAttributes(aGen.getAttributes());
aGen.getFields();
for(字段:aGen.getFields()){
field.setAttributes(cleanupAttributes(field.getAttributes());
}
对于(方法:aGen.getMethods()){
setAttributes(cleanupAttributes(method.getAttributes());
}
...
}
私有属性[]清除属性(属性[]属性){
用于(属性:属性){
if(注释的属性实例){
注释=(注释)属性;
if(annotations.isRuntimeVisible()){
AnnotationEntry[]entries=annotations.getAnnotationEntries();
List newEntries=new ArrayList();
对于(注释条目:条目){
如果(!entry.getAnnotationType().startsWith(“javax”)){
新增条目。添加(条目);
}
}
annotations.setAnnotationTable(新条目);
}
}
}
返回属性;
}

ProGuard除了混淆代码外,还将执行此操作

还有一个额外的任务

从java字节码/类文件中清除对注释的引用 (从带注释的图元中删除@Anno标记)。现在你可以使用 编译后检查字节码中星座的注释,但 在释放震击器之前,移除使用过的震击器

我已使用库删除注释。不幸的是,我无法用高级api删除注释,所以我使用了ASMAPI。下面是一个示例,说明如何从类的字段中删除@Deprecated注释:

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.jar.asm.AnnotationVisitor;
import net.bytebuddy.jar.asm.FieldVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.annotation.Annotation;
import java.util.Arrays;

public class Test {

    public static class Foo {
        @Deprecated
        public Integer bar;
    }

    public static void main(String[] args) throws Exception {
        System.out.println("Annotations before processing " + getAnnotationsString(Foo.class));
        Class<? extends Foo> modifiedClass = new ByteBuddy()
                .redefine(Foo.class)
                .visit(new AsmVisitorWrapper.ForDeclaredFields()
                        .field(ElementMatchers.isAnnotatedWith(Deprecated.class),
                                new AsmVisitorWrapper.ForDeclaredFields.FieldVisitorWrapper() {
                                    @Override
                                    public FieldVisitor wrap(TypeDescription instrumentedType,
                                                             FieldDescription.InDefinedShape fieldDescription,
                                                             FieldVisitor fieldVisitor) {
                                        return new FieldVisitor(Opcodes.ASM5, fieldVisitor) {
                                            @Override
                                            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
                                                if (Type.getDescriptor(Deprecated.class).equals(desc)) {
                                                    return null;
                                                }
                                                return super.visitAnnotation(desc, visible);
                                            }
                                        };
                                    }
                                }))
                // can't use the same name, because Test$Foo is already loaded
                .name("Test$Foo1")
                .make()
                .load(Test.class.getClassLoader())
                .getLoaded();
        System.out.println("Annotations after processing " + getAnnotationsString(modifiedClass));
    }

    private static String getAnnotationsString(Class<? extends  Foo> clazz) throws NoSuchFieldException {
        Annotation[] annotations = clazz.getDeclaredField("bar").getDeclaredAnnotations();
        return Arrays.toString(annotations);
    }
}
导入net.bytebuddy.bytebuddy;
导入net.bytebuddy.asm.AsmVisitorWrapper;
导入net.bytebuddy.description.field.FieldDescription;
导入net.bytebuddy.description.type.TypeDescription;
导入net.bytebuddy.jar.asm.AnnotationVisitor;
导入net.bytebuddy.jar.asm.FieldVisitor;
导入net.bytebuddy.jar.asm.opcode;
导入net.bytebuddy.jar.asm.Type;
导入net.bytebuddy.matcher.ElementMatchers;
导入java.lang.annotation.annotation;
导入java.util.array;
公开课考试{
公共静态类Foo{
@不赞成
公共整数条;
}
公共静态void main(字符串[]args)引发异常{
System.out.println(“处理前的注释”+getAnnotationsString(Foo.class));

类其他哪些类引用了它们?为什么你的Android应用程序中包含这些类?不幸的是,我无法控制引用注释的类。它们是从另一个库引用的。我仍然很好奇,引用这些类的库是什么?jackson。任何迭代注释的类一个类也会导致同样的问题。谢谢。是的,但是让proguard运行在一组广泛而多样的依赖项上本身就是一个项目。我正在寻找一个更快的修复方法(如果存在的话)。我如何使用ProGuard做到这一点?我看到了一个可用于注释的
-keepattributes
选项,但没有看到太多关于删除的内容。对于尝试这样做的人,我还必须修改“ConstantPool”(即使从属性中删除注释后,它也包含注释).可能是我做错了什么,但现在对我有效了。