Análisis del principio de implementación de Lombok.


prefacio

Creo que quienes desarrollan Java están familiarizados con Lombok. Basado en Lombok, podemos incrustar información complementaria en el código fuente sin cambiar el código original agregando algunas anotaciones simples a la clase de entidad, como los métodos comunes Get y Set.
Entonces, ¿algunos amigos han pensado en cuál es el principio de implementación subyacente?


1. Análisis de las anotaciones de Lombok.

Aquí tomamos el @Data más utilizado como ejemplo para el análisis.

package lombok;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

ilustrar:

  • Metaanotación @Target({ElementType.TYPE}): se utiliza para indicar que la anotación @Data se utiliza para describir una clase, interfaz (incluidos los tipos de anotaciones) o enumeración.
  • Metaanotación @Retention(RetentionPolicy.SOURCE): se utiliza para explicar que la anotación @Data es válida en el archivo fuente (es decir, el archivo fuente se conserva), se perderá durante la compilación y la información de la anotación no se conservará en el archivo .class.

En el proceso de desarrollo de programas, las anotaciones personalizadas más utilizadas son @Retention(RetentionPolicy.RUNTIME)las anotaciones en tiempo de ejecución. Combinadas con mecanismos como aspectos, interceptores y reflexiones, podemos realizar algún procesamiento lógico de acuerdo con las anotaciones en la clase durante la ejecución del programa.

Las anotaciones en Lombok son anotaciones del nivel de retención del archivo fuente, y la información de anotación correspondiente se perderá cuando se compila en un archivo de clase, entonces, ¿qué mecanismo utiliza para mejorar nuestra clase de entidad? ? ?

Suplemento:
Tres estrategias de retención de información de anotaciones:

