Рубрики
Без рубрики

Внедрение пользовательской аннотации Ломбока

Узнайте, как реализовать пользовательскую аннотацию с помощью обработчиков Lombok.

Автор оригинала: baeldung.

1. Обзор

В этом учебнике Мы будем реализовывать пользовательскую аннотацию с использованием Lombok для удаления котельной вокруг реализации Singletons в приложении.

Lombok — это мощная Java-библиотека, целью которой является сокращение кода котловой плиты на Java. Если вы не знакомы с ним, здесь вы можете найти введение во все особенности Ломбок .

Важное замечание: Lombok 1.14.8 является последней совместимой версией, которую мы можем использовать, чтобы следовать этому учебнику. С версии 1.16.0, Lombok спрятал свой внутренний API, и это больше не возможно создать пользовательские аннотации в том, как мы представляем здесь.

2. Ломбок как аннотация процессора

Java позволяет разработчикам приложений обрабатывать аннотации на этапе компиляции; самое главное, для создания новых файлов на основе аннотации. В результате такие библиотеки, как Hibernate, позволяют разработчикам уменьшать код котельной и использовать вместо этого аннотации.

Аннотация обработки подробно охвачены в этом учебнике .

Таким же образом, Проект Ломбок также работает как аннотация процессора. Он обрабатывает аннотацию, делегируя ее конкретному обработчику.

При делегировании он отправляет аннотированному обработчику аннотированное дерево синтаксиса компилятора (AST). Таким образом, он позволяет обработчикам изменять код путем расширения AST.

3. Внедрение пользовательской аннотации

3.1. Расширение Ломбока

Удивительно, но Ломбок не легко расширить и добавить пользовательские аннотации.

На самом деле, новые версии Lombok используют Shadow ClassLoader (SCL), чтобы скрыть .class файлы в Ломбоке в .scl Файлы. Таким образом, это заставляет разработчиков раскошелиться на исходный код Lombok и реализовать аннотации там.

С положительной стороны, он упрощает процесс расширения пользовательских обработчиков и модификации AST с использованием функций утилиты.

3.2. Аннотация Синглтона

Как правило, для реализации класса Singleton требуется много кода. Для приложений, которые не используют фреймворок впрыска зависимости, это просто шаблонный материал.

Например, вот один из способов реализации класса Singleton:

public class SingletonRegistry {
    private SingletonRegistry() {}
    
    private static class SingletonRegistryHolder {
        private static SingletonRegistry registry = new SingletonRegistry();
    }
    
    public static SingletonRegistry getInstance() {
        return SingletonRegistryHolder.registry;
    }
	
    // other methods
}

В отличие от этого, это, как это будет выглядеть, если мы реализуем аннотацию его версии:

@Singleton
public class SingletonRegistry {}

И, Синглтон аннотация:

@Target(ElementType.TYPE)
public @interface Singleton {}

Здесь важно подчеркнуть, что обработчик Lombok Singleton будет генерировать код реализации, который мы видели выше, изменяя AST.

Поскольку AST отличается для каждого компилятора, для каждого требуется пользовательский обработчик Lombok. Lombok позволяет пользовательские обработчики для Явак (используется Maven/Gradle и Netbeans) и компилятором Eclipse.

В следующих разделах мы будем реализовывать наш обработчик Аннотации для каждого компилятора.

4. Внедрение обработчика для javac

4.1. Зависимость от Maven

Давайте вытащим необходимые зависимости для Ломбок первый:


    org.projectlombok
    lombok
    1.14.8

Кроме того, нам также потребуется инструменты.jar поставляется с Java для доступа и изменения Явак Аст. Тем не менее, нет репозитория Maven для него. Самый простой способ включить это в проект Maven , добавив его в профиль:


    
        default-tools.jar
            
                
                    java.vendor
                    Oracle Corporation
                
            
            
                
                    com.sun
                    tools
                    ${java.version}
                    system
                    ${java.home}/../lib/tools.jar
                
            
    

4.2. Расширение JavacAnnotationHandler

Для реализации пользовательского Явак обработчик, мы должны продлить Lombok в JavacAnnotationHandler:

public class SingletonJavacHandler extends JavacAnnotationHandler {
    public void handle(
      AnnotationValues annotation,
      JCTree.JCAnnotation ast,
      JavacNode annotationNode) {}
}

