99% программистов используют Ломбок, принцип такой простой? У меня тоже есть! |Рекомендуемая коллекция

Java Java EE
99% программистов используют Ломбок, принцип такой простой? У меня тоже есть! |Рекомендуемая коллекция

Ромен Роллан сказал: «В мире есть только один вид героизма, а именно, увидев правду жизни и все-таки любя жизнь».

Я считаю, что большинство людей знакомы с Lombok, но мало что известно о принципе его реализации и недостатках.Эта статья начнется с принципа Lombok и создаст упрощенную версию Lombok, чтобы вы могли понять причину этой популярной технологии. принцип выполнения, анализ его преимуществ и недостатков.

Введение

Прежде чем говорить о принципе, давайте рассмотрим Ломбок (старые водители могут пропустить этот абзац и ознакомиться с содержанием принципиальной части).

Lombok — очень популярный проект с открытым исходным кодом (GitHub.com/R как дочерний элемент ITS...), используя его, можно эффективно решить эти утомительные и повторяющиеся коды в проектах Java, такие как Setter, Getter, toString, equals, hashCode и ненулевое суждение и т. д., могут быть эффективно решены Lombok.

использовать

1. Добавьте плагин Ломбок

Плагин Lombok должен быть установлен в среде IDE для нормального вызова кода, модифицированного Lombok.В качестве примера рассмотрим Idea.

  • Нажмите «Файл» > «Настройки» > «Плагины», чтобы перейти на страницу управления плагинами.
  • Щелкните Обзор репозиториев...
  • Поиск плагина Ломбок
  • Нажмите «Установить плагин», чтобы установить плагин.
  • Перезапустите IntelliJ IDEA.

Установка завершена, как показано на следующем рисунке:

2. Добавьте библиотеку Ломбок

Далее нам нужноДобавьте последнюю версию библиотеки Lombok в свой проект, если это проект Maven, добавьте следующую конфигурацию непосредственно в 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>

еслиJDK 9+Его можно добавить как модуль, и конфигурация выглядит следующим образом:

<annotationProcessorPaths>
	<path>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.12</version>
	</path>
</annotationProcessorPaths>

3. Используйте Ломбок

Далее идет самая важная часть использования Lombok в первой половине Давайте сначала посмотрим на код перед использованием 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;
    }
}

Вот код после использования Lombok:

@Getter
@Setter
public class Person {
    private Integer id;
    private String name;
}

Видно, что после Ломбока,Все предыдущие коды Getter/Setter выполняются с помощью одной аннотации, что мгновенно делает код более элегантным..

Все аннотации Ломбока следующие:

  • val: используется перед локальными переменными, что эквивалентно объявлению переменной как final;
  • @NonNull: добавление этой аннотации к параметру метода автоматически проверит, является ли параметр пустым в методе.Если он пуст, будет выброшено NPE (NullPointerException);
  • @Cleanup: автоматическое управление ресурсами, используемыми перед локальными переменными, будет автоматически очищать ресурсы в области действия текущей переменной перед выходом и автоматически генерировать код, такой как try-finally, для закрытия потока;
  • @Getter/@Setter: при использовании свойств вам больше не нужно самостоятельно писать методы установки и получения, а также вы можете указать диапазон доступа;
  • @ToString: метод toString может быть автоматически переопределен при использовании в классе. Конечно, можно добавить и другие параметры, например @ToString(exclude="id") для исключения атрибута id, или @ToString(callSuper=true, includeFieldNames= true) для вызова метода toString родительского класса, содержащего все свойства;
  • @EqualsAndHashCode: используется для автоматического создания метода equals и метода hashCode в классе;
  • @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor: используется в классе, он автоматически создает конструктор без параметров и конструктор, который использует все параметры, а также конструктор, который принимает все атрибуты @NonNull в качестве параметров.Если указан параметр staticName="of", статическая фабрика, которая возвращает также будет сгенерирован объект класса метод, что гораздо удобнее, чем использование конструктора;
  • @Data: аннотация класса эквивалентна одновременному использованию @ToString, @EqualsAndHashCode, @Getter, @Setter и @RequiredArgsConstrutor, что очень полезно для классов POJO;
  • @Value: используется в классах, это неизменяемая форма @Data, которая эквивалентна добавлению окончательного объявления к свойству, предоставляя только методы получения, а не методы установки;
  • @Builder: используется в классах, конструкторах и методах, чтобы предоставить вам сложные API-интерфейсы строителя, позволяя вам вызывать Person.builder().name("xxx").city("xxx").build() следующим образом;
  • @SneakyThrows: автоматически генерировать проверенные исключения без явного использования инструкции throws в методе;
  • @Synchronized: используется в методе, метод объявлен синхронизированным и автоматически заблокированным, а объект блокировки является частным свойством.lock 或LOCK, а объект блокировки синхронизированного ключевого слова в Java таков, блокировка имеет побочные эффекты для этого или его собственного объекта класса, то есть вы не можете предотвратить блокировку этого или объекта класса неконтролируемым кодом, что может вызвать условия гонки или другие потоки Ошибка;
  • @Getter(lazy=true): может заменить классический шаблонный код Double Check Lock;
  • @Log: генерировать различные типы объектов журнала в соответствии с разными аннотациями, но все имена экземпляров являются журналами, и существует шесть дополнительных классов реализации.
    • @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);

