Romain Rolland said: there is only one heroism in the world, is to see the truth of life after still love life.
I’m sure most of us are familiar with Lombok, but we don’t know much about its implementation and its drawbacks. This article will take a look at Lombok’s principles, hand you a simple version of Lombok, and give you an understanding of the implementation principles behind this popular technology, as well as its pros and cons.
Introduction to the
Before we get to the principles, let’s review Lombok (older drivers can skip this to the principles section).
Lombok is a very popular open source project (github.com/rzwitserloo…) Using it can effectively solve the tedious and repetitive code in Java projects, such as setters, getters, toString, equals, hashCode, and non-null judgment, which can be effectively solved with Lombok.
use
1. Add the Lombok plug-in
The Lombok plug-in must be installed in the IDE to properly invoke Lombok decorated code. For example, in Idea, add the following steps:
- Click File > Settings > Plugins to go to the plug-in management page
- Click Browse repositories…
- Search Lombok Plugin
- Click Install Plugin to Install the plug-in
- Restart the IntelliJ IDEA
The installation is complete, as shown in the figure below:
2. Add Lombok libraries
Next we need to add the latest Lombok libraries to the project. If it is a Maven project, add the following configuration directly to pom.xml:
<dependencies>
<! -- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
Copy the code
If JDK 9+ can be added in module mode, the configuration is as follows:
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</path>
</annotationProcessorPaths>
Copy the code
3. Use Lombok
Now to the most important part of the first half of Lombok, let’s look at the code before we use Lombok:
public class Person {
private Integer id;
private String name;
public Integer getId(a) {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name; }}Copy the code
Here’s the code after using Lombok:
@Getter
@Setter
public class Person {
private Integer id;
private String name;
}
Copy the code
You can see that after Lombok, all the previous Getter/Setter code was done with a single annotation, making the code instantly much more elegant.
All Lombok notes are as follows:
val
: is used before a local variable to declare the variable final;@NonNull
A NullPointerException (NPE) is thrown if the parameter is null.@Cleanup
Resources are automatically managed, used in front of local variables, and resources are automatically cleaned up before the end of execution in the scope of the current variable. Code such as try-finally is automatically generated to close streams.@Getter/@Setter
For properties, you no longer have to write your own setters and getters, and you can specify access scope.@ToString
: You can override toString (exclude= “id”) automatically on a class. For example, @toString (exclude= “id”) excludes the id attribute. Or @toString (callSuper=true, includeFieldNames=true) calls the ToString method of the superclass, containing all the attributes;@EqualsAndHashCode
: Used to automatically generate equals and hashCode methods on a class@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
If you specify a staticName=”of”, you will also generate a static factory method that returns the class object. This is much more convenient than using a constructor.@Data
The: annotations are the class equivalent of using @ToString, @EqualSandHashCode, @Getter, @setter, and @requiredargsConstrutor, which are useful for POJO classes;@Value
For classes, this is an immutable form of @data, which is equivalent to adding final declarations to properties, providing only getters, but not setters;@Builder
Person.builder().name(” XXX “).city(” XXX “).build(); person.builder ().name(” XXX “).build();@SneakyThrows
Throws the checked exception automatically without using the throws statement explicitly on the method.@Synchronized
: is used on methods to declare them synchronous and automatically lock them. The lock object is a private propertyA side effect of locking this or an object of its own class is that you cannot prevent uncontrolled code from locking this or an object of its own class, which may result in race conditions or other thread errors.@Getter(lazy=true)
: can replace the classic Double Check Lock boilerplate code;@Log
: Generate different types of log objects based on different annotations, but the instance name is log. There are six optional implementation classes@CommonsLog
Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);@Log
Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName());@Log4j
Creates log = org.apache.log4j.Logger.getLogger(LogExample.class);@Log4j2
Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);@Slf4j
Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class);@XSlf4j
Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
Their specific use is as follows:
1) use val
val sets = new HashSet<String>();
/ / equivalent to
final Set<String> sets = new HashSet<>();
Copy the code
(2) use NonNull
public void notNullExample(@NonNull String string) { string.length(); Public void notNullExample(String String) {if(string ! = null) { string.length(); }else {
throw new NullPointerException("null"); }}Copy the code
(3) use the Cleanup
public static void main(String[] args) { try { @Cleanup InputStream inputStream = new FileInputStream(args[0]); } catch (FileNotFoundException e) { e.printStackTrace(); } InputStream = null; try { inputStream = new FileInputStream(args[0]); } catch (FileNotFoundException e) { e.printStackTrace(); } finally {if(inputStream ! = null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); }}}}Copy the code
(4) use Getter/Setter
@Setter(AccessLevel.PUBLIC)
@Getter(AccessLevel.PROTECTED)
private int id;
private String shap;
Copy the code
5. Use the ToString
@ToString(exclude = "id", callSuper = true, includeFieldNames = true)
public class LombokDemo {
private int id;
private String name;
private int age;
public static void main(String[] args) {
// Output LombokDemo(super=LombokDemo@48524010, name=null, age=0)
System.out.println(newLombokDemo()); }}Copy the code
6 EqualsAndHashCode use
@EqualsAndHashCode(exclude = {"id"."shape"}, callSuper = false)
public class LombokDemo {
private int id;
private String shap;
}
Copy the code
⑦ NoArgsConstructor, RequiredArgsConstructor, AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor
public class LombokDemo {
@NonNull
private int id;
@NonNull
private String shap;
private int age;
public static void main(String[] args) {
new LombokDemo(1."Java");
// Use the static factory method
LombokDemo.of(2."Java");
// Constructs with no parameters
new LombokDemo();
// Include all parameters
new LombokDemo(1."Java".2); }}Copy the code
Today Builder USES
@Builder
public class BuilderExample {
private String name;
private int age;
@Singular
private Set<String> occupations;
public static void main(String[] args) {
BuilderExample test = BuilderExample.builder().age(11).name("Java").build(); }}Copy the code
Pet-name ruby SneakyThrows use
public class ThrowsTest {
@SneakyThrows(a)public void read(a) {
InputStream inputStream = new FileInputStream("");
}
@SneakyThrows
public void write(a) {
throw new UnsupportedEncodingException();
}
/ / equivalent to
public void read(a) throws FileNotFoundException {
InputStream inputStream = new FileInputStream("");
}
public void write(a) throws UnsupportedEncodingException {
throw newUnsupportedEncodingException(); }}Copy the code
Attending a Synchronized using
public class SynchronizedDemo {
@Synchronized
public static void hello() {
System.out.println("world"); } private static final Object$LOCK = new Object[0];
public static void hello() {
synchronized ($LOCK) {
System.out.println("world"); }}}Copy the code
Tick Getter(lazy = true) use
public class GetterLazyExample {
@Getter(lazy = true)
private final double[] cached = expensive();
private double[] expensive() {
double[] result = new double[1000000];
for (int i = 0; i < result.length; i++) {
result[i] = Math.asin(i);
}
returnresult; }} / / equivalent to import the Java. Util. Concurrent. Atomic. AtomicReference; public class GetterLazyExample { private final AtomicReference<java.lang.Object> cached = new AtomicReference<>(); public double[]getCached() {
java.lang.Object value = this.cached.get();
if (value == null) {
synchronized (this.cached) {
value = this.cached.get();
if(value == null) { final double[] actualValue = expensive(); value = actualValue == null ? this.cached : actualValue; this.cached.set(value); }}}return (double[]) (value == this.cached ? null : value);
}
private double[] expensive() {
double[] result = new double[1000000];
for (int i = 0; i < result.length; i++) {
result[i] = Math.asin(i);
}
returnresult; }}Copy the code
The principle of analysis
We know that the Java compilation process can be roughly divided into three phases:
- Parse and populate the symbol table
- Annotation processing
- Analysis and bytecode generation
The compilation process is shown in the figure below:
This can be verified in a program, such as the code that this article started with @data:
After we compiled, we looked at the compiled source code for the Person class and it looked like this:
The execution process of Lombok is as follows:
A Lombok
We implement a simple version of Lombok to customize a Getter method. Our implementation steps are:
- Customize an annotation label interface, and implement a custom annotation handler;
- Using tools.jar’s Javac API to process the AST (abstract syntax tree)
- Compile the code using a custom annotation handler.
This will allow you to implement a simple version of Lombok.
1. Define custom annotations and annotation handlers
First create a mygetter. Java custom annotation as follows:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE) // Annotations are reserved only in the source code
@Target(ElementType.TYPE) // Used to decorate classes
public @interface MyGetter { / / define Getter
}
Copy the code
Implement a custom annotation handler as follows:
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.example.lombok.MyGetter")
public class MyGetterProcessor extends AbstractProcessor {
private Messager messager; // Log input at compile time
private JavacTrees javacTrees; // Provides an abstract syntax tree to work with
private TreeMaker treeMaker; // Encapsulates some methods for creating AST nodes
private Names names; // Provides a method for creating identifiers
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.javacTrees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(MyGetter.class);
elementsAnnotatedWith.forEach(e -> {
JCTree tree = javacTrees.getTree(e);
tree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
// Find all variables in the abstract tree
for (JCTree jcTree : jcClassDecl.defs) {
if(jcTree.getKind().equals(Tree.Kind.VARIABLE)) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree; jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl); }}// Generate method operations on variables
jcVariableDeclList.forEach(jcVariableDecl -> {
messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
});
super.visitClassDef(jcClassDecl); }}); });return true;
}
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
// Generate an expression such as this.a = a;
JCTree.JCExpressionStatement aThis = makeAssignment(treeMaker.Select(treeMaker.Ident(
names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()));
statements.append(aThis);
JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
// Generate the input parameters
JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),
jcVariableDecl.getName(), jcVariableDecl.vartype, null);
List<JCTree.JCVariableDecl> parameters = List.of(param);
// Generate the return object
JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),
getNewMethodName(jcVariableDecl.getName()), methodType, List.nil(),
parameters, List.nil(), block, null);
}
private Name getNewMethodName(Name name) {
String s = name.toString();
return names.fromString("get" + s.substring(0.1).toUpperCase() + s.substring(1, name.length()));
}
private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
returntreeMaker.Exec( treeMaker.Assign( lhs, rhs ) ); }}Copy the code
We need to inherit from AbstractProcessor and override its init() and process() methods. In the process() method, we first query all variables. Add methods to variables. We use TreeMaker objects and Names to process the AST, as shown in the code above.
With this code in place, we can add a new Person class to try our own @myGetter function. The code looks like this:
@MyGetter
public class Person {
private String name;
}
Copy the code
2. Compile the code with a custom annotation handler
After all of the above processes are executed, we can compile the code to test the effect. First, go to the root directory of your code and execute the following three commands.
The root directory is as follows:
Use tools.jar to compile a custom annotator
javac -cp $JAVA_HOME/lib/tools.jar MyGetter* -d .
Note: the command has a ‘. ‘at the end. Represents the current folder.
Compile the Person class using a custom annotator
javac -processor com.example.lombok.MyGetterProcessor Person.java
③ View the Person source code
javap -p Person.class
The source file is as follows:
You can see that our custom getName() method has been successfully generated, and at this point the simple version of Lombok is complete.
Lombok pros and cons
The advantages of Lombok are obvious: it allows us to write less code, saves development time, and makes code look more elegant. The disadvantages are as follows.
Disadvantage 1: Reduced debugging
Lombok automatically generates a lot of code, but this code is generated at compile time, so it can be “lost” during development and debugging, which makes debugging code a major inconvenience.
Disadvantage 2: Compatibility issues may occur
Lombok is very invasive to code, and the JDK version is released every six months, and Lombok is a third-party project and maintained by an open source team, so there is no way to ensure version compatibility and iteration speed, which may lead to version incompatibilities.
Weakness 3: It may pit teammates
This is especially true for new members of the group. If they have not used Lombok before, when they pull down the code, they will be prompted with error messages such as “no method found” when compiling the project because no Lombok plug-in has been installed. As a result, the project will fail to compile and the collaboration between the unity members will be affected.
Disadvantage 4: Broken encapsulation
The definition of object-oriented encapsulation is that the internal data is hidden through access control, and the external can only access and modify the internal data through the limited interface provided by the class.
In other words, we should not mindfully use Lombok to expose Getter/Setter methods for all fields, because some fields, such as the number of items in the shopping cart, are not allowed to be modified directly under certain circumstances, which directly affects the details of the purchase and the total price, so we should provide a uniform method for modifying them. Make associated changes, rather than adding methods to access and modify each field.
summary
In this article we describe the use and implementation of Lombok, which is JSR 269 implemented via JDK 6: The Pluggable Annotation Processing API converts Lombok’s annotations to Java’s regular methods at compile time by inheriting AbstractProcessor class, Override its init() and process() methods to implement a simple version of Lombok. However, Lombok also has some disadvantages, such as reduced debuggability and possible compatibility issues, so we have to choose whether to use Lombok and how to use Lombok according to our own business scenarios and actual conditions.
Finally, a word of caution, no matter how good the technology is not the panacea, as if no matter how good the shoes also have to fit their feet!
Thanks for reading, and I hope you were inspired by this article. If you think it’s good, share it with friends in need. Thank you.
Reference & thanks
Juejin. Im/post / 684490…
www.tuicool.com/articles/y6…
For more exciting content, please follow the wechat official account “Java Chinese Community”