Далее мы будем реализовывать ручка () метод. Здесь аннотация AST доступна в качестве параметра Lombok.

4.3. Изменение АСТ

Вот где все становится сложнее. Как правило, изменение существующего АСТ не так просто.

К счастью, Lombok предоставляет много полезных функций в JavacHandlerUtil и JavacTreeMaker для генерации кода и введения его в AST. С этой если думать, давайте использовать эти функции и создать код для наших СинглтонРегистрация:

public void handle(
  AnnotationValues annotation,
  JCTree.JCAnnotation ast,
  JavacNode annotationNode) {
    Context context = annotationNode.getContext();
    Javac8BasedLombokOptions options = Javac8BasedLombokOptions
      .replaceWithDelombokOptions(context);
    options.deleteLombokAnnotations();
    JavacHandlerUtil
      .deleteAnnotationIfNeccessary(annotationNode, Singleton.class);
    JavacHandlerUtil
      .deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel");
    JavacNode singletonClass = annotationNode.up();
    JavacTreeMaker singletonClassTreeMaker = singletonClass.getTreeMaker();
    addPrivateConstructor(singletonClass, singletonClassTreeMaker);

    JavacNode holderInnerClass = addInnerClass(singletonClass, singletonClassTreeMaker);
    addInstanceVar(singletonClass, singletonClassTreeMaker, holderInnerClass);
    addFactoryMethod(singletonClass, singletonClassTreeMaker, holderInnerClass);
}

Важно отметить, что deleteAnnotationIfNeccessary () и удалитьИмпортОтмысоединениеUnit () методы, предоставляемые Lombok, используются для удаления аннотаций и любого импорта для них.

Теперь давайте посмотрим, как реализуются другие частные методы для генерации кода. Во-первых, мы создаем частного конструктора:

private void addPrivateConstructor(
  JavacNode singletonClass,
  JavacTreeMaker singletonTM) {
    JCTree.JCModifiers modifiers = singletonTM.Modifiers(Flags.PRIVATE);
    JCTree.JCBlock block = singletonTM.Block(0L, nil());
    JCTree.JCMethodDecl constructor = singletonTM
      .MethodDef(
        modifiers,
        singletonClass.toName(""),
        null, nil(), nil(), nil(), block, null);

    JavacHandlerUtil.injectMethod(singletonClass, constructor);
}

Далее, внутренняя СинглтонХолдер класс:

private JavacNode addInnerClass(
  JavacNode singletonClass,
  JavacTreeMaker singletonTM) {
    JCTree.JCModifiers modifiers = singletonTM
      .Modifiers(Flags.PRIVATE | Flags.STATIC);
    String innerClassName = singletonClass.getName() + "Holder";
    JCTree.JCClassDecl innerClassDecl = singletonTM
      .ClassDef(modifiers, singletonClass.toName(innerClassName),
      nil(), null, nil(), nil());
    return JavacHandlerUtil.injectType(singletonClass, innerClassDecl);
}

Теперь мы добавим переменную экземпляра в класс держателя:

private void addInstanceVar(
  JavacNode singletonClass,
  JavacTreeMaker singletonClassTM,
  JavacNode holderClass) {
    JCTree.JCModifiers fieldMod = singletonClassTM
      .Modifiers(Flags.PRIVATE | Flags.STATIC | Flags.FINAL);

    JCTree.JCClassDecl singletonClassDecl
      = (JCTree.JCClassDecl) singletonClass.get();
    JCTree.JCIdent singletonClassType
      = singletonClassTM.Ident(singletonClassDecl.name);

    JCTree.JCNewClass newKeyword = singletonClassTM
      .NewClass(null, nil(), singletonClassType, nil(), null);

    JCTree.JCVariableDecl instanceVar = singletonClassTM
      .VarDef(
        fieldMod,
        singletonClass.toName("INSTANCE"),
        singletonClassType,
        newKeyword);
    JavacHandlerUtil.injectField(holderClass, instanceVar);
}

Наконец, давайте добавим метод фабрики для доступа к однотонного объекта:

private void addFactoryMethod(
  JavacNode singletonClass,
  JavacTreeMaker singletonClassTreeMaker,
  JavacNode holderInnerClass) {
    JCTree.JCModifiers modifiers = singletonClassTreeMaker
      .Modifiers(Flags.PUBLIC | Flags.STATIC);

    JCTree.JCClassDecl singletonClassDecl
      = (JCTree.JCClassDecl) singletonClass.get();
    JCTree.JCIdent singletonClassType
      = singletonClassTreeMaker.Ident(singletonClassDecl.name);

    JCTree.JCBlock block
      = addReturnBlock(singletonClassTreeMaker, holderInnerClass);

    JCTree.JCMethodDecl factoryMethod = singletonClassTreeMaker
      .MethodDef(
        modifiers,
        singletonClass.toName("getInstance"),
        singletonClassType, nil(), nil(), nil(), block, null);
    JavacHandlerUtil.injectMethod(singletonClass, factoryMethod);
}

Очевидно, что метод фабрики возвращает переменную экземпляра из класса держателя. Давайте реализуем это также:

private JCTree.JCBlock addReturnBlock(
  JavacTreeMaker singletonClassTreeMaker,
  JavacNode holderInnerClass) {

    JCTree.JCClassDecl holderInnerClassDecl
      = (JCTree.JCClassDecl) holderInnerClass.get();
    JavacTreeMaker holderInnerClassTreeMaker
      = holderInnerClass.getTreeMaker();
    JCTree.JCIdent holderInnerClassType
      = holderInnerClassTreeMaker.Ident(holderInnerClassDecl.name);

    JCTree.JCFieldAccess instanceVarAccess = holderInnerClassTreeMaker
      .Select(holderInnerClassType, holderInnerClass.toName("INSTANCE"));
    JCTree.JCReturn returnValue = singletonClassTreeMaker
      .Return(instanceVarAccess);

    ListBuffer statements = new ListBuffer<>();
    statements.append(returnValue);

    return singletonClassTreeMaker.Block(0L, statements.toList());
}

В результате, у нас есть модифицированный AST для нашего класса Singleton.

4.4. Регистрация обработчика с SPI

До сих пор мы реализовали только обработчик Lombok для создания AST для нашей СинглтонРегистрация. Здесь важно повторить, что Lombok работает как аннотация процессора.

Как правило, аннотации процессоры обнаруживаются через META-INF/услуги . Ломбок также поддерживает список обработчиков таким же образом. Кроме того, он использует фреймворок под названием SPI для автоматического обновления списка обработчиков .

Для нашей цели, мы будем использовать metainf-услуги :


    org.kohsuke.metainf-services
    metainf-services
    1.8

Теперь мы можем зарегистрировать нашего обработчика в Lombok:

@MetaInfServices(JavacAnnotationHandler.class)
public class SingletonJavacHandler extends JavacAnnotationHandler {}

Это создаст lombok.javac.JavacAnnotationHandler файл во время компиляции. Такое поведение является общим для всех фреймворка SPI.

5. Внедрение обработчика для Eclipse IDE

5.1. Зависимость от Maven

Похожи на инструменты.jar мы добавили для доступа к AST для Явак , мы добавим затмение jdt для Eclipse IDE:


    org.eclipse.jdt
    core
    3.3.0-v_771

5.2. Расширение EclipseAnnotationHandler

Теперь мы продлеваем ЗатмениеАнтацияХендлер для нашего обработчика Eclipse:

@MetaInfServices(EclipseAnnotationHandler.class)
public class SingletonEclipseHandler
  extends EclipseAnnotationHandler {
    public void handle(
      AnnotationValues annotation,
      Annotation ast,
      EclipseNode annotationNode) {}
}

Вместе с аннотацией SPI MetaInfServices , этот обработчик выступает в качестве процессора для нашего Синглтон аннотация. Следовательно, всякий раз, когда класс компилируется в Eclipse IDE, обработчик преобразует аннотированный класс в однотонную реализацию.

5.3. Изменение АСТ

С нашим обработчиком, зарегистрированным в SPI, теперь мы можем начать редактировать AST для компилятора Eclipse:

public void handle(
  AnnotationValues annotation,
  Annotation ast,
  EclipseNode annotationNode) {
    EclipseHandlerUtil
      .unboxAndRemoveAnnotationParameter(
        ast,
        "onType",
        "@Singleton(onType=", annotationNode);
    EclipseNode singletonClass = annotationNode.up();
    TypeDeclaration singletonClassType
      = (TypeDeclaration) singletonClass.get();
    
    ConstructorDeclaration constructor
      = addConstructor(singletonClass, singletonClassType);
    
    TypeReference singletonTypeRef 
      = EclipseHandlerUtil.cloneSelfType(singletonClass, singletonClassType);
    
    StringBuilder sb = new StringBuilder();
    sb.append(singletonClass.getName());
    sb.append("Holder");
    String innerClassName = sb.toString();
    TypeDeclaration innerClass
      = new TypeDeclaration(singletonClassType.compilationResult);
    innerClass.modifiers = AccPrivate | AccStatic;
    innerClass.name = innerClassName.toCharArray();
    
    FieldDeclaration instanceVar = addInstanceVar(
      constructor,
      singletonTypeRef,
      innerClass);
    
    FieldDeclaration[] declarations = new FieldDeclaration[]{instanceVar};
    innerClass.fields = declarations;
    
    EclipseHandlerUtil.injectType(singletonClass, innerClass);
    
    addFactoryMethod(
      singletonClass,
      singletonClassType,
      singletonTypeRef,
      innerClass,
      instanceVar);
}

Далее, частный конструктор:

private ConstructorDeclaration addConstructor(
  EclipseNode singletonClass,
  TypeDeclaration astNode) {
    ConstructorDeclaration constructor
      = new ConstructorDeclaration(astNode.compilationResult);
    constructor.modifiers = AccPrivate;
    constructor.selector = astNode.name;
    
    EclipseHandlerUtil.injectMethod(singletonClass, constructor);
    return constructor;
}

А для экземпляра переменная:

private FieldDeclaration addInstanceVar(
  ConstructorDeclaration constructor,
  TypeReference typeReference,
  TypeDeclaration innerClass) {
    FieldDeclaration field = new FieldDeclaration();
    field.modifiers = AccPrivate | AccStatic | AccFinal;
    field.name = "INSTANCE".toCharArray();
    field.type = typeReference;
    
    AllocationExpression exp = new AllocationExpression();
    exp.type = typeReference;
    exp.binding = constructor.binding;
    
    field.initialization = exp;
    return field;
}

Наконец, заводской метод:

private void addFactoryMethod(
  EclipseNode singletonClass,
  TypeDeclaration astNode,
  TypeReference typeReference,
  TypeDeclaration innerClass,
  FieldDeclaration field) {
    
    MethodDeclaration factoryMethod
      = new MethodDeclaration(astNode.compilationResult);
    factoryMethod.modifiers 
      = AccStatic | ClassFileConstants.AccPublic;
    factoryMethod.returnType = typeReference;
    factoryMethod.sourceStart = astNode.sourceStart;
    factoryMethod.sourceEnd = astNode.sourceEnd;
    factoryMethod.selector = "getInstance".toCharArray();
    factoryMethod.bits = ECLIPSE_DO_NOT_TOUCH_FLAG;
    
    long pS = factoryMethod.sourceStart;
    long pE = factoryMethod.sourceEnd;
    long p = (long) pS << 32 | pE;
    
    FieldReference ref = new FieldReference(field.name, p);
    ref.receiver = new SingleNameReference(innerClass.name, p);
    
    ReturnStatement statement
      = new ReturnStatement(ref, astNode.sourceStart, astNode.sourceEnd);
    
    factoryMethod.statements = new Statement[]{statement};
    
    EclipseHandlerUtil.injectMethod(singletonClass, factoryMethod);
}

Кроме того, мы должны подключить этот обработчик к классу загрузки Eclipse. Как правило, это делается путем добавления следующего параметра в затмение.ini:

-Xbootclasspath/a:singleton-1.0-SNAPSHOT.jar

6. Пользовательские аннотации в IntelliJ

Вообще говоря, новый обработчик Lombok необходим для каждого компилятора, как Явак и обработчики Eclipse, которые мы реализовали раньше.

И наоборот, IntelliJ не поддерживает обработчик Lombok. Она обеспечивает поддержку Lombok через плагин вместо.

Из-за этого, любая новая аннотация должна поддерживаться плагином явно. Это также относится к любой аннотации, добавленной в Ломбок.

7. Заключение

В этой статье мы внедрили пользовательскую аннотацию с использованием обработчиков Lombok. Мы также кратко рассмотрели модификацию AST для наших Синглтон аннотация в различных компиляторах, доступная в различных IDEs.

Полный исходный код доступен более на Github .