Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set value in annotated field in java?

Tags:

java

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?

like image 816
Gokul Prabhu Avatar asked Nov 29 '22 10:11

Gokul Prabhu


1 Answers

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.

like image 87
zapl Avatar answered Dec 04 '22 12:12

zapl