public enum RetentionPolicy {
    
    
    /**
     * Annotations are to be discarded by the compiler.
     * 注解信息被编译器丢弃
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     * 注解信息会被编译器保留到class文件中,但是JVM运行期间不会保留。默认保留策略
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     * 注解信息会被编译器保留再class文件中,并且在JVM运行期间保留
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

2. Procesamiento de anotaciones durante la compilación

Después de JDK 6, se agregó JSR 269: Pluggable Annotation Processing API(procesador de anotaciones en el momento de la compilación)
. A través de este período de procesamiento, podemos mejorar la información de clase generada de acuerdo con la información de anotaciones durante la compilación, que es el núcleo de la implementación de Lombok.

Declare una serie de anotaciones a nivel del archivo fuente, personalice el procesador de anotaciones en tiempo de compilación heredando la clase AbstractProcessor, reescriba sus métodos init() y Process() y convierta las anotaciones de Lombok en rutinas Java en el método de tiempo de compilación.

Pero al mismo tiempo, Lombok también tiene algunas desventajas en el uso, como: 降低了可调试性Puede haber problemas de compatibilidad, por lo que tenemos que elegir si usar Lombok y cómo usarlo de acuerdo con nuestros escenarios comerciales y condiciones reales.

A continuación, analizamos el principio de lombok, tomando como ejemplo la herramienta de compilación javac de Oracle. Desde Java 6, javac ha comenzado a admitir la especificación API de procesamiento de anotaciones conectables JSR 269. Siempre que el programa implemente la API, se pueden llamar las anotaciones definidas cuando se compila el código fuente de Java.
Por ejemplo, ahora hay un programa A que implementa "JSR 269 API", entonces el proceso específico cuando se usa javac para compilar el código fuente es el siguiente:
1. javac analiza el código fuente y genera un árbol de sintaxis abstracta (AST);
2. Llame al programa A que implementa "JSR 269 API" durante el proceso de ejecución;
3. En este momento, el programa A puede completar su propia lógica, incluida la modificación del árbol de sintaxis abstracta (AST) obtenido en el primer paso;
4. javac utiliza el árbol de sintaxis abstracta (AST) modificado para generar archivos de código de bytes;

El diagrama de flujo detallado es el siguiente:
inserte la descripción de la imagen aquí
Se puede ver que en la etapa de compilación, cuando el código fuente de Java se abstrae en un árbol de sintaxis (AST), Lombok modificará dinámicamente el AST de acuerdo con su propio procesador de anotaciones y agregará nuevos códigos ( nodos) Después de ejecutar todo esto, el archivo de código de bytes final (.class) se genera mediante análisis, que es el principio de ejecución de Lombok.

3. Cómo utilizar Lombok

El método de utilización del proyecto Lombok es simple y se divide en cuatro pasos:

  1. Instale el complemento y agregue el paquete lombok.jar a la ruta de clase de compilación (el método de instalación específico se puede encontrar en Baidu);
  2. En la clase o método que necesita simplificarse, agregue la anotación que se utilizará;
  3. Compile el código fuente con una herramienta de compilación que admita lombok (para herramientas de compilación que admitan lombok, consulte 4. Herramientas de compilación que admitan lombok);
  4. El método o código correspondiente a la anotación de Lombok se genera automáticamente en el archivo de código de bytes compilado;

Tomando como ejemplo nuestra herramienta de desarrollo IDEA común, primero debemos instalar el complemento Lombok en IDEA. La función de este paso es agregar el procesador de anotaciones en tiempo de compilación de las anotaciones Lombok.
inserte la descripción de la imagen aquí
Es necesario introducir la dependencia Meven de lombok en el proyecto, que contiene principalmente toda la información de anotaciones de la declaración de lombok.

  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
  </dependency>

4. Procesador de anotaciones personalizado

La implementación de un procesador de anotaciones personalizado requiere tres pasos:
el primero es declarar la anotación personalizada, el segundo es implementar la interfaz del Procesador para procesar la anotación y el tercero es registrar el procesador de anotaciones.

1. Anotaciones personalizadas

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface CustomAnnotation{
    
    

}

2. Implementar la interfaz del procesador.

El procesador de anotaciones se puede personalizar implementando la interfaz Procesador. Aquí adoptamos un método más simple para implementar el procesador de anotaciones personalizado heredando la clase AbstractProcessor. Implementar el proceso del método abstracto para manejar las funciones que queremos.

El procesador de anotaciones ya tenía esta función en JDK1.5, pero el procesador de anotaciones en ese momento era apto y la API relacionada estaba en el paquete com.sun.mirror. A partir de JDK1.6, se han incluido funciones relacionadas con apt en javac y se proporcionan nuevas API en los dos paquetes javax.annotation.processing y javax.lang.model para procesar anotaciones. La antigua API del procesador de anotaciones se marcó como obsoleta en JDK1.7, y las API apt y relacionadas se eliminaron en JDK1.8.

public class CustomProcessor extends AbstractProcessor {
    
    
    //核心方法:注解处理过程
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
    
    
        return false;
    }

    //支持的注解类型
    @Override
    public Set<String> getSupportedAnnotationTypes() {
    
    
        Set<String> annotataions = new LinkedHashSet<String>();
        annotataions.add(CustomAnnotation.class.getCanonicalName());
        return annotataions;
    }

    //支持的java版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
    
    
        return SourceVersion.latestSupported();
    }
}

También puede especificar el tipo de anotación admitida y la versión de JDK mediante anotación:

@SupportedAnnotationTypes({
    
    "com.laowan.annotation.CustomAnnotation"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CustomProcessor extends AbstractProcessor {
    
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
    
    
        return false;
    }
}

Por razones de compatibilidad, especialmente para la plataforma Android, se recomienda utilizar los métodos sobrecargados getSupportedAnnotationTypes() y getSupportedSourceVersion() en lugar de @SupportedAnnotationTypes y @SupportedSourceVersion.

3. Registre el procesador de anotaciones.

Finalmente, también necesitamos registrar nuestro procesador de anotaciones personalizado.

Método 1:
crear una nueva resourcescarpeta en recursos, crear una nueva META-INFcarpeta en el directorio, crear una nueva carpeta de servicios en el directorio, crear un nuevo javax.annotation.processing.Processorarchivo en el directorio y luego escribir el nombre de clase completo de nuestro procesador de anotaciones personalizado en este archivo. :

com.laowan.annotation.CustomProcessor

Nota ⚠️:
Cuando utilice el método anterior para registrar un procesador de anotaciones personalizado, asegúrese de resourcesconfigurar la carpeta como Raíz de recursos;
de lo contrario, durante la compilación, solo le indicará que javax.annotation.processing.Processorno se puede encontrar el procesador configurado en el archivo.
inserte la descripción de la imagen aquí

Ejemplo, registrar el procesador de anotaciones en lombok:
inserte la descripción de la imagen aquí


Método 2: el método de registro anterior al servicio automático es demasiado problemático. Google nos ayudó a escribir un procesador de anotaciones para generar este archivo.
dirección de github: https://github.com/google/auto

Agregar dependencias:

<!-- https://mvnrepository.com/artifact/com.google.auto.service/auto-service -->
<dependency>
  <groupId>com.google.auto.service</groupId>
  <artifactId>auto-service</artifactId>
  <version>1.0.1</version>
</dependency>

Agregar anotaciones:

@AutoService(Processor.class)
public class CustomProcessor extends AbstractProcessor {
    
    
    ...
}

Ejemplo en Lombok:

@SupportedAnnotationTypes({
    
    "lombok.*"})
public static class ClaimingProcessor extends AbstractProcessor {
    
    
    public ClaimingProcessor() {
    
    
    }

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
    
        return true;
    }

    public SourceVersion getSupportedSourceVersion() {
    
    
        return SourceVersion.latest();
    }
}

Hágalo y descubra el poder del procesador de anotaciones. Más adelante solo necesitamos centrarnos en la lógica de procesamiento en el procesador de anotaciones.

5. Notas reales de MyGetter

Implementamos una versión simple de Lombok para personalizar un método Getter. Nuestros pasos de implementación son:

  1. Personalice una anotación MyGetter e implemente un procesador de anotaciones personalizado;
  2. Utilice la API javac de tools.jar para procesar AST (árbol de sintaxis abstracta)
  3. Compile el código con un procesador de anotaciones personalizado.

1. Crea un nuevo proyecto Maven myLombok

Contiene 2 submódulos, myget se usa para almacenar anotaciones personalizadas y procesadores de anotaciones, y el módulo person se usa para usar anotaciones personalizadas.
inserte la descripción de la imagen aquí

El archivo pom.xml del proyecto myLombok:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <modules>
        <module>myget</module>
        <module>person</module>
    </modules>

    <groupId>com.example</groupId>
    <artifactId>myLombok</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>myLombok</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>
</project>

Nota:
No utilice el compilador spring-boot-maven-plugin aquí, de lo contrario la compilación fallará.

2. Crea un nuevo submódulo myget

1. Agregar dependencia de Maven

    <dependencies>
        <!--Processor中的解析过程需要依赖tools.jar-->
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.6.0</version>
            <scope>system</scope>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>

        <!--采用google的auto-service来注入注解处理器-->
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0.1</version>
        </dependency>
    </dependencies>

2. Primero cree una anotación personalizada MyGetter.java, el código es el siguiente:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE) // 注解只在源码中保留
@Target(ElementType.TYPE) // 用于修饰类
public @interface MyGetter {
    
     // 定义 Getter

}

2. Implemente un procesador de anotaciones personalizado MyGetterProcessor, el código es el siguiente:

//这里的导入最好直接拷贝过去
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;

@AutoService(Processor.class) //自动注入注解处理器
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.example.myget.annotation.MyGetter")
public class MyGetterProcessor extends AbstractProcessor {
    
    

    private Messager messager; // 编译时期输入日志的
    private JavacTrees javacTrees; // 提供了待处理的抽象语法树
    private TreeMaker treeMaker; // 封装了创建AST节点的一些方法
    private Names names; // 提供了创建标识符的方法

    @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);
                        }
                    }
                    // 对于变量进行生成方法的操作
                    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<>();
        // 生成表达式 例如 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
                )
        );
    }
}

El procesador de anotaciones personalizado es lo más importante para que implementemos la versión simple de Lombok. Necesitamos heredar la clase AbstractProcessor y reescribir sus métodos init() y Process(). En el método Process(), primero consultamos todas las variables. , agregando el método correspondiente a la variable. Nosotros 使用 TreeMaker 对象和 Names 来处理 AST,这一步需要依赖 tool.jar, como se muestra en el código anterior.

3. Crea una nueva persona de submódulo.

1. Introducir la dependencia de Maven

      <dependency>
          <groupId>com.example</groupId>
          <artifactId>myget</artifactId>
          <version>0.0.1-SNAPSHOT</version>
      </dependency>

2. Agregue la clase Persona y agregue la anotación de clase @MyGetter

@MyGetter
public class Person {
    
    
    private String name;
}

4. Compile y vea los resultados.

1. Ejecutar compilación y empaquetado.
inserte la descripción de la imagen aquí

2. Verifique el resultado de la compilación de la clase Persona y el método get se genera automáticamente, lo que indica que la anotación personalizada @MyGetter entra en vigor.
inserte la descripción de la imagen aquí


Resumir

Este artículo presenta principalmente el principio de implementación de Lombok y demuestra el uso del procesador de anotaciones en tiempo de compilación a través de la anotación personalizada @MyGetter.
1. La RetentionPolicy de la información de la anotación se puede configurar a través de la metaanotación @Retention:

  • SOURCEPolítica de retención del archivo fuente, la información de anotaciones se descartará durante la compilación
  • CLASSPolítica de retención de archivos de clase, el compilador retendrá la información de anotación en el archivo de clase, pero no se retendrá durante la operación de JVM. política de retención predeterminada
  • RUNTIMEPolítica de retención en tiempo de ejecución: el compilador retendrá la información de anotación en el archivo de clase y la retendrá durante la operación JVM.

2. Las anotaciones en Lombok son anotaciones de la política de retención de archivos fuente SOURCE. El principio de implementación es utilizar el JSR 269 agregado después de JDK 6: ( Pluggable Annotation Processing APIprocesador de anotaciones en tiempo de compilación). La información de clase generada se mejora.

3. Existen dos métodos de registro de anotaciones personalizadas en tiempo de compilación: método de recursos y método de servicio automático.

Supongo que te gusta

Origin blog.csdn.net/w1014074794/article/details/128244097
Recomendado
Clasificación