Romain Rolland said: there is only one heroism in the world, is to see the truth of life and still love life.
I’m sure most of you are familiar with Lombok, but you don’t know much about how it works and how it doesn’t work. In this article, we’ll start with a simple version of Lombok to help you understand how the popular technology works, as well as its strengths and weaknesses.
Introduction to the
Before we get to the principles, let’s review Lombok (veteran drivers can skip the principles section).
Lombok is a very popular open source project (github.com/rzwitserloo…) Lombok can be used to effectively solve tedious and repetitive code in Java projects such as setters, getters, toString, equals, hashCode, and non-null judgments.
use
1. Add the Lombok plug-in
The Lombok plug-in must be installed in the IDE to invoke Lombok code. In the example of 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
Installation completed, as shown below:
2. Add the Lombok library
Next we need to add the latest Lombok library 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 codeCopy the code
If JDK 9+ can be added using modules, the configuration is as follows:
<annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> < version > 1.18.12 < / version > < path > / < / annotationProcessorPaths > duplicate codeCopy the code
3. Use Lombok
Coming to the most important part of Lombok’s first half, let’s look at code before Lombok:
public class Person { private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; }} Copy the codeCopy the code
This is the code after using Lombok:
@Getter @Setter public class Person { private Integer id; private String name; } Duplicate codeCopy the code
You can see that since Lombok, all Getter/Setter code has been done with one annotation, making it instantly more elegant.
All of Lombok’s notes are as follows:
val
: used before a local variable to declare the variable final;@NonNull
NullPointerException (NPE); NullPointerException (NPE);@Cleanup
: Automatically manages resources before local variables, cleans up resources before exiting within the current variable scope, and automatically generates try-finally code to close streams;@Getter/@Setter
For properties, you no longer need to write your own setters and getters. You can also specify the access scope.@ToString
: ToString(exclude= “id”); toString (exclude= “id”); Or @toString (callSuper=true, includeFieldNames=true) calls the ToString method of the parent class, containing all attributes;@EqualsAndHashCode
Automatically generate equals and hashCode methods on the class.@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
StaticName =”of” generates a static factory method that returns a class object. StaticName =”of” generates a static factory method that returns a class object.@Data
This is a class equivalent of using @ToString, @EqualSandhashCode, @getter, @setter, and @RequiredargsConstrutor at the same time, which is a very useful construtor for POJO classes.@Value
For classes, it is the immutable form of @data. It is equivalent to adding a final declaration to a property, providing getters instead of setters.@Builder
: classes, constructors, and methods that provide you with the complex Builder APIs that allow you to call Person.Builder ().name(” XXX “).city(” XXX “).build() as follows;@SneakyThrows
: Automatically throws checked exceptions without explicitly using a THROWS statement on methods;@Synchronized
: used on a method to declare the method synchronous and automatically lock the lock object, which is a private propertyLocking on this or its own class has the side effect that you can’t prevent uncontrolled code from locking this or its own class, which can cause race conditions or other thread errors.@Getter(lazy=true)
: can replace the classic Double Check Lock boilerplate code@Log
: Generates different types of log objects based on different annotations, but the instance name is log, and 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 uses are as follows:
1) use val
val sets = new HashSet<String>(); // final Set<String> sets = new HashSet<>(); Copy the codeCopy 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 codeCopy 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 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 codeCopy the code
(4) use Getter/Setter
@Setter(AccessLevel.PUBLIC) @Getter(AccessLevel.PROTECTED) private int id; private String shap; Copy the codeCopy 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) {LombokDemo(super=LombokDemo@48524010, name=null, age=0) System.out.println(new LombokDemo()); }} Copy the codeCopy the code
6 EqualsAndHashCode use
@EqualsAndHashCode(exclude = {"id", "shape"}, callSuper = false) public class LombokDemo { private int id; private String shap; } Duplicate codeCopy 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"); // Construct new LombokDemo() with no arguments; New LombokDemo(1, "Java", 2); }} Copy the codeCopy 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 codeCopy the code
Pet-name ruby SneakyThrows use
public class ThrowsTest { @SneakyThrows() public void read() { InputStream inputStream = new FileInputStream(""); } @SneakyThrows public void write() { throw new UnsupportedEncodingException(); Public void read() throws FileNotFoundException {InputStream InputStream = new FileInputStream(""); } public void write() throws UnsupportedEncodingException { throw new UnsupportedEncodingException(); }} Copy the codeCopy 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 codeCopy the code
Use Getter(lazy = true)
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); } return result; }} / / 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); } return result; }} Copy the codeCopy the code
The principle of analysis
We know that the Java compilation process can be roughly divided into three stages:
- Parse and populate symbol tables
- Annotation processing
- Analysis and bytecode generation
The compilation process is as follows:
Lombok is implemented using JSR 269 in JDK 6: Pluggable Annotation Processing API (compile-time Annotation processor), which gracefully converts Lombok Annotation code into regular Java methods at compile-time.
This can be verified in a program, such as the @data code we implemented at the beginning of this article:
After we compiled, we looked at the compiled source of the Person class and found that the code looked like this:
You can see that the Person class is modified at compile time by the annotation translator into regular Java methods, adding getters, setters, equals, hashCode, and so on.
The Lombok implementation process is as follows:
As you can see, Lombok dynamically modifies the AST to add new code (nodes) according to its annotation processor at compile time, after the Java source code has been abstracted into the syntax tree (AST), and then analyses the resulting bytecode (.class) file. This is how Lombok works.
Hand off a Lombok
We implement a simplified version of Lombok’s custom Getter method by doing the following steps:
- Define a custom annotation tag interface and implement a custom annotation handler;
- Using the JavAC API of Tools.jar to handle AST (Abstract Syntax Tree)
- Compile code using custom annotation handlers.
This allows for a simplified 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 only retain @target (elementtype.type) // used to modify class @interface MyGetter {// definition Getter} copies the codeCopy 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; Private JavacTrees JavacTrees; // Provide abstract syntax tree to be processed private TreeMaker TreeMaker; // Encapsulates some methods for creating an AST node. @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(); For (JCTree JCTree: jcClassDecl.defs) { if (jcTree.getKind().equals(Tree.Kind.VARIABLE)) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree; jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl); }} / / generation method for variable operation jcVariableDeclList forEach (jcVariableDecl - > {messenger. 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 expressions 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()); Jctree.jcvariabledecl param = treemaker.vardef (treemaker.modiFIERS (flags.parameter), jcVariableDecl.getName(), jcVariableDecl.vartype, null); List<JCTree.JCVariableDecl> parameters = List.of(param); 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) { return treeMaker.Exec( treeMaker.Assign( lhs, rhs ) ); }} Copy the codeCopy the code
A custom annotation handler is the key to implementing a simplified version of Lombok. We need to inherit AbstractProcessor classes and override their init() and process() methods, where we first query all variables, Add the corresponding method to the variable. We use TreeMaker objects and Names to handle the AST, as shown in the code above.
When this is done, we can add a Person class to try out our custom @mygetter function, like this:
@MyGetter public class Person { private String name; } Duplicate codeCopy the code
2. Compile code using custom annotation handlers
With all of the above procedures completed, we can compile the code to test the effect. First, let’s go to the root directory of the code and execute the following three commands.
The root directory is as follows:
① Use tools.jar to build custom annotators
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
③ Check 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 that the simple version of Lombok is now complete.
Here, by the way, I send you a classic learning materials, I used in university and work of the classic e-book library (including data structure, operating system, C++/C, network classics, front-end programming classics, Java related, programmer cognition, career development), interview and job summary are packed here.
Click here to get directly:
Computer Classics required reading list (including download methods)
Java to master the interview with the most complete information package (including download methods)
Lombok pros and cons
Lombok’s advantages are obvious. It allows us to write less code, saves development time, and makes code look more elegant.
Disadvantage 1: Reduced debuggability
Lombok automatically generates a lot of code for us, but it is generated at compile time, so it can be “lost” during development and debugging, which makes debugging code very inconvenient.
Disadvantage 2: Possible compatibility issues
Lombok is very intrusive to code, and because JDK versions are updated quickly and released every six months, Lombok is a third-party project and is maintained by an open source team, there is no way to ensure version compatibility and iteration speed, which can lead to version incompatibations.
Weakness 3: May pit teammates
If the new member of the team has never used Lombok before, when he or she pulls down the code, because Lombok plug-ins are not installed, he or she will be prompted with error messages such as unable to find the method when compiling the project. As a result, the project will fail to compile and the collaboration between the united members will be affected.
Disadvantage 4: Encapsulation is broken
Object-oriented encapsulation is defined as hiding internal data through access control and allowing outsiders to access and modify internal data only through a limited interface provided by a class.
That is, we should not blindly use Lombok to expose Getter/Setter methods for all fields, because some fields, such as the number of items in the cart, are not allowed to be modified directly in certain situations, which directly affect the purchase details and total price, and therefore should provide a uniform method for modifying them. Make associated changes rather than adding access and modification methods to each field.
I myself liver six copies of PDF < about Java entry to god >, the whole network spread more than 10W +, search “code farmers attack” after paying attention to the public number, in the background reply PDF, get all PDF
Six PDF links
summary
This article introduces the use and implementation of Lombok, which is implemented in JSR 269 with JDK 6: Pluggable Annotation Processing API (compile-time Annotation processor), which converts Lombok annotations into regular Java methods at compile-time by inheriting AbstractProcessor classes, Rewrite its init() and process() methods to implement a simplified version of Lombok. However, Lombok also has some disadvantages, such as reduced debuggability and potential compatibility issues, so the choice of whether to use Lombok and how to use Lombok depends on your business scenario and actual situation.
Welcome to pay attention to my public number “code farmers attack”. Share Python, Java, big data, machine learning, artificial intelligence and other technologies, pay attention to code farming technology improvement, career breakthrough, thinking transition, 100,000 + code farming growth charge first stop, accompany you have a dream to grow together.
In the end, good technology is not a cure-all, just like good shoes have to fit their feet!
Thanks for reading and I hope you found this article inspiring. If you feel good, share it with friends who need it. Thank you.
Reference & acknowledgements
Juejin. Cn/post / 684490…
www.tuicool.com/articles/y6…