preface

Lombok is probably one of the most used plug-ins for a Java developer. It provides a series of annotations to help ease the need to write repetitive code, such as numerous setters and getters in entity classes, closing of various IO streams, trys, etc. The catch… Finally templates, etc., can help us generate these methods through the IDE’s shortcuts, but the verbose code still compromises the simplicity and readability of the code. Today, Lombok has even become a built-in plugin for IDEA (version 2020.3 +) as more and more people use it.

But if you don’t know Lombok, have you ever wondered how this auto-generated code works? 🤔 How does this code come about? This is what this article will introduce.

There are plenty of articles on installing and using Lombok on the web, but you can read them yourself.

Annotation parsing in two ways

I covered annotations in detail in a previous article. Before explaining Lombok, I recommend reading 👉 annotations for compilers – “Annotations” to review how Lombok annotations are parsed.

Runtime resolution

This is the most common way to parse annotations. For annotations that can be parsed at RUNTIME, you must set @Retention to RUNTIME so that they can be retrieved by reflection. For example, the most used @RequestMapping annotation is a runtime annotation.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name(a) default "";

    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};

    String[] params() default {};

    String[] headers() default {};

    String[] consumes() default {};

    String[] produces() default {};
}
Copy the code

Annotation parsing at runtime is also simple. In java.lang, the Reflect reflection package provides an interface AnnotatedElement that defines methods for retrieving annotation information, Class, Constructor, Field, Method, Package, etc., all implement this interface, which should be familiar to anyone familiar with reflection. Related examples have been introduced in previous articles, so I won’t repeat them here. Does Lombok’s annotations work the same way? If we look at the SOURCE code, we can see that the @data interface RetentionPolicy is SOURCE level, which means that the annotation information is lost when the code is compiled and is not loaded into the JVM. So why do we see those get/set methods when we Compile code? This brings us to another approach to annotation parsing — compile-time parsing

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
    String staticConstructor(a) default "";
}
Copy the code

Compile-time parsing

The compilation process of the Javac compiler can be roughly divided into one preparation process and three processing processes:

  1. Initialize the plug-in annotation handler
  2. Parse and populate symbol tables;
  3. Annotation processing of plug-in annotation processor;
  4. Analysis and bytecode generation.

The relationship between these three steps is shown below:

Annotation processing at compile time has two mechanisms, which are briefly described as follows:

Annotation Processing Tool (APT)

APT is generated from JDK5, JDK7 has been marked expired, it is not recommended to use, JDK8 has been completely removed, since JDK6, you can use Pluggable Annotation Processing API to replace it, APT is replaced mainly for 2 reasons:

  • Related apis are in com.sun. Mirror non-standard package
  • It is not integrated into javAC and needs to be run extra

Pluggable Annotation Processing API (Pluggable Annotation Processor)

Java6 is starting to incorporate the jsr-269 specification: Pluggable Annotation Processing API. As an alternative to APT, JSR-269 provides a standard API to handle Annotations and addresses the two problems with APT mentioned earlier. In the process of using JavAC, it works as follows 👇

  1. Javac analyzes the source code to generate an abstract syntax tree (AST)

  2. A Lombok program that implements ** “JSR 269 API” ** is invoked during runtime

  3. Lombok then processes the AST obtained in the first step, finds the syntax tree (AST) corresponding to the class of the @data annotation, and modifies the AST to add the corresponding tree nodes defined by getter and setter methods

  4. Javac generates bytecode files using a modified abstract syntax tree (AST), which adds new nodes (code blocks) to the class

    AST is a tree representation used to describe the syntax structure of program code. Each node in the syntax tree represents a syntax structure in program code, such as package, type, modifiers, operators, interfaces, return values and even code comments.

It is also easy to implement an application based on the JSR 269 API. In particular, we just need to inherit AbstractProcessor classes and override the Process () method to implement our own annotation processing logic. And in the meta-inf/services directory to create javax.mail. Annotation. Processing. The Processor files registered its own annotation Processor, The Annotation Processor we implemented is called by the compiler during javac compilation, giving us the opportunity to modify the abstract syntax tree produced during Java compilation.

Some additions to Java compilation

Compilation time for Java is actually a relatively vague concept, which needs to be analyzed according to specific situations.

  1. will*.javaThe file to*.classThe process is called the front end of the compiler (front end compilation). For example: the JAVac compiler for the JDK.
  2. The bytecode (*.classThe process by which files are converted to native machine code is called the Just-in-time (JIT) runtime (Just In Time) of the Java virtual machine. For example, C1 and C2 compilers for the HotSpot virtual machine.
  3. The process Of mutating a program directly into binary code associated with the target and its instruction set using a static AOT Compiler (Ahead Of Time). For example: JDK Jaotc.

The relationship between them is about 👇

Javac compiles *. Java files to *. Class files, which go into the JVM and are interpreted into the corresponding machine code by the JIT compiler. AOT, on the other hand, compiles *. Class files directly into the system’s library files instead of relying on the JIT to do this.

Compilers like Javac do almost nothing to optimize the efficiency of the code, but because this stage is the closest to programmer coding (as opposed to the JIT), the front-end compiler optimizes the code more closely at compile time. Many of the new Java syntax features, This is done by the compiler’s “syntactic sugar” (automatic boxing, unboxing, and traversal loops), because Javac has made many improvements to the Java language coding process to improve the programmer’s coding style and efficiency, rather than relying on the underlying improvements of the virtual machine.