Jayachandra Ch
Jayachandra Ch

Reputation: 21

Add Lombok annotations dynamically using Javassist

For a project I was experimenting javassist (java binary code manipulation library).

I was able to create a simple POJO class using below code

public static Class<?> createClass(ModelClass clazz) throws NotFoundException, CannotCompileException {
    ModelProperty[] properties = clazz.getProperties();

    ClassPool classPool = ClassPool.getDefault();
    CtClass ctClass = classPool.makeClass(clazz.getName());

    // add annotation
    ClassFile classFile = ctClass.getClassFile();
    ConstPool constpool = classFile.getConstPool();
    AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
    System.out.println(Data.class.getName());
    Annotation annotation = new Annotation(lombok.Data.class.getName(), constpool);
    Annotation annotation1 = new Annotation(Component.class.getName(), constpool);
    Annotation annotation2 = new Annotation(Deprecated.class.getName(), constpool);
    annotationsAttribute.setAnnotations(new Annotation[] {annotation, annotation1, annotation2});
    ctClass.getClassFile().addAttribute(annotationsAttribute);

    for (ModelProperty property : properties) {
        String camelCaseField = property.getName().substring(0, 1).toUpperCase() + property.getName().substring(1);
        CtField ctField = new CtField(resolveCtClass(property.getType()), property.getName(), ctClass);
        ctClass.addField(ctField);

        // add getter
        // CtMethod fieldGetter = CtNewMethod.getter("get" + camelCaseField, ctField);
        // ctClass.addMethod(fieldGetter);

        // add setter
        // CtMethod fieldSetter = CtNewMethod.setter("set" + camelCaseField, ctField);
        // ctClass.addMethod(fieldSetter);
    }

    return ctClass.toClass();
}

// ModelClass
@Data
public class ModelClass {

    private String name;

    private ModelProperty[] properties;

    public void setName(String name) {
        this.name = name;
    }

    public void setModelProperty(ModelProperty[] properties) {
        this.properties = properties;
    }

}

// ModelProperty
@Data
public class ModelProperty {

    private String name;

    private Class<?> type;

    public void setType(String type) {
        switch (type.toLowerCase()) {
            case "int":
            case "integer":
                this.type = Integer.class;
                break;
            case "float":
                this.type = Float.class;
                break;
            case "str":
            case "string":
                this.type = String.class;
                break;
            case "bool":
            case "boolean":
                this.type = Boolean.class;
                break;
            default:
                this.type = String.class;
                break;
        }
    }
}

I was able to create class by using createClass method. While inspecting class with Reflection API, I've noticed that lombok.Data annotation is not adding to class.

Class<?> clazz = PojoCreator.createClass(modelClassPerson);

for (final Method method : clazz.getMethods()) {
    System.out.println(method);
}
for (final Annotation annotation : clazz.getAnnotations()) {
    System.out.println(annotation);
}
for (final Method method : clazz.getDeclaredMethods()) {
    System.out.println(method);
}
for (final Annotation annotation : clazz.getDeclaredAnnotations()) {
    System.out.println(annotation);
}

Output

public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
@org.springframework.stereotype.Component(value="")
@java.lang.Deprecated(forRemoval=false, since="")
@org.springframework.stereotype.Component(value="")
@java.lang.Deprecated(forRemoval=false, since="")

As you can see from the output spring.Component and javalang.Deprecated is added but lombok.Data didn't.

pom.xml

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <!-- <scope>annotationProcessor</scope> -->
</dependency>

Tried setting scope as compile, runtime, provided but none seems to work.

Using mvn spring-boot:run to execute in command line

Where did I go wrong?

Thanks.

Upvotes: 2

Views: 679

Answers (1)

dan1st
dan1st

Reputation: 16338

TL;DR

You cannot use lombok at runtime, you need to generate your boilerplate code by yourself.

Why?

This cannot work because lombok uses an annotation processor (see this and that).

This means that lombok is run when the program. is compiled.

As you try to insert annotations at runtime, the lombok annotations will not be processed and nothing will be generated.

If you look at the javadoc of lombok annotations like @Data, you can see the following:

@Retention(SOURCE)

This means that the annotation is visible at compile-time but not it is not present in the bytecode and you also cannot access it at runtime.

Upvotes: 5

Related Questions