Lombok: как отключить автоматическую генерацию геттеров и сеттеров

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Java-разработчики, которые хотят улучшить свой код и снизить его объем с помощью Lombok
  • Специалисты, работающие с конфиденциальными данными и требованиями к инкапсуляции
  • Студенты и новички, изучающие Java и желающие освоить современные инструменты разработки

    Lombok — это бесценный помощник Java-разработчика, избавляющий нас от тонн шаблонного кода. Но что делать, когда нужен полный контроль над доступом к полям? 🔍 Представьте ситуацию: у вас класс с десятком полей, для которых @Getter и @Setter работают отлично, но есть одно-два поля, требующие особого обращения. Настройка селективного генерирования методов доступа — это не просто вопрос чистоты кода, это вопрос защиты данных и соблюдения инкапсуляции. Разберемся, как именно Lombok позволяет тонко настраивать доступ к каждому полю вашего класса.

Изучаете Java и хотите писать чистый, лаконичный код? На Курсе Java-разработки от Skypro вы не только освоите Lombok и другие инструменты профессиональной разработки, но и научитесь создавать высокопроизводительные приложения. Наши выпускники уже через 9 месяцев применяют продвинутые техники оптимизации кода в реальных проектах. Присоединяйтесь — и пишите код, которым действительно можно гордиться!

Базовые приемы отключения геттеров и сеттеров в Lombok

Работа с Lombok начинается с понимания его базовой философии — уменьшение шаблонного кода. Однако контроль над генерацией методов доступа остается в руках разработчика. Рассмотрим основные способы выборочного отключения геттеров и сеттеров.

Самый простой способ избежать генерации геттера или сеттера для конкретного поля — использовать аннотацию с параметром уровня доступа:

Java
Скопировать код
@Getter
@Setter
public class User {
private String username;
private String email;

@Getter(AccessLevel.NONE) // Геттер не будет сгенерирован
private String password;
}

В приведенном примере Lombok сгенерирует методы доступа для полей username и email, но опустит создание геттера для поля password, обеспечивая дополнительный уровень защиты чувствительных данных.

Lombok предлагает различные уровни доступа через перечисление AccessLevel:

  • PUBLIC — стандартный уровень доступа (по умолчанию)
  • PROTECTED — для методов, доступных только в пакете и подклассах
  • PACKAGE — для методов с доступом на уровне пакета
  • PRIVATE — для методов, доступных только внутри класса
  • NONE — полное исключение генерации метода

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

Java
Скопировать код
public class Configuration {
@Getter // Только геттер
private String serverName;

@Setter // Только сеттер
private int maxConnections;

// Без автоматически генерируемых методов доступа
private boolean debugMode;
}

Этот подход особенно полезен при создании конфигурационных классов или классов данных с различными требованиями к доступу для разных полей.

Метод исключения Применимость Пример использования
Явное указание AccessLevel.NONE Для отдельных полей при общей аннотации класса @Getter(AccessLevel.NONE) private String secretKey;
Отсутствие аннотации на поле Когда аннотации применяются к каждому полю отдельно private int internalCounter; // Без аннотаций
Явное создание ручного метода Для полей с особой логикой доступа public String getFormattedName() { return name.toUpperCase(); }

Алексей Петров, Tech Lead

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

Изначально я применял аннотации @Getter и @Setter на уровне класса, что привело к нежелательной доступности конфиденциальных полей. Решение пришло, когда я обнаружил возможность использовать AccessLevel.NONE:

Java
Скопировать код
@Getter
@Setter
public class PaymentTransaction {
private Long id;
private BigDecimal amount;

@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private String securityToken;

// Специализированный метод доступа с логированием
public String getSecurityToken(User requestingUser) {
log.info("Token access requested by: {}", requestingUser.getId());
// Дополнительная проверка прав доступа
return securityToken;
}
}

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

Пошаговый план для смены профессии

Применение AccessLevel.NONE для избирательной генерации

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

При работе с классами, имеющими смешанные требования к доступу, AccessLevel.NONE становится незаменимым. Рассмотрим более сложный пример:

Java
Скопировать код
@Getter
@Setter
public class BankAccount {
private String accountNumber;
private String ownerName;

@Getter(AccessLevel.NONE) // Блокируем прямой доступ к балансу
private BigDecimal balance;

@Setter(AccessLevel.NONE) // Идентификатор нельзя изменять после создания
private UUID accountId = UUID.randomUUID();

// Специальный метод с проверкой и аудитом
public BigDecimal getBalance(User requestingUser) {
// Проверка прав доступа и аудит
return balance;
}

// Методы для изменения баланса с бизнес-логикой
public void deposit(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Deposit amount must be positive");
}
this.balance = this.balance.add(amount);
}

