The copyright of this article belongs to the official account of WeChat public code ID:onblog. If it is reproduced, please keep the original statement of the paragraph, and the offender will investigate it. If there are any shortcomings, please welcome the official account of WeChat public. ]
Yesterday, I found out by accident lombok The author of the video just wrote a few fields in the entity class, which can be automatically compiled into a class file containing setter, getter, toString() and other methods. Looking at it is quite novel, so I studied the principle and sorted it out.
1. Where to start
The author's process in the video is as follows:
(1) Write Java file and @ Data annotation on class
@Data public class Demo { private String name; private double abc; }
(2)javac compilation, lombok.jar It's Lombok's jar package.
javac -cp lombok.jar Demo.java
(3)javap view Demo.class Class file
javap Demo
Demo.class:
public class Demo { public Demo(); public java.lang.String getName(); public void setName(java.lang.String); public double getAbc(); public void setAbc(double); }
You can see Demo.class There are many undefined setter and getter methods in the video, and the video author mainly uses annotation + compilation, so let's start from this aspect.
2. Necessary knowledge
2.1 notes
I believe that most people have used annotations. Many people will customize annotations and use reflection to make small things. But this article is not about using annotations and reflection to customize behavior at run time, but at compile time.
Four meta annotations are indispensable for custom annotation.
@Retention: annotation retention period
Retention type | explain |
---|---|
SOURCE | Only reserved in the source code, the compiled class does not exist |
CLASS | Keep in the class file, but the JVM will not load |
RUNTIME | It always exists. The JVM will be loaded and can be obtained by reflection |
@Target: used to mark which types can be applied
Element type | Applicable occasions |
---|---|
ANOTATION_TYPE | Annotation type declaration |
PACKAGE | package |
TYPE | Class, enumeration, interface, annotation |
METHOD | method |
CONSTRUCTOR | Construction method |
FIELD | Member fields, enumerating constants |
PARAMETER | Method or constructor parameters |
LOCAL_VARIABLE | local variable |
TYPE_PARAMETER | Type parameter |
TYPE_USE | Type usage |
@Documented: the function is to be able to include elements in annotations in Javadoc
@Inherited: inherited. Suppose annotation A uses this annotation, class B uses annotation A, class C inherits class B, and class C also uses annotation A. (used here to distinguish easy to understand, actually annotated)
2.1 annotation processor
Annotation processor is a special tool for processing annotations in javac package. All annotation processors must inherit the abstract class AbstractProcessor and override several of its methods.
The annotation processor is running in its own JVM. javac starts a full java virtual machine to run the annotation processor, which means you can use anything you use in other java applications. The abstract method process must be rewritten. In this method, the annotation processor can traverse all the source files, and then obtain all the elements marked by the annotation we need to process through the RoundEnvironment class. The elements here can represent package, class, interface, method, attribute, etc. In the process of reprocessing, specific tool classes can be used to automatically generate specific. java files or. Class files to help us deal with custom annotations.
A common annotation processor file is as follows:
package com.example; import java.util.LinkedHashSet; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { return false; } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotataions = new LinkedHashSet<String>(); annotataions.add("com.example.MyAnnotation"); return annotataions; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); } }
- init(ProcessingEnvironment processingEnv): all annotation processor classes must have a parameterless constructor. However, there is a special method, init(), which is called by the annotation processing tool with ProcessingEnvironment as the parameter. ProcessingEnvironment provides some utility classes, such as Elements, Types and filers.
- Process (set <? Extensions typeelement > announcements, RoundEnvironment Env): This is similar to the main() method of each processor. You can code in this method to scan, process annotations, and generate java files. Using the RoundEnvironment parameter, you can query the elements annotated by a specific annotation.
- getSupportedAnnotationTypes(): in this method you must specify which annotations should be registered by the annotation handler. Note that its return value is a String collection that contains the full name of the annotation type your annotation processor wants to handle. In other words, here you define which annotations your annotation processor will process.
- getSupportedSourceVersion(): used to specify the java version you are using. Usually you should go back SourceVersion.latestSupported() . However, if you have enough reasons to stick with java 6, you can also return SourceVersion.RELEASE_6.
For the two methods getSupportedAnnotationTypes() and getSupportedSourceVersion(), you can also use the corresponding annotation instead. The code is as follows:
@SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedAnnotationTypes("com.example.MyAnnotation") public class MyProcessor extends AbstractProcessor { ....
However, in order to be compatible with Java 6, it is better to overload these two methods.
3. Start coding
We have learned knowledge and now we are going to fight.
3.1 user defined notes
@Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface Data { }
3.2 custom annotation processor
public class DataAnnotationProcessor extends AbstractProcessor { private Messager messager; //For printing logs private Elements elementUtils; //For processing elements private Types typeUtils; private Filer filer; //Used to create java files or class files @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); messager = processingEnv.getMessager(); elementUtils = processingEnv.getElementUtils(); filer = processingEnv.getFiler(); typeUtils = processingEnvironment.getTypeUtils(); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public Set<String> getSupportedAnnotationTypes(){ Set<String> set = new HashSet<>(); set.add(Data.class.getCanonicalName()); return Collections.unmodifiableSet(set); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { messager.printMessage(Diagnostic.Kind.NOTE,"-----Start automatic source generation"); try { // identifier boolean isClass = false; // Fully qualified name of class String classAllName = null; // Return annotated node Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Data.class); Element element = null; for (Element e : elements) { // If the comment is on a class if (e.getKind() == ElementKind.CLASS && e instanceof TypeElement) { TypeElement t = (TypeElement) e; isClass = true; classAllName = t.getQualifiedName().toString(); element = t; break; } } // If no comment is used on the class, it will return directly, return false to stop compilation if (!isClass) { return true; } // Return all nodes in the class List<? extends Element> enclosedElements = element.getEnclosedElements(); // Save a collection of fields Map<TypeMirror, Name> fieldMap = new HashMap<>(); for (Element ele : enclosedElements) { if (ele.getKind() == ElementKind.FIELD) { //Type of field TypeMirror typeMirror = ele.asType(); //Name of the field Name simpleName = ele.getSimpleName(); fieldMap.put(typeMirror, simpleName); } } // Generate a Java source file JavaFileObject sourceFile = filer.createSourceFile(getClassName(classAllName)); // Write code createSourceFile(classAllName, fieldMap, sourceFile.openWriter()); // Manual compilation compile(sourceFile.toUri().getPath()); } catch (IOException e) { messager.printMessage(Diagnostic.Kind.ERROR,e.getMessage()); } messager.printMessage(Diagnostic.Kind.NOTE,"-----Auto generate source code complete"); return true; } private void createSourceFile(String className, Map<TypeMirror, Name> fieldMap, Writer writer) throws IOException { // Generate source code JavaWriter jw = new JavaWriter(writer); jw.emitPackage(getPackage(className)); jw.beginType(getClassName(className), "class", EnumSet.of(Modifier.PUBLIC)); for (Map.Entry<TypeMirror, Name> map : fieldMap.entrySet()) { String type = map.getKey().toString(); String name = map.getValue().toString(); //field jw.emitField(type, name, EnumSet.of(Modifier.PRIVATE)); } for (Map.Entry<TypeMirror, Name> map : fieldMap.entrySet()) { String type = map.getKey().toString(); String name = map.getValue().toString(); //getter jw.beginMethod(type, "get" + humpString(name), EnumSet.of(Modifier.PUBLIC)) .emitStatement("return " + name) .endMethod(); //setter jw.beginMethod("void", "set" + humpString(name), EnumSet.of(Modifier.PUBLIC), type, "arg") .emitStatement("this." + name + " = arg") .endMethod(); } jw.endType().close(); } /** * Compile file * @param path * @throws IOException */ private void compile(String path) throws IOException { //Get the compiler JavaCompiler complier = ToolProvider.getSystemJavaCompiler(); //Document manager StandardJavaFileManager fileMgr = complier.getStandardFileManager(null, null, null); //get files Iterable units = fileMgr.getJavaFileObjects(path); //Compile task JavaCompiler.CompilationTask t = complier.getTask(null, fileMgr, null, null, null, units); //Compile t.call(); fileMgr.close(); } /** * Hump naming * * @param name * @return */ private String humpString(String name) { String result = name; if (name.length() == 1) { result = name.toUpperCase(); } if (name.length() > 1) { result = name.substring(0, 1).toUpperCase() + name.substring(1); } return result; } /** * Read class name * @param name * @return */ private String getClassName(String name) { String result = name; if (name.contains(".")) { result = name.substring(name.lastIndexOf(".") + 1); } return result; } /** * Read package name * @param name * @return */ private String getPackage(String name) { String result = name; if (name.contains(".")) { result = name.substring(0, name.lastIndexOf(".")); }else { result = ""; } return result; } }
In the custom annotation processor, the annotation explains the idea of each step in detail. First, read the annotated node, judge whether it is a class node, then generate a java source file, write java code using the javawriter framework, and compile the Java source file manually.
The javawriter framework is referenced as follows:
compile 'com.squareup:javawriter:2.5.1'
3.3 registration of annotation processor
The copyright of this article belongs to the official account of WeChat public code ID:onblog. If it is reproduced, please keep the original statement of the paragraph, and the offender will investigate it. If there are any shortcomings, please welcome the official account of WeChat public. ]
After coding, you also need to register the annotation processor with the javac compiler, so you need to provide a. jar file. Like other. jar files, you package your compiled annotation processor into this file. Also, in your. jar file, you have to package a special file javax.annotation.processing.Processor to META-INF/services directory. So your. jar file directory structure looks like this:
MyProcess.jar -com -example -MyProcess.class -META-INF -services -javax.annotation.processing.Processor
javax.annotation.processing The content of the. Processor file is a list, and each line is the full name of an annotation processor. For example:
com.example.MyProcess
In IDE, you only need to create a new meta-inf / services in the resources directory/ javax.annotation.processing . processor file.
Other registration methods
The previous registration method is very low-level, which is recommended by individuals. When there are too many annotation processors, this method is too cumbersome, so the other way is to use the framework of automatically registering annotation processors.
Add a reference to Google auto register annotation Library
implementation 'com.google.auto.service:auto-service:1.0-rc4'
Declared before annotation handler class
@AutoService(Processor.class)
4. Packing
At this time, we can use the project as jar package. Next, we will demonstrate the use process.
(1) Write a Demo.java
import cn.zyzpp.annotation.Data; @Data public class Demo { private String name; private double abc; }
(2) Compile the java file in the Demo.java Open the console window under the folder, and remember to put the packaged jar package together in this directory.
javac -cp annotation-1.0-SNAPSHOT.jar Demo.java
(3) Using javap to view the compiled Demo.class
Compiled from "Demo.java" public class Demo { public Demo(); public double getAbc(); public void setAbc(double); public java.lang.String getName(); public void setName(java.lang.String); }
Look again at this time Demo.java code
public class Demo { private double abc; private String name; public double getAbc() { return abc; } public void setAbc(double arg) { this.abc = arg; } public String getName() { return name; } public void setName(String arg) { this.name = arg; } }
So far, we have officially developed a plug-in for automatically generating getter and setter methods. Some small partners may think that this is not very useful, and it can be done very easily with the IDE shortcut key. In fact, knowledge has been learned. What colorful framework can be made depends on the wisdom of our partners. For example, we will generally create a new Entity class, and then based on this new Dao layer, Service layer code. With the knowledge described in this article, we can create a code generator suitable for ourselves, saving time and improving development efficiency.
Bonus
Installation and use of IntelliJ IDEA lombok plug-in
supplement
It is better to rely on the processor as a Jar package. If the package fails, you can specify the version.
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <executions> <execution> <id>default-compile</id> <configuration> <compilerArgument>-proc:none</compilerArgument> <source>1.8</source> <target>1.8</target> </configuration> </execution> <execution> <id>default-testCompile</id> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </execution> </executions> </plugin> </plugins>
Copyright notice
The copyright of this article belongs to the official account of WeChat public code ID:onblog. If it is reproduced, please keep the original statement of the paragraph, and the offender will investigate it. If there are any shortcomings, please welcome the official account of WeChat public. ]