Lombok java что это
Перейти к содержимому

Lombok java что это

  • автор:

Lombok. Полное руководство

Здесь изложен необходимый минимум информации, которую нужно изучить, если хочешь приступить к использованию проекта Lombok. Рассмотрим, как интегрировать его в вашу IDE и использовать, чтобы сократить объем шаблонного кода.

Java – отличный язык, только многословный. Возможно, вам придется писать много кода, чтобы достичь даже самых простых целей. Кроме того, в Java определенно присутствует повторяющийся код, например, геттеры и сеттеры. Поэтому у вас получаются огромные объемы повторяющегося и необязательного кода. Мало того, что такой код не добавляет ничего нового в бизнес-логику вашего приложения, так и писать его долго и скучно. Именно поэтому следует переходить к использованию библиотек и инструментов – они помогают повысить продуктивность и избежать этой рутины. Именно здесь в игру вступает Lombok!

Это библиотека Java, в которой предоставляется ряд аннотаций, направленных на исключение именно того кода Java, о котором известно, что он часто становится повторяющимся и/или шаблонным. Проект Lombok включается прямо в процесс сборки. Затем Lombok автоматически сгенерирует для Java байт-код, который вставляет в файлы .class, необходимые для реализации желаемого поведения, в зависимости от используемых вами аннотаций. Следовательно, каждая аннотация, предлагаемая в проекте Lombok, позволяет частично обойтись без написания методов и логики, без которых вы хотели бы обойтись. Речь о конструкторах, равенствах и функциях хеш-кода. Так вы сможете сэкономить массу времени и сосредоточиться на бизнес-логике вашего проекта. Кроме того, вы сможете держать базу кода сравнительно компактной, чистой, удобной для чтения и поддержки.

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

Предпосылки

Вот что вам нужно установить, чтобы воспроизвести все примеры, приведенные ниже:

  • Java >= 1.8
  • Gradle >= 4.x or Maven 3.6.x
  • Project Lombok >= 1.18.20

Что такое Lombok

Проект Lombok (далее – просто Lombok) – это основанная на аннотациях библиотека Java, позволяющая сократить шаблонный код. В Lombok предлагаются различные аннотации, цель которых – заменить ненужный повторяющийся код, писать который утомительно. Например, Lombok избавит вас от написания безаргументных конструкторов, методов toString(), equals() и hashCode(), для этого нужно просто добавить несколько аннотаций. Плюс, как будет показано ниже, эта библиотека подключается прямо к вашей IDE, поэтому работа воспринимается так, словно вы написали весь шаблонный код сами.

Установить Lombok легко – добавьте lombok к вашим зависимостям. Если вы пользуетесь Gradle, допишите следующие две строки с зависимостями в ваш файл build . gradle :

compileOnly 'org.projectlombok:lombok:1.18.20' annotationProcessor 'org.projectlombok:lombok:1.18.20'

Тогда как если работаете с Maven, добавьте следующую зависимость в ваш файл pom . xml :

 org.projectlombok lombok 1.18.20 provided 

Кроме того, добавьте зависимость Lombok в конфигурационный раздел maven-compiler-plugin следующим образом:

   org.apache.maven.plugins maven-compiler-plugin 3.5.1 11 11   org.projectlombok lombok 1.18.20     

Теперь вы настроили все необходимое, чтобы приступить к работе с Lombok.

Наиболее распространенные аннотации Lombok

Ниже перечислены самые распространенные и важные аннотации Lombok. Каждая из них будет объяснена и рассмотрена на практике в сравнении с эквивалентным «переводом» на обычный язык Java. Чтобы посмотреть примеры и получить дополнительную поддержку, можно щелкнуть по любой аннотации и посетить посвященную ей страницу на сайте с официальной документацией по Lombok.

@Getter, @Setter

Когда после аннотировано @Getter и/или @Setter , Lombok автоматически сгенерирует заданный по умолчанию геттер и/или сеттер, соответственно. Заданная по умолчанию реализация геттеров просто берет на себя возврат аннотированного поля. Аналогично, заданная по умолчанию реализация сеттеров принимает один параметр того же типа, что и аннотированное поле, и просто устанавливает его в полученное значение. Когда поле под названием value аннотируется одновременно @Getter и @Setter , Lombok определит метод getValue() (или isValue(), если это поле типа boolean ) и setValue(). Сгенерированный метод, действующий как геттер и сеттер, будет публичным (public), если не указан конкретный уровень доступа ( AccessLevel ). Допустимые значения AccessLevel – это PUBLIC, PROTECTED, PACKAGE и PRIVATE. Обратите внимание: можно аннотировать и целый класс. В таком случае данная логика будет применяться к каждому из его полей.

С Lombok

@Getter @Setter public class Author

Просто Java