Их конкретное использование заключается в следующем:

① значение использования

val sets = new HashSet<String>();  
// 相当于
final Set<String> sets = new HashSet<>();

② Ненулевое использование

public void notNullExample(@NonNull String string) {
    string.length();
}
// 相当于
public void notNullExample(String string) {
    if (string != null) {
        string.length();
    } else {
        throw new NullPointerException("null");
    }
}

③ Использование очистки

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();
            }
        }
    }
}

④ Использование геттера/сеттера

@Setter(AccessLevel.PUBLIC)
@Getter(AccessLevel.PROTECTED)
private int id;
private String shap;

⑤ Использование 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());
    }
}

⑥ Использование EqualsAndHashCode

@EqualsAndHashCode(exclude = {"id", "shape"}, callSuper = false)
public class LombokDemo {
    private int id;
    private String shap;
}

⑦ Использование 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");
        // 使用静态工厂方法
        LombokDemo.of(2, "Java");
        // 无参构造
        new LombokDemo();
        // 包含所有参数
        new LombokDemo(1, "Java", 2);
    }
}

⑧ Использование конструктора

@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();
    }
}

⑨ Использование SneakyThrows

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();
    }
}

⑩ Синхронное использование

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");
        }
    }
}

⑪ Использование 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;
    }
}
// 相当于
import 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;
    }
}

Принципиальный анализ

Мы знаем, что процесс компиляции Java можно условно разделить на три этапа:

  1. Разобрать и заполнить таблицу символов
  2. Обработка аннотаций
  3. Анализ и генерация байт-кода

Процесс компиляции показан на следующем рисунке:

编译流程.png
И Lombok реализован с использованием шага «обработки аннотаций». Lombok использует JSR 269: Pluggable Annotation Processing API (обработчик аннотаций во время компиляции), реализованный JDK 6, который является кодом аннотаций Lombok во время компиляции., который элегантно запрограммирован. путем преобразования его в обычный метод Java.

Это можно проверить в программе, например, этой статьей только начали пользоваться@DataРеализованный код:

image.png

После компиляции посмотрим на скомпилированный исходный код класса Person и обнаружим, что код выглядит так:

Person 生成的源码.png
Видно, что класс Person преобразуется транслятором аннотаций в обычный метод Java во время компиляции, добавляя методы Getter, Setter, equals, hashCode и другие.

Поток выполнения Lombok выглядит следующим образом:

lombok 执行流程.png
Видно, что на этапе компиляции, когда исходный код Java абстрагируется в синтаксическое дерево (AST), Lombok будет динамически модифицировать AST в соответствии со своим процессором аннотаций, добавлять новый код (узлы), и после всего этого выполняется, то анализ генерирует окончательный файл байт-кода (.class), как работает Lombok.

Рука Ломбок

Мы реализуем упрощенную версию Lombok для настройки метода Getter.Наши шаги реализации:

  1. Настройте интерфейс тега аннотации и внедрите собственный процессор аннотаций;
  2. Обработка AST (абстрактного синтаксического дерева) с помощью javac API tools.jar
  3. Скомпилируйте код с помощью пользовательского процессора аннотаций.

Таким образом можно реализовать упрощенную версию Ломбока.

1. Определите пользовательские аннотации и обработчики аннотаций

Сначала создайтеMyGetter.javaНастройте аннотацию, код выглядит следующим образом:

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

}

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

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; // 提供了待处理的抽象语法树
    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
                )
        );
    }
}