public void withdraw(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Withdrawal amount must be positive");
}
if (this.balance.compareTo(amount) < 0) {
throw new InsufficientFundsException("Insufficient funds");
}
this.balance = this.balance.subtract(amount);
}
}

В этом примере мы:

  • Блокируем прямой доступ к полю balance через стандартный геттер
  • Запрещаем изменение accountId после создания объекта
  • Предоставляем специальные методы с бизнес-логикой для контролируемого изменения баланса

Применение AccessLevel.NONE особенно эффективно в следующих случаях:

  1. Защита чувствительных данных — предотвращение несанкционированного доступа к конфиденциальной информации
  2. Обеспечение бизнес-правил — гарантия того, что изменение данных происходит только через методы с проверками
  3. Поддержка неизменяемости — запрет модификации определенных полей после инициализации
  4. Упрощение API — сокрытие внутренних деталей реализации от пользователей класса

Иногда возникает необходимость в более тонкой настройке доступа — например, когда нужно сгенерировать метод, но с другим уровнем доступа:

Java
Скопировать код
public class AuditRecord {
@Getter // Публичный геттер
@Setter(AccessLevel.NONE) // Сеттер не генерируется
private final Instant timestamp = Instant.now();

@Getter(AccessLevel.PACKAGE) // Геттер с доступом на уровне пакета
@Setter(AccessLevel.PACKAGE) // Сеттер с доступом на уровне пакета
private String internalNote;

@Getter // Публичный геттер
@Setter(AccessLevel.PROTECTED) // Защищенный сеттер
private AuditStatus status = AuditStatus.CREATED;
}

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

Сценарий использования Рекомендуемая настройка Преимущества подхода
Иммутабельные поля @Getter + @Setter(AccessLevel.NONE) Гарантия неизменности после инициализации
Поля только для внутреннего использования @Getter(AccessLevel.NONE) + @Setter(AccessLevel.NONE) Полное сокрытие внутренних деталей реализации
Поля с особой логикой доступа @Getter(AccessLevel.NONE) + ручной метод с бизнес-логикой Контролируемый доступ с соблюдением бизнес-правил
Поля для ограниченного внутреннего использования @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) Доступность только для классов в том же пакете

Тонкая настройка аннотаций @Getter и @Setter для полей

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

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

Java
Скопировать код
public class Product {
@Getter // Стандартный геттер
private String name;

@Setter(AccessLevel.NONE) // Отключаем стандартный сеттер
private BigDecimal price;

// Ручной сеттер с валидацией
public void setPrice(BigDecimal newPrice) {
if (newPrice == null || newPrice.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Price must be positive");
}
this.price = newPrice;
}

// Lombok не переопределит этот метод
public BigDecimal getPrice() {
return price.setScale(2, RoundingMode.HALF_UP);
}
}

В этом примере мы:

  • Используем стандартный Lombok-геттер для поля name
  • Отключаем автогенерацию сеттера для price с помощью AccessLevel.NONE
  • Реализуем собственный сеттер с проверкой корректности цены
  • Предоставляем ручной геттер, который возвращает цену, округленную до двух знаков

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

Для полей с особыми требованиями к формату или представлению можно также использовать сочетание аннотаций и дополнительных методов:

Java
Скопировать код
public class Employee {
@Getter @Setter
private String firstName;

@Getter @Setter
private String lastName;

@Getter(AccessLevel.NONE) // Отключаем стандартный геттер
private LocalDate birthDate;

// Стандартный сеттер для birthDate генерируется автоматически

// Специализированные методы доступа к возрасту и дате рождения
public LocalDate getBirthDate() {
return birthDate;
}

public int getAge() {
return birthDate != null 
? Period.between(birthDate, LocalDate.now()).getYears()
: 0;
}

// Дополнительный метод для полного имени
public String getFullName() {
return firstName + " " + lastName;
}
}

Такой подход позволяет одновременно использовать преимущества Lombok для снижения шаблонного кода и при этом расширять функциональность класса специализированными методами.

Михаил Соколов, Senior Java Developer

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

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