public class User < private int id; private String name; private String surname; public int getId() < return id; >public void setId(int id) < this.id = id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public String getSurname() < return surname; >protected void setSurname(String surname) < this.surname = surname; >>
@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

Когда класс аннотирован @NoArgsConstructor , Lombok возьмет на себя автоматическую генерацию непараметризованного конструктора. Аналогично, когда класс аннотирован @AllArgsConstructor , будет сгенерирован конструктор с одним параметром для каждого поля вашего класса. Далее, @RequiredArgsConstructor позволит получить конструктор с параметром для каждого поля, но эти параметры потребуют специальной обработки. В частности, здесь мы будем работать с неинициализированными полями final , а также с любыми полями, помеченными как @NonNull , которые не инициализируются при объявлении. Пожалуйста, не забудьте, что статические поля будут этими аннотациями игнорироваться.

С Lombok

@NoArgsConstructor @AllArgsConstructor @RequiredArgsConstructor public class Author

Просто Java

public class Author < private int id; private String name; private String surname; private final String birthPlace; // @NoArgsConstructor public Author() <>// @AllArgsConstructor public Author(int id, String name, String surname, String birthPlace) < this.id = id this.name = name this.surname = surname this.birthPlace = birthPlace >// @RequiredArgsConstructor public Author(String birthPlace) < this.birthPlace = birthPlace >>
@ToString

Когда класс аннотирован @ToString , Lombok позаботится о том, чтобы сгенерировать нужную реализацию метода toString(). По умолчанию будет возвращаться строка, в которой содержится имя класса, за которой следуют значения всех полей, разделенные запятыми. Устанавливая параметр includeFieldNames в значение true, добьемся того, что имя каждого поля будет ставиться перед его значением. По умолчанию при генерировании метода toString() будут учитываться все нестатические поля. Если мы хотим, чтобы Lombok проигнорировал поле, нужно аннотировать его @ToString . Exclude . Как вариант, можете сами указать, какие поля на ваше усмотрение должны аннотироваться, для этого воспользуйтесь @ ToString( onlyExplicitlyIncluded = true). Затем пометьте при помощи @ToString . Include каждое поле, которое хотите включить.

С Lombok

@ToString(includeFieldNames=true) public class Author

Просто Java

public class Author < private int id; private String name; private String surname; @Override public String toString() < return "Author(id=" + this.id + ", name=" + this.name + ", surnname=" + this.surname + ")"; >>
@EqualsAndHashCode

Если аннотировать класс при помощи @EqualsAndHashCode , то Lombok автоматически реализует за вас методы equals() и hashCode(). По умолчанию будут учитываться все нестатические непереходные поля. Можно повлиять на то, какие поля будут использоваться, аннотировав их @EqualsAndHashCode . Include или @EqualsAndHashCode . Exclude . Как вариант, можете аннотировать ваш класс при помощи @ EqualsAndHashCode( onlyExplicitlyIncluded = true), а затем в точности указать, какие поля или методы вы хотите использовать, пометив их @EqualsAndHashCode . Include . Обратите внимание: Lombok сгенерирует методы equals() и hashCode(), не нарушая контракта между ними. Перейдите по ссылкам о двух этих методах на сайт с официальной документацией Java и почитайте подробнее о тех контрактах, которым должны удовлетворять реализации equals() и hashCode().

С Lombok

@Getter @Setter @EqualsAndHashCode public class Author

Просто Java

public class Author < // геттеры и сеттеры . @Override public int hashCode() < final int PRIME = 31; int result = 1; result = prime * result + id; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((surname == null) ? 0 : surname.hashCode()); return result; >@Override public boolean equals(Object o) < if (o == this) return true; if (!(o instanceof Author)) return false; Author other = (Author) o; if (!other.canEqual((Object)this)) return false; if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false; return true; >> 
@NonNull

Можно аннотировать при помощи @NonNull компонент записи, параметр, метод или целый конструктор, В таком случае Lombok сгенерирует для вас команды для проверки на нуль.

С Lombok

public class Author < private int id; private String name; private String surname; public Author( @NonNull int id, @NonNull String name, String surname ) < this.id = id; this.name = name; this.surname = surname; >>

Просто Java

public class Author < private int id; private String name; private String surname; public Author( int id, String name, String surname ) < if (id == null) < throw new NullPointerException("id is marked @NonNull but is null"); >this.id = id; if (name == null) < throw new NullPointerException("name is marked @NonNull but is null"); >this.name = name; this.surname = surname; > >
@Data

@Data – это сокращенная аннотация, сочетающая возможности @ToString , @EqualsAndHashCode , @Getter @Setter и @RequiredArgsConstructor . Так что @Data генерирует весь шаблонный код, вовлеченный в работу с объектами POJO (Plain Old Java Objects). Это, в частности, дает нам геттеры для всех полей, сеттеры для всех нефинальных полей, правильные реализации toString , equals и hashCode , охватывающие все поля класса, а также конструктор для всех финальных полей.