Пользовательские процессоры аннотаций являются для нас главным приоритетом для реализации упрощенной версии Lombok, нам нужно унаследоватьAbstractProcessorкласса, переписать его методы init() и process().В методе process() мы сначала опрашиваем все переменные, а затем добавляем к переменным соответствующие методы. Мы используем объекты и имена TreeMaker для обработки AST, как показано в приведенном выше коде.

Когда эти коды написаны, мы можем добавить новый класс Person, чтобы попробовать наш собственный@MyGetterфункция, код выглядит следующим образом:

@MyGetter
public class Person {
    private String name;
}

2. Скомпилируйте код с помощью специального процессора аннотаций.

После того, как все вышеперечисленные процессы выполнены, мы можем скомпилировать код для проверки эффекта. Во-первых, мы сначала входим в корневой каталог кода и выполняем следующие три команды.

Введенный корневой каталог выглядит следующим образом:

image.png
① Используйте tools.jar для компиляции пользовательских аннотаторов.

javac -cp $JAVA_HOME/lib/tools.jar MyGetter* -d .

Примечание: «.» в конце команды указывает на текущую папку.

② Используйте пользовательский аннотатор для компиляции класса Person.

javac -processor com.example.lombok.MyGetterProcessor Person.java

③ Просмотр исходного кода пользователя

javap -p Person.class

Исходные файлы следующие:

image.png

Вы можете видеть, что наш пользовательский метод getName() был успешно сгенерирован., здесь делается упрощенная версия Ломбока.

Ломбок плюсы и минусы

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

Недостаток 1: меньшая отлаживаемость

Ломбок будет автоматически генерировать для нас много кода, но эти коды генерируются во время компиляции, поэтому эти коды могут быть «потеряны» на этапах разработки и отладки, что приносит большие неудобства отладочному коду.

Недостаток 2: могут быть проблемы с совместимостью

Ломбок очень навязчив к коду, и сейчас версия JDK быстро обновляется, а версия выпускается раз в полгода, а ломбок принадлежит стороннему проекту и поддерживается командой open source, так что нет возможности обеспечить совместимость версий и скорость итерации, что может привести к несовместимости версий.

Недостаток 3: может столкнуть товарищей по команде

Особенно для новичков, которые приходят в группу, это может иметь большее влияние.Если этот человек раньше не пользовался ломбоком, то когда скидывает код, потому что не установлен ломбок-плагин, при компиляции проекта ошибка будет отображаться сообщение, такое как метод не найден. , что приведет к сбою компиляции проекта, что, в свою очередь, повлияет на сотрудничество между участниками Unity.

Недостаток 4: разрушает инкапсуляцию

Определение объектно-ориентированной инкапсуляции таково: через управление доступом внутренние данные скрыты, а внешние могут получать доступ и изменять внутренние данные только через ограниченный интерфейс, предоставляемый классом.

Другими словами, мы не должны использовать методы Lombok Getter/Setter для предоставления всех полей внешнему миру, потому что в некоторых случаях некоторые поля не могут быть изменены напрямую, например, количество товаров в корзине, которое напрямую влияет на сведения о покупках и общую стоимость, поэтому при изменении должен быть предоставлен единый метод, а не добавление методов доступа и модификации к каждому полю.

резюме

В этой статье мы представляем принцип использования и выполнения Lombok, который реализован JDK 6 JSR 269: подключаемый API обработки аннотаций (процессор аннотаций во время компиляции), который преобразует аннотации Lombok в обычные методы Java во время компиляции. упрощенная версия Lombok путем наследования класса AbstractProcessor и переопределения его методов init() и process(). Но в то же время у Lombok есть и некоторые недостатки в использовании, такие как: снижение отлаживаемости, возможная совместимость и другие проблемы, поэтому нам нужно выбирать, использовать ли Lombok в соответствии с нашими собственными бизнес-сценариями и реальными ситуациями, и каким должен быть Lombok. использовал.

И, наконец, напоминание, какой бы хорошей ни была технология, это не панацея, так же как и лучшая обувь должна быть подходящей для ваших ног!

Спасибо за прочтение, надеюсь, эта статья вдохновит вас. Если вы думаете, что это хорошо, поделитесь им с друзьями, которым это нужно, спасибо.

Ссылки и благодарности

nuggets.capable/post/684490…

Woohoo Назад cool.com/articles/ Есть 6…

Для получения более интересного контента, пожалуйста, обратите внимание на публичный аккаунт WeChat «Java Chinese Community».

Java中文社群公众号二维码