Java Annotation Processing: A Beginner Walkthrough

Preview:

Citation preview

Java Annotation ProcessingA Beginner Walkthrough

Mahfuz Islam Bhuiyan

Software Engineer, Genweb2

What’s Annotation?● Provide meta-data for Java code

● Can describe usage of an element, e.g. @Deprecated

● Can describe the nature of an element, e.g. @WebService

● and many more

Built-in Java Annotations@Deprecated

@Override

@SuppressWarning

Custom Annotationpublic @interface MyAnnotation { // @interface tells Java that it’s a custom annotation

String value();

String name(); int age(); String[] newNames();}

Custom Annotation@MyAnnotation( value="Test123456", name=" Oveget ", age=37, newNames={"Rishi", "Farzana"})public class MyClass {

}

Custom Annotationpublic @interface MyAnnotation {

String value() default "";

String name() default " The Sufi ";

int age(); String[] newNames();

}

Annotation Processing Trends● Remove boilerplate

● Inject Source Code

● Validate Fields, Methods, Class etc

Annotation Processing Facts● Part of javac

● Introduced in Java 5

● Run at Compile Time(!)

● Own JVM

● Native Java code

● No Reflection(by default)

Java Reflection● Makes it possible to inspect classes, interfaces, fields and methods at runtime,

without knowing the names of the classes, methods etc. at compile time.

● Can instantiate new objects, invoke methods and get/set field values using

reflection.

Java ReflectionsMethod[] methods = MyObject.class.getMethods();

for(Method method : methods){ System.out.println("method = " + method.getName());}

Java ReflectionsClass aClass = MyObject.classField field = aClass.getField("someField");

MyObject objectInstance = new MyObject();

Object value = field.get(objectInstance);

field.set(objetInstance, value);

@Retention@Retention(RetentionPolicy.RUNTIME) // Allows the annotation to be available at Runtime

@Target({ElementType.METHOD})public @interface MyAnnotation { // Yes, we can apply annotation over another annotation

String value() default "";

}

Annotation Processing Limitations● Generate only new files

● Can’t manipulate already existing files(But byte manipulation possible with

sacrificing debugging capability)

Create Your Own Annotation Processor1. Extends AbstractProcessor

2. Register the processor with javac

Extending Abstract Processorpublic class MyProcessor extends AbstractProcessor {

@Override public synchronized void init(ProcessingEnvironment env){}

@Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

@Override public Set<String> getSupportedAnnotationTypes() { }

@Override public SourceVersion getSupportedSourceVersion() { }

}

Extending Abstract Processor@SupportedSourceVersion(SourceVersion.latestSupported())@SupportedAnnotationTypes({ // Set of full qualified annotation type names })public class MyProcessor extends AbstractProcessor {

@Override public synchronized void init(ProcessingEnvironment env){ }

@Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }}

Extending Abstract Processor@SupportedSourceVersion(SourceVersion.latestSupported())@SupportedAnnotationTypes({ // Set of full qualified annotation type names })public class MyProcessor extends AbstractProcessor {

@Override public synchronized void init(ProcessingEnvironment env){ }

// We need to get our hands dirty with following method @Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }}

Register the Processor1. Create a .jar file with our Annotation Processor Java file

2. Provide a special file called javax.annotation.processing.Processor located in

META-INF/services in your .jar file

3. Within the javax.annotation.processing.Processor , there should be the fully

qualified names of the processors contained in the Jar file(like, com.example.

MyProcessor.class)

So, it’ll look likeMyProcessor.jar

=> com

=> example

=> MyProcessor.class

=> META-INF

=> services

=> javax.annotation.processing.Processor

Validate a Class with Custom Annotation Processor

Annotationimport com.example;

public @interface ConstructorCheck{

}

Annotation Processor@SupportedSourceVersion(SourceVersion.latestSupported())@SupportedAnnotationTypes({"com.example.ConstructorCheck"})}public class MyProcessor extends AbstractProcessor {

@Override public synchronized void init(ProcessingEnvironment env){ }

@Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }}

initProcessingEnvironment processingEnv;

@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.processingEnv = processingEnv;}

ProcessingEnvironment● getElementUtils => Elements

● getTypeUtils => Types

● getFiler => Filer

● getMessager => Messager

Element● Is not a class per se !

● Contains value that can be of Class, Interface etc

Elementpublic class User{ // TypeElement

private String name; // VariableElement

private Person personObj; // VariableElement

public User(){} // ExecutableElement

public boolean isUserHasNID(){ // ExecutableElement /*...*/ }}

TypeMirror● Provide some meta data about Element

● Get to know the class inheritance hierarchy, for instance

process@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { checkEntityAnnotatedElements(roundEnv); return false; // true if we want to stop processor’s further execution

}

private void checkEntityAnnotatedElements(RoundEnvironment roundEnv) {

Set <? extends Element > entityAnnotated = roundEnv.getElementsAnnotatedWith(CheckConstructor.class);

for (TypeElement typeElement: ElementFilter.typesIn(entityAnnotated)) {

for (ExecutableElement constructor: ElementFilter.constructorsIn(typeElement.getEnclosedElements())) { List <? extends VariableElement > parameters = constructor.getParameters();

if (parameters.isEmpty()) return; } AnnotationMirror entityAnnotation = getAnnotation(typeElement, entityType.type); processingEnv.getMessager().printMessage(Kind.ERROR, "missing no argument constructor", typeElement, entityAnnotation); }}

Automatic Source Code Generating Tool

JavaPoet● JavaPoet is a API for generating java source files.

● It can be useful when doing things such as annotation processing or interacting

with metadata files.

JavaPoetHere goes a plain simple java class

package com.example.helloworld;

public final class HelloWorld {

public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); }}

JavaPoetWith JavaPoet, it’ll look like this.

MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class).addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!").build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL).addMethod(main).build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld).build();

javaFile.writeTo(System.out)

Some Cool Annotation Based Tools

Project Lombok● Project Lombok greatly reduces the number of lines of boilerplate code

Dagger 2 ● Dagger 2 is a fork from Dagger 1 under heavy development by Google

● Dependency Injection design pattern without the burden of writing the

boilerplate

● No reflection at all

● Achieved 13% performance boost over Dagger 1

Butter Knife● Butter Knife injects views on Android

● Reduce boilerplate codes

● Support Resource and Event binding too

Butter Knifeclass ExampleActivity extends Activity { TextView title, subtitle; EditText inputTitle, inputSubTitle;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity);

title = (TextView)findViewById(R.id.title); subtitle = (TextView)findViewById(R.id.subtitle); inputTitle = (EditText)findViewById(R.id.inputTitle); inputSubtitle = (EditText)findViewById(R.id.inputSubTitle); }}

Butter Knifeclass ExampleActivity extends Activity { @Bind(R.id.title) TextView title; @Bind(R.id.subtitle) TextView subtitle; @Bind(R.id.subtitle) EditText inputTitle; @Bind(R.id.subtitle) EditText inputSubtitle;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); }}

@thankYou