С Lombok

@Data public class Author

Просто Java

public class Author < private final int id; private String name; private String surname; public Author(int id) < this.id = id; >public int getId() < return id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public String getSurname() < return surname; >public void setSurname(String surname) < this.surname = surname; >@Override public int hashCode() < final int PRIME = 31; int result = 1; result = prime * result + getId(); result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); result = prime * result + ((getSurname() == null) ? 0 : getSurname().hashCode()); return result; >@Override public boolean equals(Object o) < if (o == this) return true; if (!(o instanceof Author)) return false; Author other = (Author) o; if (!other.canEqual((Object)this)) return false; if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false; return true; >>
@Value

@Value – это неизменяемый вариант @Data . С этой аннотацией Lombok по умолчанию делает все поля private и final . Кроме того, сеттеры не генерируются, а класс как таковой помечается final . Таким образом, от этого класса нельзя наследовать. Точно как и в случае с @Data , создаются реализации toString(), equals() и hashCode().

С Lombok

@Data public class Author

Просто Java

public final class Author < private final int id; private final String name; private final String surname; public Author(int id, String name, String surname) < this.id = id this.name = name this.surname = surname >public int getId() < return id; >public String getName() < return name; >public String getSurname() < return surname; >@Override public int hashCode() < final int PRIME = 31; int result = 1; result = prime * result + getId(); result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); result = prime * result + ((getSurname() == null) ? 0 : getSurname().hashCode()); return result; >@Override public boolean equals(Object o) < if (o == this) return true; if (!(o instanceof Author)) return false; Author other = (Author) o; if (!other.canEqual((Object)this)) return false; if (this.getId() == null ? other.getId() != null : !this.getId().equals(other.getId())) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; if (this.getSurname() == null ? other.getSurname() != null : !this.getSurname().equals(other.getSurname())) return false; return true; >>

Продвинутые аннотации Lombok

Ниже рассмотрены самые сложные аннотации Lombok. Подробнее о каждой из них рассказано на собственной странице в официальной документации Lombok.

@Cleanup

Аннотация @Cleanup позволяет гарантировать, что заданный ресурс будет автоматически очищаться перед тем, как покинет актуальную область видимости. По умолчанию предполагается, что метод очистки аннотированного ресурса будет close(), но вы можете сами указать имя того метода, который, по вашему желанию, будет вызываться вместо него. Обратите внимание: эта аннотация активно использует команды try-with-resources.

С Lombok

public class CleanupDemo < public static void main(String[] args) throws IOException < @Cleanup InputStream input = new FileInputStream(args[0]); @Cleanup OutputStream output = new FileOutputStream(args[1]); byte[] b = new byte[10000]; while (true) < int r = input.read(b); if (r == -1) break; output.write(b, 0, r); >> >

Просто Java

public class CleanupDemo < public static void main(String[] args) throws IOException < try (OutputStream output = new FileOutputStream(args[1])) < try (InputStream input = new FileInputStream(args[0])) < byte[] b = new byte[10000]; while (true) < int r = input.read(b); if (r == -1) break; output.write(b, 0, r); >> > > >
@Synchronized

Аннотация @Synchronized действует примерно так же, как и ключевое слово synchronized , но защелкивается на других объектах. Ключевое слово защелкивается на this, а эта аннотация – на особом приватном поле под названием $lock . Если это поле не существует, то Lombok создаст его. Такое поведение задано по умолчанию, но вы можете и сами указать те объекты, на которых будет происходить защелкивание. При работе с методами static аннотация будет защелкиваться на статическом поле $ LOCK. Учтите, что эта аннотация, как и ключевое слово synchronized , может использоваться только со статическими методами и методами экземпляра.

С Lombok

public class SynchronizedDemo < private final Object objectToLock = new Object(); @Synchronized public static void sayHello() < System.out.println("Hello!"); >@Synchronized public int getOne() < return 1; >@Synchronized("objectToLock") public void printObject() < System.out.println(objectToLock); >>

Просто Java

public class SynchronizedDemo < private static final Object $LOCK = new Object[0]; private final Object $lock = new Object[0]; private final Object readLock = new Object(); public static void sayHello() < synchronized($LOCK) < System.out.println("Hello"); >> public int getOne() < synchronized($lock) < return 1; >> public void printObject() < synchronized(readLock) < System.out.println(objectToLock); >> >
@SneakyThrows

Аннотация @SneakyThrows позволяет бесшумно выбрасывать проверяемые исключения, не объявляя их явно в условии throws вашего метода, как принято делать. Итак, эта аннотация позволяет вам полностью избавиться от (как правило, необходимых в таких случаях) блоков try- catch , поскольку тихо обрабатывает все проверяемые исключения. В противном случае она запутала бы компилятор. Фактически, на уровне файлов классов JVM (виртуальной машины Java), все исключения могут выбрасываться безотносительно условия throws , присутствующего в ваших методах, поэтому-то данный механизм и работает. Вот почему стоит прочитать эту страницу из официальной документации Lombok, чтобы подробнее разобраться в этой аннотации и понять, как ею пользоваться.

