post-compilation removal of annotations from byte code
-
15-06-2021 - |
質問
we are using a library that contains beans that are annotated with JAXB annotations. nothing in the way we use these classes depends on JAXB. in other words, we don't need JAXB and do not depend on the annotations.
however, because the annotations exist, they end up being referenced by other classes that process annotations. this requires me to bundle JAXB in our application, which isn't allowed, because JAXB is in the javax.*
package (android doesn't allow "core libraries" to be included in your application).
so, with this in mind, i'm looking for a way to remove the annotations from the compiled byte code. i know there are utilities for manipulating byte code, but this is quite new to me. any help getting started toward this end would be appreciated.
解決
I recommend BCEL 6. You can also use ASM, but I hear BCEL is easier to use. Here is a quick test method for making a field final:
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());
}
Of course, you would actually write your classes out to disk as files and then jar them up but this is easier for trying things out. I don't have BCEL 6 handy, so I can't modify this example to remove annotations, but I imagine the code would be something like:
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;
}
他のヒント
I have used ByteBuddy library to remove annotations. Unfortunately I was unable to remove annotation with high level api, so I used ASM api. Here is example, how you can remove @Deprecated annotation from field of a class:
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);
}
}
ProGuard will also do this, in addition to obfuscating your code.
There is an additional AntTask Purge Annotation References Ant Task, which
Purge references to annotations out of the java bytecode/classfiles (remove the @Anno tag from annotated elements). Now you can use annotations to check constellations in bytecode after compilation but remove the used annos before releasing the jars.