Введение в проект Ломбок

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 () , включающий все атрибуты класса. Не нужно самим писать и поддерживать его, поскольку мы обогащаем нашу модель данных.

по умолчанию сгенерирует методы 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”}) .

Бонус: если вам понравились функции из аннотаций, которые мы рассмотрели до сих пор, вы можете изучить @Data и @ Value аннотации, которые ведут себя так, как будто их набор был применен к нашим классам. В конце концов, эти обсуждаемые способы употребления очень часто объединяются во многих случаях.

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 .