Java
Скопировать код
@Getter
@Setter
public class PatientRecord {
// Основные данные с обычным доступом
private UUID patientId;
private String firstName;
private String lastName;

// Медицинские данные с ограниченным доступом
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private List<Diagnosis> diagnoses = new ArrayList<>();

@Getter(AccessLevel.NONE)
private String medicalNotes;

// Контролируемый доступ к медицинским данным
public List<Diagnosis> getDiagnoses(Doctor requestingDoctor) {
auditAccess(requestingDoctor, "diagnoses");
if (!hasAccess(requestingDoctor)) {
throw new AccessDeniedException("No permission to view diagnoses");
}
return Collections.unmodifiableList(diagnoses);
}

public void addDiagnosis(Doctor doctor, Diagnosis diagnosis) {
auditAccess(doctor, "diagnoses:add");
if (!hasAccess(doctor)) {
throw new AccessDeniedException("No permission to add diagnosis");
}
this.diagnoses.add(diagnosis);
}

// Метод с расширенными проверками и аудитом
public String getMedicalNotes(Doctor requestingDoctor) {
auditAccess(requestingDoctor, "medicalNotes");
if (!isTreatingPhysician(requestingDoctor)) {
throw new AccessDeniedException("Only treating physician can view medical notes");
}
return medicalNotes;
}

public void setMedicalNotes(Doctor doctor, String notes) {
auditAccess(doctor, "medicalNotes:update");
if (!isTreatingPhysician(doctor)) {
throw new AccessDeniedException("Only treating physician can update notes");
}
this.medicalNotes = notes;
}

// Вспомогательные методы проверки доступа...
}

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

Сочетание аннотаций класса и полей для гибкого контроля

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

Рассмотрим стратегию "по умолчанию для всех, исключения для избранных":

Java
Скопировать код
@Getter // Геттеры для всех полей по умолчанию
@Setter // Сеттеры для всех полей по умолчанию
public class UserProfile {
private String username;
private String email;

@Setter(AccessLevel.NONE) // Запрещаем изменение ID
private final UUID userId = UUID.randomUUID();

@Getter(AccessLevel.NONE) // Отключаем прямой доступ к паролю
private String password;

@Getter(AccessLevel.PROTECTED) // Ограниченный доступ к роли
@Setter(AccessLevel.PROTECTED) // Ограниченный доступ к изменению роли
private UserRole role;

// Специализированные методы для работы с паролем
public boolean verifyPassword(String inputPassword) {
return password != null && password.equals(inputPassword);
}

public void changePassword(String oldPassword, String newPassword) {
if (!verifyPassword(oldPassword)) {
throw new SecurityException("Old password is incorrect");
}
// Дополнительные проверки сложности пароля
this.password = newPassword;
}
}

В приведенном примере мы:

  • Устанавливаем геттеры и сеттеры для всех полей по умолчанию
  • Запрещаем сеттер для поля userId, делая его эффективно неизменяемым
  • Отключаем стандартный геттер для password и предоставляем метод проверки пароля
  • Ограничиваем доступ к полю role на уровне protected

Также можно использовать обратный подход — "запрещено всё, кроме разрешенного явно":

Java
Скопировать код
@Getter(AccessLevel.NONE) // По умолчанию геттеры не генерируются
@Setter(AccessLevel.NONE) // По умолчанию сеттеры не генерируются
public class SecureDocument {
@Getter // Явно разрешаем геттер
private final UUID documentId = UUID.randomUUID();

@Getter // Явно разрешаем геттер
private final Instant createdAt = Instant.now();

private String title;
private String content;
private DocumentStatus status = DocumentStatus.DRAFT;

// Методы доступа с проверками прав
public String getTitle(User user) {
verifyReadAccess(user);
return title;
}

public void setTitle(User user, String newTitle) {
verifyWriteAccess(user);
this.title = newTitle;
}

public String getContent(User user) {
verifyReadAccess(user);
return content;
}

public void setContent(User user, String newContent) {
verifyWriteAccess(user);
this.content = newContent;
}

@Getter(AccessLevel.PACKAGE) // Доступен только в пакете
public DocumentStatus getStatus() {
return status;
}

// Специализированные методы изменения статуса
public void publish(User user) {
verifyPublishAccess(user);
this.status = DocumentStatus.PUBLISHED;
}

// Вспомогательные методы проверки доступа...
}

В этом подходе мы:

  • Отключаем генерацию всех методов доступа на уровне класса
  • Избирательно включаем генерацию геттеров только для необходимых полей
  • Реализуем специализированные методы доступа с проверками безопасности
  • Используем методы с бизнес-семантикой вместо прямых сеттеров

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

При работе с аннотациями Lombok важно помнить о порядке их применения:

  1. Аннотации на уровне поля всегда переопределяют аннотации на уровне класса
  2. Если для поля определен ручной метод доступа, Lombok не будет генерировать его автоматически
  3. Аннотации более конкретного уровня имеют приоритет над более общими