@Builder

Возможно, вам потребуется разработать объект-строитель, который позволял бы вам создавать объекты, следуя пошаговой процедуре, например, Author .builder().id(«1»).name(«Maria»).surname(«Williams»).build();. Это особенно полезно, когда имеешь дело с большими классами, в каждом из которых по несколько полей. Вместо использования конструктора со многими полями, можно попробовать этот подход, более удобочитаемый. При помощи аннотации @Builder вы поручаете Lombok генерировать строители за вас. Аннотируя класс при помощи @Builder , Lombok выдает класс, реализующий вышеупомянутый паттерн «строитель». Например, аннотируя ею класс Author , получим автоматически сгенерированный класс AuthorBuilder . Поскольку поведение вашего строителя может быть сложным или сильно подогнанным под задачу, Lombok предлагает много параметров, при помощи которых можно достичь желаемого результата. Все они находятся здесь.

@Log

Большинство средств логирования требуют закладывать экземпляр логгера для каждого класса, в котором вы хотите вести лог. Разумеется, при этом возникает шаблонный код. Lombok, аннотируя класс при помощи @Log , автоматически добавляет статическое финальное поле log , инициализируемое вашей библиотекой логирования как требуемое. Вот почему Lombok дает разработчикам по аннотации на каждый из наиболее популярных фреймворков логирования. Их полный список находится здесь.

Плагин Lombok

К наиболее популярным и широко используемым IDE прилагается официальный плагин Lombok, предназначенный именно для упрощения работы с Lombok. В частности, он предлагает шорткаты для наиболее распространенных аннотаций Lombok. Кроме того, подсказывает, какие аннотации вам могут потребоваться или заинтересовать вас, в зависимости от того, где вы щелкаете мышью. На момент написания оригинала официально поддерживались IntelliJ IDEA, Eclipse, Spring Tool Suite, (Red Hat) JBoss Developer Studio, MyEclipse, Microsoft Visual Studio Code и Netbeans. Полный список поддерживаемых IDE и советы по установке даны на официальном сайте Lombok. .

Рискованно ли работать с Lombok?

Возможно, вас беспокоит, а что будет, если вы расставите аннотации Lombok по всей вашей базе кода. В самом деле, а что будет, если после этого вы решите избежать работы с Lombok? Возможно, в таком случае работа забуксует. Но на практике это не слишком большая проблема, поскольку в Lombok есть инструмент delombok. Как указано в официальной документации (где, правда, не покрыты все возможные IDE и случаи), этот инструмент действительно упрощает процесс разломбочивания вашего кода. Фактически, он обеспечивает автоматическую генерацию исходного кода Java, содержащего в байт-коде ровно те же возможности, которые внедрялись бы при помощи Lombok. Таким образом, ваша база кода, аннотированная Lombok, просто заменяется стандартной базой кода на Java, без Lombok. После этого весь ваш проект больше вообще не будет зависеть от Lombok. Соответственно, работа с Lombok не представляет никаких рисков для развития вашего проекта в будущем.

Заключение

В этой статье было рассмотрено, как использовать проект Lombok — библиотеку Java, которая автоматически подключается к вашему редактору и инструментам сборки, чтобы вам не приходилось писать скучного шаблонного повторяющегося кода, который так характерен для языка Java. Как показано в статье, Lombok помогает разработчику легко повысить производительность труда и не тратить времени на утомительную рутину. Научившись пользоваться самыми важными аннотациями, можно обойтись без написания тысяч строк кода, который не несет никакой реальной пользы для бизнес-логики вашего проекта. Кроме того, в любой момент можно легко отвязать ваш проект от использования проекта Lombok.

  • Блог компании Издательский дом «Питер»
  • Программирование
  • Java
  • ООП

Lombok: хорошее и плохое применение

Lombok — по-настоящему хороший инструмент, который помогает писать меньше кода и больше сосредотачиваться на реальной работе. Суть в том, как вы используете Lombok в проекте: есть как хорошие, так и плохие способы использования.

Что такое Lombock

Lombok — это библиотека Java, которая сокращает объем стандартного кода в классах. Вы добавляете аннотации, и Lombok генерирует код во время компиляции. Например, если аннотировать класс с помощью @Getter , то Lombok сгенерирует методы-геттеры для всех переменных в классе.

Как работает Lombock

Начнем с того, как происходит процесс компиляции. В нем есть три основных этапа:

  • разбор и ввод;
  • обработка аннотаций;
  • анализ и генерация.

