javaandroidannotationsjavassistbcel

Post-compilation removal of annotations from byte code


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. How can I get started?


Solution

  • 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;
    }