5

My annotation Class

@Target({java.lang.annotation.ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Base {
    int[] value();
}

Actual class

public class Demo {
    @Base(1)
    public int var;
    public int var2;
    public void call() {
        InjectingClass.inject(this);
        System.out.print(var + "");
    }
}

How can I set value one to var and not in var2?

MrSimpleMind
  • 7,890
  • 3
  • 40
  • 45
Gokul Prabhu
  • 112
  • 1
  • 3
  • 7
  • 2
    Huh? Isn't that what you already did? What's the problem with the current code? – Tunaki Nov 28 '15 at 13:32
  • Sorry for not being specific.I didn't implement InjectingClass.How to implement InjectingClass? – Gokul Prabhu Nov 28 '15 at 13:35
  • With `RetentionPolicy.CLASS`? Very difficult (but doable see http://google.github.io/dagger/ ) because that would mean you need an annotation processor that generates code at compile time and then the `InjectingClass` doing almost nothing. With `.RUNTIME` a few lines of (reflection) code. – zapl Nov 28 '15 at 13:39
  • I was inspired by ButterKnife Library.So I just wanted to see how it works?.It is genterating code at complie time hope so.How it works?.RetentionPolicy.RUNTIME already I have used.Does it affects performance in android?.does compile time injection also causes performance overhead? – Gokul Prabhu Nov 28 '15 at 13:47
  • The work in compile time generation happens before you even upload the app to the phone. Butterknife is open source, so have a look :) https://github.com/JakeWharton/butterknife/blob/master/butterknife-compiler/src/main/java/butterknife/compiler/ButterKnifeProcessor.java When this work happens at runtime it slows down things a little. But typically not by noticable amounts. – zapl Nov 28 '15 at 13:58

1 Answers1

27

With RUNTIME generation, that's pretty simple

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Set {
    int value();
}

class Injector {
    public static void inject(Object instance) {
        Field[] fields = instance.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(Set.class)) {
                Set set = field.getAnnotation(Set.class);
                field.setAccessible(true); // should work on private fields
                try {
                    field.set(instance, set.value());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class Demo {
    @Set(1)
    public int var;
    public int var2;

    public void call(){
        Injector.inject(this);
        System.out.println(var);
        System.out.println(var2);
    }
}
public class AnnotationDemo {
    public static void main(String[] args) {
        new Demo().call();
    }
}

When you run that it prints

1
0

It's iterating over the declared fields (i.e. all fields where the declaration is in this class, If you want this to work with inherited fields from super classes you'd have to scan for those as well)

Checks each field for the annotation and if found sets the field to the value present in the annotation.

When you want to do the same with a CLASS or the simpler SOURCE (class is odd and I would use either source or runtime) annotation you'd have to implement a special annotation processor class that is called by the java compiler when compiling .java files that contain annotations you're interested in. In the next step you would generate a .java text source file that contains the code that does the injection. That code is then also compiled by the compiler and your Injector class at runtime would simply call the generated code.

So all you need to do is to manage to write a class .java file like

class GeneratedInjector {
    public static void inject(Object instance) {
        if (instance instanceof Demo) {
            injectDemo((Demo) instance);
        }
    }
    public static void injectDemo(Demo demo) {
        demo.var = 1;
    }
}

at compile time based on analysis of annotations.

So at runtime, the annotation is essentially not present and the code that runs is basically the following

class GeneratedInjector {
    public static void inject(Object instance) {
        if (instance instanceof Demo) {
            injectDemo((Demo) instance);
        }
    }
    public static void injectDemo(Demo demo) {
        demo.var = 1;
    }
}

class Injector {
    public static void inject(Object instance) {
        GeneratedInjector.inject(instance);
    }
}

class Demo {
    public int var;
    public int var2;

    public void call(){
        Injector.inject(this);
        System.out.println(var);
        System.out.println(var2);
    }
}

public class AnnotationDemo {
    public static void main(String[] args) {
        new Demo().call();
    }
}

Since that's all straight forward plain old Java and not reflection you save some CPU cycles. It's most likely not noticeable in most cases but lots of reflection can have an impact.

https://deors.wordpress.com/2011/10/31/annotation-generators/ has some more nice information

There is also a third hybrid approach and that is bytecode generation at runtime. With that you would generate a .class file that implements roughly the same as the .java file. Needs a bytecode framework like https://github.com/cglib/cglib. That approach is also not easily compatible with Android since you need to generate .dex for Android. But I think I've seen even that somewhere.

Community
  • 1
  • 1
zapl
  • 63,179
  • 10
  • 123
  • 154