На этапе разбора и ввода исходные файлы считываются в синтаксическое дерево (AST), и каждое дерево передается на ввод. Все обработчики аннотаций вызываются на этапе обработки аннотаций. Если обработчики аннотаций генерируют новые исходные файлы или файлы классов, процесс компиляции переходит к первым шагам и все запускается заново. Так повторяется до тех пор, пока не появятся новые исходные файлы или файлы классов, созданные обработчиками аннотаций. AST преобразуется в файл класса на этапе анализа и генерации.

Вся магия Lombock происходит на этапе обработки аннотаций. Задача процессора аннотаций заключается в создании новых исходных файлов или классов, но вместо этого Lombock изменяет существующие. Нигде в спецификации компилятора Java не утверждается, могут или не могут обработчики аннотаций изменять существующий исходный файл. Lombock использует эту лазейку в своих интересах. Мы можем изменить класс, который будет сгенерирован из исходного кода, изменив AST. Вот как работает Lombock.

Преимущества Lombock

Lombock упрощает написание кода, позволяя сосредоточиться только на том, что нужно реализовать, а именно — переводе бизнес-требований в код. Написание геттеров, сеттеров, конструкторов, методов equals или реализация шаблона конструктора — это не то, на что мы должны тратить время.

Lombock позаботится об этом, просто нужно добавить подходящую аннотацию, и он сгенерирует код. Например, если нужно создать геттеры и сеттеры, достаточно аннотировать класс с помощью @Getter и @Setter .

Если у вас сложный объект со многими атрибутами, вы можете аннотировать класс с помощью @Builder , и Lombok реализует шаблон builder для этого класса.

Вот скомпилированный код вышеупомянутого класса:

Есть также множество дополнительных аннотаций, которыми можно воспользоваться в проектах, чтобы писать меньше кода, например @Cleanup , @AllArgsConstructor , @Data и @Value . Меньше кода означает меньше поводов к беспокойству. Однако Lombock добавляет код в проект во время компиляции — и иногда его оказывается больше, чем если бы вы писали самостоятельно.

Плохое применение Lombock

Иногда разработчики забывают, что Lombok генерирует код, потому что его не видно в исходном коде.

Прежде чем что-либо реализовать, многие добавляют аннотации Lombock в классы. Например, аннотируют классы DTO, используя аннотацию @Data для создания геттеров и сеттеров. Но они забывают (или не знают), что Lombock генерирует дополнительные методы, такие как equals , hashCode , toString и canEqual . Иногда они просто не нужны, а в репозитории не должно быть ничего, что мы не используем.

У любого кода должна быть причина для существования, а если ее нет, его необходимо удалить. Ответственность за применение правильных аннотаций Lombock лежит на разработчиках. Наиболее очевидный способ исправить это — добавить аннотации @Getter и @Setter вместо аннотации @Data .

Некоторые разработчики пользуются Lombock, чтобы скрывать нарушения Sonar, а не исправлять их (намеренно или непреднамеренно). Возьмите в качестве примера приведенный ниже код:

У этого конструктора — девять параметров, и это нарушение Sonar.

Класс может делать слишком много. Это должно быть исправлено путем внесения необходимых изменений в код. Разработчики берут @Requiredargsconstructor для создания конструктора с требуемыми параметрами. Но когда они добавляют эту аннотацию, нарушение Sonar не подсвечивается.

Так не стоит справлять нарушение.

Кроме того, лучше не использовать какую-либо аннотацию Lombock, если она изменяет код. Если вы берете @Data , @Getter , @Setter или @AllArgsConstructor , то добавляете новый код в существующий без изменения написанного. Но если взять аннотацию @Utilityclass , она изменит код. К примеру, вот утилитный класс ниже:

Аннотация @Utilityclass преобразует существующий класс в утилитный, делая его окончательным и создавая приватный конструктор по умолчанию. Она также изменяет существующий метод и переменные, делая их статическими. Если вы проверите вышеуказанный класс, то не обнаружите никаких проблем. Нарушений Sonar также нет. Но при проверке скомпилированного кода вы увидите реальные проблемы:

Переменная value является общедоступной статической конечной переменной, поэтому она должна соответствовать соглашению об именовании констант. Но когда мы проверяем исходный код, то видим, что это переменная экземпляра. Даже IDE не смогла идентифицировать ее как константу. Можно утверждать, что если знать, как работает аннотация @Utilityclass , то понятно: переменная value — константа. Такого рода модификации плохо влияют на читаемость. В первую очередь код должен быть удобочитаемым для человека. И на человеческий взгляд, это переменная экземпляра.

Еще одно, что стоит иметь в виду, — Lombock использует лазейку в спецификации компилятора Java. Если Java исправит это, мы, возможно, не сможем дальше пользоваться Lombock.

Заключение

Lombock — хороший и полезный инструмент. Но стоит использовать его разумно. Если не знать этого, то реальные преимущества Lombock останутся для нас недоступны.

  • Java-Lombok: нужны ли геттеры и сеттеры?
  • String, StringBuilder и StringBuffer: понимаете ли вы разницу?
  • Основы многопоточности