Это позволяет создавать точные и читаемые определения классов с минимальным объемом кода. 🔒

Практические решения для типичных сценариев использования

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

Сценарий 1: Классы с иммутабельными полями

Для создания классов с частично или полностью неизменяемыми полями:

Java
Скопировать код
@Getter
public class ImmutableEntity {
private final UUID id;
private final Instant createdAt;

@Setter // Только это поле можно изменять
private String description;

public ImmutableEntity(String description) {
this.id = UUID.randomUUID();
this.createdAt = Instant.now();
this.description = description;
}
}

Здесь поля id и createdAt инициализируются при создании и не имеют сеттеров, что делает их неизменяемыми, а поле description можно модифицировать.

Сценарий 2: Поля с валидацией при установке значений

Java
Скопировать код
@Getter
@Setter
public class ValidatedEntity {
private String name;

@Setter(AccessLevel.NONE) // Отключаем стандартный сеттер
private Integer age;

// Сеттер с валидацией
public void setAge(Integer age) {
if (age == null || age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be between 0 and 150");
}
this.age = age;
}

@Setter(AccessLevel.NONE) // Отключаем стандартный сеттер
private String email;

// Сеттер с валидацией формата email
public void setEmail(String email) {
if (email != null && !email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
throw new IllegalArgumentException("Invalid email format");
}
this.email = email;
}
}

В этом примере мы отключаем стандартные сеттеры для полей, требующих валидации, и предоставляем собственные методы с проверками.

Сценарий 3: Поля с ленивой инициализацией

Java
Скопировать код
@Getter
public class LazyLoadingEntity {
private final String id;

@Getter(AccessLevel.NONE) // Отключаем стандартный геттер
private List<RelatedEntity> relatedEntities;

public LazyLoadingEntity(String id) {
this.id = id;
}

// Геттер с ленивой инициализацией
public List<RelatedEntity> getRelatedEntities() {
if (relatedEntities == null) {
// Загружаем связанные сущности только при необходимости
relatedEntities = databaseService.loadRelatedEntities(id);
}
return Collections.unmodifiableList(relatedEntities);
}
}

Этот подход позволяет отложить дорогостоящие операции загрузки данных до момента их реального использования.

Сценарий 4: Классы с аудитом действий

Java
Скопировать код
@Getter
@Setter
public class AuditedEntity {
private String name;

@Getter(AccessLevel.NONE) // Отключаем стандартный геттер
@Setter(AccessLevel.NONE) // Отключаем стандартный сеттер
private BigDecimal balance;

// Геттер с аудитом
public BigDecimal getBalance(User user) {
auditService.logAccess(user, "balance:read", this);
return balance;
}

// Специализированные методы изменения баланса с аудитом
public void deposit(User user, BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}

auditService.logOperation(user, "balance:deposit", this, amount);
this.balance = this.balance.add(amount);
}

public void withdraw(User user, BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
if (this.balance.compareTo(amount) < 0) {
throw new InsufficientFundsException("Insufficient funds");
}

auditService.logOperation(user, "balance:withdraw", this, amount);
this.balance = this.balance.subtract(amount);
}
}

Этот паттерн особенно полезен для финансовых или других чувствительных операций, где требуется отслеживание всех действий пользователей.

Проблема Решение с Lombok Ключевые аннотации
Иммутабельность @Getter + final поля + отсутствие @Setter @Getter, @AllArgsConstructor
Валидация входных данных @Setter(AccessLevel.NONE) + ручные сеттеры с проверками @Getter, @Setter(AccessLevel.NONE)
Ленивая инициализация @Getter(AccessLevel.NONE) + ручной геттер с проверкой null @Getter(AccessLevel.NONE)
Аудит доступа @Getter(AccessLevel.NONE) + @Setter(AccessLevel.NONE) + специальные методы @Getter(AccessLevel.NONE), @Setter(AccessLevel.NONE)
Разграничение прав Геттеры/сеттеры с параметром пользователя и проверкой прав @Getter(AccessLevel.NONE), @Setter(AccessLevel.NONE)

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

Избирательная генерация геттеров и сеттеров с помощью Lombok — это не просто техническая особенность, а мощный инструмент проектирования API ваших классов. Правильное применение AccessLevel.NONE и комбинирование аннотаций на разных уровнях позволяет создавать более безопасные, понятные и соответствующие принципам инкапсуляции классы, не жертвуя при этом краткостью кода. Помните: каждый публичный метод — это часть контракта вашего класса. Используйте представленные техники для точного контроля над тем, что вы действительно хотите предоставить пользователям ваших классов.

Загрузка...