Lombok - это один из инструментов, который я всегда использую в своих проектах. Я не мог представить себя программирующим Java без него в наши дни. Я действительно надеюсь, что вы найдете свою силу, читая эту статью!
1. Избегайте повторяющихся кодов
Java - отличный язык, но иногда он становится слишком многословным для вещей, которые вы должны делать в своем коде для выполнения общих задач или соответствия некоторым методикам фреймворка. Они очень часто не приносят реальной пользы для деловых программ в ваших программах, и именно здесь Lombok делает вашу жизнь счастливее, а себя - продуктивнее.
Это работает путем подключения к процессу сборки и автоматической генерации байт-кода Java в файлы .class в соответствии с рядом аннотаций проекта, которые вы вводите в свой код.
Включать его в свои сборки независимо от того, какую систему вы используете, очень просто. На их странице project содержатся подробные инструкции по конкретным вопросам. Большинство моих проектов основаны на maven, поэтому я просто отбрасываю их зависимость в области provided , и я готов идти:
<dependencies>
...
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
<scope>provided</scope>
</dependency>
...
</dependencies>
Проверьте наличие последней доступной версии here .
Обратите внимание, что в зависимости от Lombok пользователи ваших _. Jar _ не будут зависеть от него, поскольку это чистая зависимость сборки, а не время выполнения.
2. Геттеры/сеттеры, конструкторы - такие повторяющиеся
Инкапсуляция свойств объекта с помощью общедоступных методов получения и установки является обычной практикой в мире Java, и многие фреймворки в значительной степени полагаются на этот шаблон «Java Bean»: класс с пустым конструктором и методы get/set для «свойств».
Это настолько распространено, что большинство IDE поддерживают автогенерацию кода для этих шаблонов (и не только). Однако этот код должен находиться в ваших источниках, а также поддерживаться, например, при добавлении нового свойства или переименовании поля.
Давайте рассмотрим этот класс, который мы хотим использовать в качестве сущности JPA в качестве примера:
@Entity
public class User implements Serializable {
private @Id Long id;//will be set when persisting
private String firstName;
private String lastName;
private int age;
public User() {
}
public User(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
//getters and setters: ~30 extra lines of code
}
Это довольно простой класс, но все же подумайте, если бы мы добавили дополнительный код для геттеров и сеттеров, у нас получилось бы определение, в котором у нас было бы больше стандартного кода с нулевым значением, чем у соответствующей бизнес-информации: «У пользователя есть первый и фамилии и возраст.
Давайте теперь Lombok-ize этот класс:
@Entity
@Getter @Setter @NoArgsConstructor//<--- THIS is it
public class User implements Serializable {
private @Id Long id;//will be set when persisting
private String firstName;
private String lastName;
private int age;
public User(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
}
Добавив аннотации @ Getter и @ Setter , мы сказали Lombok, что нужно сгенерировать их для всех полей класса.
@ NoArgsConstructor приведет к генерации пустого конструктора.
Обратите внимание, что это целый код класса, я не опускаю ничего, в отличие от версии выше с комментарием //getters и setters .
Для трех соответствующих классов атрибутов это значительная экономия в коде!
Если вы в дальнейшем добавите атрибуты (свойства) в свой класс User , произойдет то же самое: вы применили аннотации к самому типу, чтобы они по умолчанию обращали внимание на все поля.
Что, если вы хотите улучшить видимость некоторых свойств? Например, я хотел бы, чтобы модификаторы полей id моих сущностей package или protected были видны, потому что ожидается, что они будут прочитаны, но явно не установлены кодом приложения. Просто используйте более мелкозернистый @ Setter для этого конкретного поля:
private @Id @Setter(AccessLevel.PROTECTED) Long id;
3. Классы значений/DTO’s
Есть много ситуаций, в которых мы хотим определить тип данных с единственной целью представления сложных «значений» или «объектов передачи данных», большую часть времени в виде неизменяемых структур данных, которые мы строим один раз и никогда не хотим изменять ,
Мы разрабатываем класс для представления успешной операции входа в систему. Мы хотим, чтобы все поля были ненулевыми, а объекты были неизменяемыми, чтобы мы могли безошибочно обращаться к его свойствам:
public class LoginResult {
private final Instant loginTs;
private final String authToken;
private final Duration tokenValidity;
private final URL tokenRefreshUrl;
//constructor taking every field and checking nulls
//read-only accessor, not necessarily as get** () form
}
Опять же, объем кода, который мы должны написать для закомментированных разделов, будет иметь гораздо больший объем, чем та информация, которую мы хотим инкапсулировать, и это имеет для нас реальную ценность. Мы можем снова использовать Lombok, чтобы улучшить это:
@RequiredArgsConstructor
@Accessors(fluent = true) @Getter
public class LoginResult {
private final @NonNull Instant loginTs;
private final @NonNull String authToken;
private final @NonNull Duration tokenValidity;
private final @NonNull URL tokenRefreshUrl;
}
Просто добавьте аннотацию @ RequiredArgsConstructor , и вы получите конструктор для всех последних полей в классе, как вы их и объявили. Добавление @ NonNull к атрибутам заставляет наш конструктор проверять обнуляемость и генерировать NullPointerExceptions соответственно. Это также произошло бы, если бы поля были не окончательными, и мы добавили @ Setter для них.
Разве вам не нужна скучная старая форма get ** () для вашей недвижимости? Поскольку мы добавили @ Accessors (fluent = true) , в этом примере «getters» будет иметь то же имя метода, что и свойства: getAuthToken () просто становится authToken () .
Эта «свободная» форма будет применяться к неокончательным полям для установщиков атрибутов, а также учитывать цепочки вызовов
----//Imagine fields were no longer final now
return new LoginResult()
.loginTs(Instant.now())
.authToken("asdasd")
.//and so on
----
4. Основной Java Boilerplate
Другая ситуация, в которой мы в конечном итоге пишем код, который нам нужно поддерживать, - это генерация методов toString () , equals () и hashCode () . IDE пытаются помочь с шаблонами для их автоматической генерации с точки зрения атрибутов нашего класса.
Мы можем автоматизировать это с помощью других аннотаций уровня класса Lombok:
-
@ToString : будет
генерировать метод toString () , включающий все атрибуты класса. Не нужно самим писать и поддерживать его, поскольку мы обогащаем нашу модель данных.
по умолчанию сгенерирует методы equals () и hashCode () с учетом всех соответствующих полей, и в соответствии с http://www.artima.com/lejava/articles/equality.html так очень хорошо распознает семантику]
Эти генераторы поставляют очень удобные опции конфигурации. Например, если ваши аннотированные классы входят в иерархию, вы можете просто использовать параметр callSuper = true и родительские результаты будут учитываться при создании кода метода.
Подробнее об этом: скажем, у нас в примере объекта User JPA есть ссылка на события, связанные с этим пользователем:
@OneToMany(mappedBy = "user")
private List<UserEvent> events;
Мы не хотели бы, чтобы список дырочных событий сбрасывался всякий раз, когда мы вызываем метод toString () нашего пользователя, просто потому, что мы использовали аннотацию @ ToString . Нет проблем: просто настройте параметры следующим образом:
@ ToString (exclude = \ {«events»}) , и этого не произойдет. Это также помогает избежать циклических ссылок, если, например, _UserEvent s имел ссылку на User_ .
Для примера LoginResult нам может потребоваться определить равенство и вычисление хеш-кода только с точки зрения самого токена, а не других конечных атрибутов в нашем классе. Затем просто напишите что-нибудь вроде @ EqualsAndHashCode (of = \ {“authToken”}) .
5. Образец Строителя
Следующее может сделать для примера класса конфигурации для клиента REST API:
public class ApiClientConfiguration {
private String host;
private int port;
private boolean useHttps;
private long connectTimeout;
private long readTimeout;
private String username;
private String password;
//Whatever other options you may thing.
//Empty constructor? All combinations?
//getters... and setters?
}
Мы могли бы иметь первоначальный подход, основанный на использовании пустого конструктора класса по умолчанию и предоставлении методов установки для каждого поля. Однако в идеале мы бы хотели, чтобы конфигурации no были set -ed после того, как они были созданы (созданы), что делает их неизменными. Поэтому мы хотим избежать сеттеров, но написание такого потенциально длинного конструктора args является анти-паттерном.
Вместо этого мы можем указать инструменту для создания шаблона builder , не позволяя нам писать дополнительный класс Builder и связанные методы, подобные беглому сеттеру, просто добавив аннотацию @Builder в нашу ApiClientConfiguration .
@Builder
public class ApiClientConfiguration {
//... everything else remains the same
}
Оставив приведенное выше определение класса как таковое (без объявления конструкторов или сеттеров + @ Builder ), мы можем использовать его как:
ApiClientConfiguration config =
new ApiClientConfigurationBuilder()
.host("api.server.com")
.port(443)
.useHttps(true)
.connectTimeout(15__000L)
.readTimeout(5__000L)
.username("myusername")
.password("secret")
.build();
6. Проверено Исключения Бремя
Многие API Java спроектированы таким образом, что они могут выдавать множество проверенных исключений, код клиента принудительно либо catch , либо объявляется как throws . Сколько раз вы превращали эти исключения, которые, как вы знаете, не произойдут в нечто подобное?
public String resourceAsString() {
try (InputStream is = this.getClass().getResourceAsStream("sure__in__my__jar.txt")) {
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException | UnsupportedCharsetException ex) {
//If this ever happens, then its a bug.
throw new RuntimeException(ex); <--- encapsulate into a Runtime ex.
}
}
Если вы хотите избежать этих шаблонов кода, потому что компилятор не будет счастлив в противном случае (и, в конце концов, вы знаете , что проверенные ошибки не могут произойти), используйте метко названный https://projectlombok.org/features/SneakyThrows . HTML[ @ SneakyThrows ]:
@SneakyThrows
public String resourceAsString() {
try (InputStream is = this.getClass().getResourceAsStream("sure__in__my__jar.txt")) {
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
return br.lines().collect(Collectors.joining("\n"));
}
}
7. Убедитесь, что ваши ресурсы выпущены
Java 7 представила блок try-with-resources, чтобы гарантировать, что ваши ресурсы, хранящиеся в экземплярах чего-либо, реализующего java.lang . AutoCloseable , освобождаются при выходе.
Lombok предоставляет альтернативный способ достижения этого, и более гибко, через @Cleanup Используйте его для любой локальной переменной, ресурсы которой вы хотите убедиться, что освобождены. Им не нужно реализовывать какой-либо конкретный интерфейс, вы просто вызовете его метод close () .
@Cleanup InputStream is = this.getClass().getResourceAsStream("res.txt");
Ваш метод выпуска имеет другое название? Нет проблем, просто настройте аннотацию:
@Cleanup("dispose") JFrame mainFrame = new JFrame("Main Window");
8. Аннотируйте свой класс, чтобы получить регистратор
Многие из нас экономно добавляют операторы регистрации в наш код, создавая экземпляр Logger из нашего фреймворка. Скажи, SLF4J:
public class ApiClientConfiguration {
private static Logger LOG = LoggerFactory.getLogger(ApiClientConfiguration.class);
//LOG.debug(), LOG.info(), ...
}
Это настолько распространенный шаблон, что разработчики Lombok решили упростить его для нас:
@Slf4j//or: @Log @CommonsLog @Log4j @Log4j2 @XSlf4j
public class ApiClientConfiguration {
//log.debug(), log.info(), ...
}
Поддерживается множество https://projectlombok.org/features/Log.html [logging frameworks и, конечно, вы можете настроить имя экземпляра, тему и т. Д.
9. Напишите потокобезопасные методы
В Java вы можете использовать ключевое слово synchronized для реализации критических разделов. Однако это не на 100% безопасный подход: другой клиентский код может в конечном итоге также синхронизироваться на вашем экземпляре, что может привести к неожиданным тупикам.
Вот где приходит @Synchronized : аннотируйте свои методы (как экземпляры, так и статические) с ним, и вы получите автоматически сгенерированное частное, неэкспонированное поле, которое ваша реализация будет использовать для блокировки:
@Synchronized
public/** better than: synchronized ** /void putValueInCache(String key, Object value) {
//whatever here will be thread-safe code
}
10. Автоматизировать состав объектов
В Java нет конструкций на уровне языка, чтобы сгладить подход «наследования композиции в пользу». Другие языки имеют встроенные концепции, такие как Traits или Mixins для достижения этой цели.
Https://projectlombok.org/features/experimental/Delegate.html[@Delegate]от Lombok очень удобен, если вы хотите использовать этот шаблон программирования. Давайте рассмотрим пример:
-
Мы хотим, чтобы __User s и Customer __s имели общие свойства
для именования и номера телефона ** Мы определяем интерфейс и класс адаптера для этих полей
-
Наши модели реализуют интерфейс и @ Delegate для их
Адаптер, эффективно composing их с нашей контактной информацией
Во-первых, давайте определим интерфейс:
public interface HasContactInformation {
String getFirstName();
void setFirstName(String firstName);
String getFullName();
String getLastName();
void setLastName(String lastName);
String getPhoneNr();
void setPhoneNr(String phoneNr);
}
А теперь адаптер как класс support :
@Data
public class ContactInformationSupport implements HasContactInformation {
private String firstName;
private String lastName;
private String phoneNr;
@Override
public String getFullName() {
return getFirstName() + " " + getLastName();
}
}
Интересная часть приходит сейчас, посмотрим, как легко теперь объединить контактную информацию в оба класса моделей:
public class User implements HasContactInformation {
//Whichever other User-specific attributes
@Delegate(types = {HasContactInformation.class})
private final ContactInformationSupport contactInformation =
new ContactInformationSupport();
//User itself will implement all contact information by delegation
}
Случай с Customer был бы настолько похож, что для краткости мы опускаем образец.
11. Роллинг Ломбок Назад?
Краткий ответ: совсем нет.
Вы можете быть обеспокоены, что есть шанс, что вы используете Lombok в одном из ваших проектов, но позже захотите отменить это решение. Тогда у вас может быть большое количество классов, аннотированных для этого … что вы могли бы сделать?
Я никогда не сожалел об этом, но кто знает для вас, вашей команды или вашей организации. В этих случаях вы получаете покрытие благодаря инструменту delombok из того же проекта.
Путем delombok-ing вашего кода вы получите автоматически сгенерированный исходный код Java с теми же функциями, что и из байт-кода, созданного Lombok. Тогда вы можете просто заменить свой оригинальный аннотированный код этими новыми delomboked файлами и больше не зависеть от него.
Это то, что вы можете integrate в вашей сборке , и я делал это в прошлом, чтобы просто изучить сгенерированный код или интегрировать Lombok с другим инструментом на основе исходного кода Java. ,
12. Заключение
Есть некоторые другие функции, которые мы не представили в этой статье, и я рекомендую вам глубже погрузиться в feature обзор для более подробной информации и вариантов использования.
Кроме того, большинство функций, которые мы показали, имеют ряд параметров настройки, которые могут оказаться полезными для того, чтобы инструмент генерировал наиболее подходящие для вашей команды методы именования и т. Д. Доступные встроенные https://projectlombok.org/features/configuration .html[система конфигурации]также может помочь вам в этом.
Я надеюсь, что вы нашли стимул дать Lombok шанс войти в ваш набор инструментов для разработки на Java. Попробуйте и увеличьте свою производительность!
Код примера можно найти в проекте GitHub .