тут блог

тут блог

Общественные обязательства интроверта.
Сообщения на ИТ тематику, но не обязательно.

О Lombok

На одном проекте, который, ой, уже как почти год идёт, мы не уломали технарей от заказчика на Kotlin. На наши попытки рассказать, чем Kotlin хорош, нам говорили: ну вот же, берёте Lombok, и будет так же лаконично. Так мы узнали, что такое Lombok, Project Lombok.

На самом деле, Lombok — это остров в Индонезии. Рядом с островом Java. Там ещё рядом есть город Jakarta. Ну вы поняли, да?

Индонезия

Lombok — это библиотека кодогенерации. В Maven достаточно просто добавить зависимость, и вот у вас уже работает плугин, который творит магию. В Gradle Lombok нужно подключить либо как плугин, либо как специальную annotationProcessor зависимость.

Что за код генерирует Lombok? Ну вот простенький пример.

import lombok.Data; @Data public class Location  private double latitude; private double longitude; > 

Аннотация @Data добавляет нам геттеры, сеттеры, toString() , equals() , hashCode() и конструктор для «обязательных» аргументов. В данном случае и latitude , и longitude являются double , у которых есть дефолтное значение 0.0 , и они не объявлены final . С точки зрения Lombok это «необязательные» аргументы, поэтому создался конструктор без аргументов.

Location location = new Location(); location.setLatitude(1.23); location.setLongitude(4.56); location.getLatitude(); location.getLongitude(); location.toString(); // Location(latitude=1.23, longitude=4.56) location.hashCode(); // -819911996 Location anotherLocation = new Location(); location.equals(anotherLocation); // false 

Аналогичный data class на Kotlin будет выглядеть так:

data class Location( var latitude: Double = 0.0, var longitude: Double = 0.0 ) 

Чтобы можно было изменять свойства класса, они объявлены как var . Чтобы можно было вызывать конструктор без аргументов, добавлены значения по умолчанию. Не вполне котлиновый подход.

val location = Location() location.latitude = 1.23 location.longitude = 4.56 location.latitude location.longitude println(location) // Location(latitude=1.23, longitude=4.56) println(location.hashCode()) // 1091536083 val anotherLocation = Location() location == anotherLocation // false 

Но мы ведь все за иммутабельные данные? Поэтому отходим от JavaBean и используем аннотацию @Value .

import lombok.Value; @Value public class Location  double latitude; double longitude; > 

@Value делает все поля класса private final , поэтому явно писать private не нужно. Нам сгенерируют геттеры, toString() , equals() и hashCode() . Сеттеров не будет, иммутабельный класс же. И будет конструктор со всеми аргументами, который создаёт наш объект раз и навсегда.

Location location = new Location(1.23, 4.56); location.getLatitude(); location.getLongitude(); location.toString(); // Location(latitude=1.23, longitude=4.56) location.hashCode(); // -819911996 Location anotherLocation = new Location(1.23, 4.56); location.equals(anotherLocation); // true 

Если, после Котлина, вам не нравится слово new , можно попробовать добавить @AllArgsConstructor(staticName=»of») . @Value уже неявно добавляет эту аннотацию, только без этого аргумента. Тогда можно будет так:

Location location = Location.of(1.23, 4.56); 

Так вот, навешивая всё больше и больше аннотаций, и меняется поведение Lombok.

Иммутабельное значение уже похоже на поведение типичных дата классов Котлина.

data class Location( val latitude: Double, val longitude: Double ) 

Конструктор без new . Возможность только читать свойства.

val location = Location(1.23, 4.56) location.latitude location.longitude println(location) // Location(latitude=1.23, longitude=4.56) println(location.hashCode()) // 1091536083 val anotherLocation = Location(1.23, 4.56) location == anotherLocation // true 

А что если не все свойства обязательны? Как конструировать такие объекты?

В Java нам либо придётся создавать больше конструкторов, в которых можно запутаться, особенно, если типы полей одинаковые. Либо создавать больше фабричных методов. Либо воспользоваться паттерном Builder.

В Lombok есть аннотация @Builder .

@Value @Builder public class Location  double latitude; double longitude; Double radius; > 

Теперь объекты можно конструировать через билдер. При этом не обязательно указывать все свойства.

Location location = Location.builder() .latitude(1.23) .longitude(4.56) .build(); location.toString(); // Location(latitude=1.23, longitude=4.56, radius=null) Location radiusLocation = Location.builder() .latitude(1.23) .longitude(4.56) .radius(5.0) .build(); radiusLocation.toString(); // Location(latitude=1.23, longitude=4.56, radius=5.0) 

А в Kotlin билдеры просто не нужны. Потому что там есть именованные аргументы и аргументы по умолчанию.

data class Location( val latitude: Double, val longitude: Double, val radius: Double? = null ) 

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

val location = Location( latitude = 1.23, longitude = 4.56 ) println(location) // Location(latitude=1.23, longitude=4.56, radius=null) val radiusLocation = Location( latitude = 1.23, longitude = 4.56, radius = 5.0 ) println(radiusLocation) // Location(latitude=1.23, longitude=4.56, radius=5.0) 

А что, если нам нужно «поменять» иммутабельный класс? Ну чтобы не указывать заново 100500 свойств, а лишь поменять парочку при копировании.

В Lombok можно получить билдер из объекта. Нужно добавить toBuilder .

@Value @Builder(toBuilder = true) public class Location  double latitude; double longitude; Double radius; > 

Теперь у объекта есть метод toBuilder() .

Location location = Location.builder() .latitude(1.23) .longitude(4.56) .build(); location.toString(); // Location(latitude=1.23, longitude=4.56, radius=null) Location radiusLocation = location.toBuilder() .radius(5.0) .build(); radiusLocation.toString(); // Location(latitude=1.23, longitude=4.56, radius=5.0) 

В Kotlin ничего добавлять не нужно. У дата классов уже есть метод copy() с теми же аргументами, что у конструктора, только всеми необязательными.

val location = Location( latitude = 1.23, longitude = 4.56 ) println(location) // Location(latitude=1.23, longitude=4.56, radius=null) val radiusLocation = location.copy( radius = 5.0 ) println(radiusLocation) // Location(latitude=1.23, longitude=4.56, radius=5.0) 

Билдеры, это, конечно, хорошо. Если у вас Java, а не Kotlin. Но @Value класс с @Builder — это не JavaBean. От этого возникают проблемы с десериализацией.

С сериализацией проблем нет. У нас есть геттеры. А значит, сериализатор может узнать имена и значения свойств.

А вот для десериализации нам нужно создать объект по сериализованному описанию. Конструктор у нас без аргументов. Сеттеров нет. Где-то там есть билдер, но про него десериализатору ещё нужно сообщить.

Полностью обвешенный аннотациями для Jackson класс выглядит так:

@Value @Builder(builderClassName = "Builder", toBuilder = true) @JsonDeserialize(builder = Location.Builder.class) public class Location  double latitude; double longitude; @JsonPOJOBuilder(withPrefix = "") public static final class Builder  > > 

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

ObjectMapper mapper = new ObjectMapper(); Location location = Location.builder() .latitude(1.23).longitude(4.56) .build(); String json = mapper.writeValueAsString(location); // Location fromJson = mapper.readValue(json, Location.class); location.equals(fromJson); // true 

Либо можно попросить Lombok сгенерировать публичный конструктор с аннотацией @ConstructorProperties . Эта аннотация сохраняет имена параметров конструктора. И Джексон может воспользоваться этим конструктором, чтобы создать объект.

Нужно снова добавить @AllArgsConstructor :

@Value @Builder @AllArgsConstructor public class Location  double latitude; double longitude; > 

Но также создать файл lombok.config в корне проекта. С таким содержимым:

lombok.addLombokGeneratedAnnotation = true lombok.anyConstructor.addConstructorProperties = true 

Расширение jackson-lombok , кстати, устарело.

Сложно. Более одного способа выстрелить себе в ногу. Нет никакой гарантии, что очередные правки аннотаций ничего не сломают.

Котлиновые дата классы тоже Джексону из коробки не по зубам. Но есть стабильный официальный поддерживаемый jackson-module-kotlin . Он работает.

import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.registerKotlinModule //. val mapper = ObjectMapper().registerKotlinModule() val location = Location( latitude = 1.23, longitude = 4.56 ) val json = mapper.writeValueAsString(location) // val fromJson: Location = mapper.readValue(json) println(location == fromJson) // true 

registerKotlinModule() и readValue() — это extension функции. Ещё одна приятная фича Котлина. А readValue() ещё и inline reified функция, благодаря чему можно не указывать тип, который мы пытаемся прочесть из JSON. Он будет выведен из типа переменной, которой здесь присваивается значение.

У Lombok есть проблемы не только с Jackson. Это — кодогенерация. И даже не столько кодогенерация, потому что нового кода-то и не создаётся, сколько вмешательство в процесс компиляции. Модификация AST, судя по тому, что гуглится.

А кодогенерация в Java так и не стала естественным этапом сборки, как, например, в Go. Раз в коде нет ни геттеров, ни сеттеров, ни билдеров, ни прочих нагенерированных методов, ни IDE, ни прочие инструменты их не увидят.

Без соответствующего плугина, работать с Lombok в IDE практически невозможно. А к IDEA плугин поддерживается не авторами Lombok, не JetBrains, а неким частным лицом с русской фамилией, проживающим в Германии. И за этот год этот плугин уже несколько раз отваливался, при обновлении IDEA. Причём в последний раз это, вроде как, было по вине JetBrains.

И не только IDE плохо. Всякие инструменты покрытия кода тестами, статистические анализаторы и прочие тоже спотыкаются о Lombok. Поэтому Lombok приходится разломбочивать. То есть генерировать тот самый отсутствующий код, который (не) создаёт Lombok.

С Kotlin таких проблем нет. Это отдельный язык со своим отдельным компилятором из исходного кода в JVM байткод. С полноценными плугинами для Maven, Gradle и IDE.

Что ещё мы используем от Lombok? Ну, например, аннотацию @Slf4j .

@Slf4j public class Main  public static void main(final String[] args)  log.info("Hello, World!"); > > 

Lombok добавляет нам в класс статическое свойство log .

В Kotlin можно добавить синтаксического сахара с помощью extension функций. Но оставить явное создание логера.

fun Any.logger(): Logger = LoggerFactory.getLogger(this.javaClass) class Main  private val log = logger() fun main()  log.info("Hello, World!") > > 

Lombok не нужен, если есть Kotlin.

Lombok очень странно встраивается в работу компилятора. Он требует сильно больших танцев с бубнами, чтобы все сопутствующие инструменты заработали. Kotlin надёжнее, стабильнее и удобнее.

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

Lombok как способ трансформировать test automation project

Решил написать заметку о применении Lombok для построения автоматизации тестирования.

Lombok — это библиотека, с помощью которой вы можете сократить количество шаблонного кода, который нужно писать на Java.

Чем Lombok может помочь автоматизатору?

Применить ломбок можно разными способами — от уменьшения кода для простых Pojo, до использования Extension Methods.

Каноническая Java не поддерживает extension methods, хотя в языках Groovy, Kotlin и C# они есть. Можно долго дискутировать на предмет того, нужны они или нет, но после того, как я попробовал их применять в Groovy, в Джаве мне их не хватало.

Что такое extension method?

Это возможность добавлять методы в уже существующие классы.

Скажем, у нас есть класс WebElement, у которого есть свой API. Вот там нету метода

shouldHave(String text) , а нам бы очень хотелось иметь такую штуку.

С помощью Lombok мы можем такой метод легко добавить:

public class WebElementExt < public static void shouldHave(WebElement el, String text)< assertEquals(el.getText(),text) >>

Затем используем в наших тестах:

@ExtensionMethod(WebElementExt.class) class Test < @Test void testWithExt()< Webdriver driver = new ChromeDriver(); driver.findElement(By.css(".header")).shouldHave("MainPage") >>

Легко и просто. Это лучше, чем писать декоратор, который будет отличаться по типу класса.

Если брать более сложный пример, то мы можем написать экстеншн и для самого драйвера:

public class DriverExtension < public static void goTo(WebDriver driver, String url) < driver.get(url); >public static T open(WebDriver driver, Class tClass) < try < BasePage page = tClass.getDeclaredConstructor(WebDriver.class).newInstance(driver); driver.get(page.getUrl()); return (T) page; >catch (InstantiationException e) < e.printStackTrace(); >catch (IllegalAccessException e) < e.printStackTrace(); >catch (NoSuchMethodException e) < e.printStackTrace(); >catch (InvocationTargetException e) < e.printStackTrace(); >return null; > public static UIElement $(WebDriver driver, String locator) < return new UIElement(new Locator(By.cssSelector(locator), driver)); >public static UIElement $(WebDriver driver, By locator) < return new UIElement(new Locator(locator, driver)); >>
abstract class BasePage < protected WebDriver browser; public BasePage(WebDriver driver)< this.browser = driver; >public abstract String getUrl(); > @ExtensionMethod(DriverExtension.class) public class MainPage extends BasePage < @Getter private String url = "http://automation-remarks.com/workshop"; @Getter private UIElement mainLink; public MainPage(WebDriver driver) < super(driver); this.mainLink = driver.$("#nav >div.nav-panel > a.main_link > span"); > >
public class UIElement < private Locator locator; public UIElement(Locator locator) < this.locator = locator; >public WebElement element() < return locator.find(); >public void should(String text) < boolean res = element().getText().equals(text); assert res; >public void click() < element().click(); >>

И наконец мы можем использовать все это в наших тестах:

class Advanced < @Test void test()< val browser = Browser.chrome(); MainPage mp = browser.open(MainPage.class); mp.getMainLink().click(); >>

Вы можете применять такой подход в своем существующем фреймворке практически безболезненно. Правда, есть одно НО — Lombok содержит определенный уровень магии, которую вы не будете понимать. Да и плагин для Intelij IDEA пока что не полностью поддерживает все фишки Lombok. Но, несмотря на это, я рекомендую обратить внимание на эту библиотеку и попробовать применить ее у себя на проекте.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *