Библиотека Java разработчика
📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate. По всем вопросам @evgenycarter РКН clck.ru/3KoGeP
Больше📈 Аналитический обзор Telegram-канала Библиотека Java разработчика
Канал Библиотека Java разработчика (@bookjava) языкового сегмента Русский является активным участником. Сейчас сообщество объединяет 10 115 подписчиков, занимая 11 697 место в категории Технологии и приложения и 62 901 место в регионе Россия.
📊 Показатели аудитории и динамика
С момента создания невідомо проект демонстрирует стремительный рост, собрав аудиторию из 10 115 подписчиков.
Согласно последним данным от 27 августа, 2026, канал показывает стабильную активность. За последние 30 дней изменение числа участников составило -73, а за последние 24 часа — -3, при этом общий охват остаётся высоким.
- Статус верификации: Не верифицирован
- Уровень вовлечённости (ER): Средний показатель вовлечённости аудитории составляет 7.20%. В первые 24 часа после публикации контент обычно набирает 3.71% реакций от общего числа подписчиков.
- Охват публикаций: В среднем каждый пост получает 729 просмотров. В течение первых суток публикация набирает 375 просмотров.
- Реакции и взаимодействия: Аудитория активно поддерживает контент: среднее количество реакций на один пост — 7.
- Тематические интересы: Контент сосредоточен на ключевых темах, таких как string, интерфейс, строка, boot, api.
📝 Описание и контентная политика
Автор описывает ресурс как площадку для выражения субъективного мнения:
“📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate.
По всем вопросам @evgenycarter
РКН clck.ru/3KoGeP”
Благодаря высокой частоте обновлений (последние данные получены 28 августа, 2026) канал поддерживает актуальность и высокий уровень охвата публикаций. Аналитика показывает, что аудитория активно взаимодействует с контентом, что делает его важной точкой влияния в категории Технологии и приложения.
record и @ConstructorBinding
Вместо традиционных @Data + пустого конструктора можно сразу использовать Java 17 record для настройки свойств:
📌 Почему это полезно?
🔴Полная иммутабельность: поля конфигов больше нельзя случайно перезаписать.
🔴Минимум «шаблонного» кода: не нужны геттеры, сеттеры, toString(), equals() и т.д.
🔴Чёткая связь с Java 17+ и актуальными best practices.
💡 Как сделать:
1. Подключаем зависимость:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
Это нужно, чтобы IDE и Spring метаинфу подхватили.
2. Создаём record с аннотацией:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
@ConstructorBinding
@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(
String host,
int port,
String username,
String password
) {}
3. Регистрируем бин в Spring:
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class AppConfig { }
4. Конфигурируем в application.yml (или .properties):
app:
mail:
host: smtp.example.com
port: 587
username: user@example.com
password: secret123
⚠️ Обратите внимание:
🔴Без @ConstructorBinding Spring не сможет смотать значения в record’ы.
🔴Уберите все сеттеры и по умолчанию конструктор генерируется автоматически.
🔴Если вам нужна валидация свойств, добавьте @Validated и JSR-303 аннотации (@NotNull, @Min и т.д.).
🧠 Что получилось?
🔴Минимум «мусора» в коде: один блок record заменил класс с 4 полями, геттерами и конструктором.
🔴Полная типобезопасность и поддержка автокомплита при обращении к полям.
🔴Быстрый переход на Java 17+ подходы без потери функциональности.
💡 Дополнительный лайфхак:
Если вам нужно разделить конфиги по окружениям (dev/prod), просто создайте два record’а с разными префиксами или используйте @Profile. В Spring Boot 3 этот подход «из коробки» работает наилучшим образом.
👉@BookJavaSecurityContext не “переходит” автоматически в новые потоки. Для этого используют SecurityContextRepository и специальные методы в WebFlux.
⚠️ Важно: не храните SecurityContext в сессии, если у вас stateless-приложение (REST API). Вместо сессии используйте JWT или OAuth 2.0.
🧠 5. Авторизация: FilterSecurityInterceptor & AccessDecisionManager
FilterSecurityInterceptor запускается в конце цепочки фильтров и проверяет доступ к URL. Он запрашивает у SecurityMetadataSource список необходимых ролей для данного эндпоинта (Spring на основании @PreAuthorize, HttpSecurity конфигурации или XML). Затем передаёт дело в AccessDecisionManager (по умолчанию AffirmativeBased), который опрашивает список AccessDecisionVoter (например, RoleVoter для проверок ролей, WebExpressionVoter для SpEL).
FilterSecurityInterceptor
└─> SecurityMetadataSource (что нужно: ROLE_ADMIN)
└─> AccessDecisionManager.vote()
├─ RoleVoter.vote() → совпадает?
└─ WebExpressionVoter.vote() → SpEL-выражения?
Если хотя бы один голос “grant”, AffirmativeBased отпускает запрос (по умолчанию). Можно менять стратегию на Consensus или Unanimous.
💡 Трюк: чтобы локально протестировать SecurityContext, можно в тестах использовать аннотацию @WithMockUser(roles = "ADMIN") и проверять, что нужный эндпоинт доступен.
🧠 6. Аннотации & Method Security
Помимо URL-уровня, есть методная проверка:
@EnableMethodSecurity // (Spring Boot 3+) вместо @EnableGlobalMethodSecurity
public class SecurityConfig { ... }
// Где-то в сервисе:
@PreAuthorize("hasRole('ADMIN') and #id == principal.id")
public void deleteUser(Long id) { ... }
* 📌 @PreAuthorize / @PostAuthorize / @Secured / @RolesAllowed — все используют тот же механизм Voter’ов, но проверяют уже на методах сервиса.
* 💡 Совет: включайте методную безопасность только там, где действительно нужна тонкая грануляция.
🧠 7. Хранение паролей & PasswordEncoder
С Java 17+ используйте PasswordEncoder с алгоритмами Argon2 или BCrypt:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
⚠️ Никогда не храните пароли в открытом виде и не используйте MD5/SHA-1 — они считаются небезопасными.
🧠 8. Stateless vs Stateful
* Stateful (Сессии): Spring создаёт HTTP-сессию, а SecurityContextPersistenceFilter хранит контекст в сессии. Удобно для монолитов с классическим web-приложением.
* Stateless (JWT/OAuth2): убираем SessionCreationPolicy.STATELESS, используем BearerTokenAuthenticationFilter, аутентификация и авторизация проверяются по JWT в каждом запросе.
💡 Современный стек (Spring Boot 3+):
1. Настраиваем SecurityFilterChain с oauth2ResourceServer().jwt().
2. Подключаем spring-boot-starter-oauth2-resource-server.
3. Указываем issuer-uri или jwk-set-uri в application.yml.
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://keycloak.example.com/realms/myrealm
🧠 9. Подпись запросов & CSRF
* По умолчанию CSRF включён для “форменных” запросов (POST, PUT, DELETE). Для stateless-API его обычно отключают:
http.csrf().disable();
* Если используете формы, не забудьте добавить в шаблон:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
⚠️ Внимание: не отключайте CSRF, если ваше приложение использует сессии и cookie на фронтенде!
💡 Совет по отладке: включите логирование фильтров:
logging.level.org.springframework.security=DEBUG
Тогда в логах вы увидите, как проходит запрос через каждый фильтр и где происходит отказ.
👉@BookJavaSecurityContext (где хранится Authentication).
* ⚙️ UsernamePasswordAuthenticationFilter – обрабатывает форму логина (если вы используете formLogin).
* ⚙️ BasicAuthenticationFilter – поддерживает HTTP Basic (для REST).
* ⚙️ BearerTokenAuthenticationFilter (Spring Boot 3+) – для JWT/OAuth2 Bearer-токенов.
* ⚙️ ExceptionTranslationFilter – перехватывает AccessDeniedException и AuthenticationException, перенаправляет на страницу логина или возвращает 401.
* ⚙️ FilterSecurityInterceptor – проверяет, есть ли у аутентифицированного пользователя разрешение (ROLE_*) для доступа к ресурсу.
Каждый фильтр решает конкретную задачу, и порядок важен: если, например, фильтр авторизации (FilterSecurityInterceptor) стоит раньше, чем фильтр аутентификации, вы получите неожиданный отказ.
💡 Современный подход (Spring Boot 3+):
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(oauth2 -> oauth2.jwt()); // JWT из OIDC/JWK
return http.build();
}
Таким образом вы сами управляете порядком фильтров и включаете только нужные.
🧠 2. AuthenticationManager & ProviderManager
Когда UsernamePasswordAuthenticationFilter (или другой аутентификатор) получает учётные данные, он создает UsernamePasswordAuthenticationToken с неверифицированными (unauthenticated) флагом. Затем передаёт этот токен в AuthenticationManager:
UsernamePasswordAuthenticationFilter → AuthenticationManager.authenticate()
AuthenticationManager по умолчанию — это ProviderManager, который хранит список AuthenticationProvider (например, DaoAuthenticationProvider для UserDetailsService или JwtAuthenticationProvider для токенов). Каждый Provider пытается аутентифицировать токен, и если успешно, возвращает уже аутентифицированный Authentication с authorities.
📌 Совет: если нужно добавить кастомную проверку (например, MFA), реализуйте свой AuthenticationProvider и зарегистрируйте его перед DaoAuthenticationProvider.
🧠 3. UserDetailsService & UserDetails
DaoAuthenticationProvider опирается на UserDetailsService (или ReactiveUserDetailsService в WebFlux), чтобы получить UserDetails (имя, пароль, роли, статус аккаунта). В Java 17+ можно пользоваться Map.of(...) или List.of(...), но в реальных проектах лучше хранить в БД через JPA/Hibernate.
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository repo;
@Override
public UserDetails loadUserByUsername(String username) {
UserEntity user = repo.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return User.withUsername(user.getUsername())
.password(user.getPassword())
.authorities(user.getRoles().toArray(new String[0]))
.accountLocked(!user.isAccountNonLocked())
.build();
}
}
🧠 4. SecurityContext & SecurityContextHolder
После успешной аутентификации фильтр устанавливает SecurityContext в SecurityContextHolder. По умолчанию используется стратегия MODE_THREADLOCAL, т.е. контекст привязан к текущему потоку.
SecurityContextHolder.getContext().setAuthentication(authenticatedToken);User и связанные с ними сущности Order. При запросе пользователей:
List<User> users = userRepository.findAll(); // Один запрос
for(User user : users) {
List<Order> orders = user.getOrders(); // +1 запрос на каждого пользователя!
}
Если пользователей 1000, то будет 1 + 1000 SQL-запросов. Это убийственно для производительности ⚠️
🛠 Как решить?
💡 Вариант 1: JOIN FETCH
Самый быстрый и простой путь — сразу подтянуть связанные сущности:
@Query("SELECT u FROM User u JOIN FETCH u.orders")
List<User> findAllWithOrders();
✅ Всего один SQL-запрос
❌ Может привести к дублированию строк, если связей много и они сложные
💡 Вариант 2: Entity Graph (Spring Data JPA)
Entity Graph — более гибкий подход:
@EntityGraph(attributePaths = {"orders"})
List<User> findAll();
✅ Удобно и понятно, нет дублей
✅ Можно легко настраивать под конкретный запрос
💡 Вариант 3: Batch fetching (настройка Hibernate)
spring.jpa.properties.hibernate.default_batch_fetch_size=20
Hibernate будет автоматически загружать связанные сущности пачками.
✅ Просто настроить и сразу эффект
❌ Всё равно несколько запросов, хотя и пачками
🧠 Когда что использовать?
▫️ JOIN FETCH — когда связи простые, а данных немного.
▫️ Entity Graph — если нужна гибкость и удобство.
▫️ Batch fetching — когда настроить проще, чем переписывать код.
Не забывай проверять SQL-запросы в логах и профилировать приложение 🔥
👉@BookJavaXmx и Xms ты прописываешь с закрытыми глазами, самое время разобраться, что же реально происходит с памятью в Java и как новые сборщики мусора могут повлиять на производительность твоего приложения.
🗂 В этой серии — подробный разбор всех современных GC, доступных в Java HotSpot VM: от базовых до самых продвинутых. Для каждого — объяснение принципов, сценарии применения, плюсы и минусы, а также практические советы по настройке.
Список статей:
1️⃣ Введение
2️⃣ Serial GC и Parallel GC
3️⃣ CMS и G1
4️⃣ ZGC
5️⃣ Epsilon GC
6️⃣ Shenandoah GC
👉@BookJava
// НЕ рекомендуется:
@EnableTransactionManagement
public class Config {
@Bean
public JtaTransactionManager transactionManager() {
return new JtaTransactionManager();
}
}
// Лучше outbox + отдельный publisher
🧠 В 99% случаев проще и надёжнее строить архитектуру вокруг событий и простых локальных транзакций, чем бороться с XA.
👉@BookJava@Transactional) тут не подходят — нет распределённого ACID. Saga решает эту проблему через последовательность локальных транзакций с возможностью отката (compensation).
💡 Виды Saga
1. Choreography
Нет единого оркестратора. Каждый сервис реагирует на события предыдущих через очередь (Kafka, RabbitMQ).
* Простой flow
* Меньше точек отказа
2. Orchestration
Есть выделенный "оркестратор", который координирует выполнение саги, рассылая команды сервисам.
* Более явный контроль
* Лучше трассировка
⚖️ Плюсы
* Обеспечивает согласованность между сервисами
* Высокая отказоустойчивость: каждый шаг можно компенсировать
* Гибкость — легко расширять и модифицировать бизнес-логику
⚠️ Минусы
* Сложнее реализовать, чем простые транзакции
* Не моментальная консистентность — возможны временные аномалии
* Нужно проектировать compensating transactions для каждого шага
* Тяжело тестировать крайние случаи и "сорванные" шаги
🔁 Альтернативы
* Distributed Transactions (XA, 2PC) — редко используются из-за сложности и слабой поддержки в современных cloud-системах.
* Event Sourcing — другой паттерн работы с изменениями, но сложнее для чтения.
* Idempotency + Retry — иногда достаточно, если бизнес-процесс допускает.
💡 Совет: для большинства бизнес-операций с межсервисным взаимодействием, где требуется отмена — Saga остаётся самым практичным выбором. В Spring есть библиотека Axon Framework, также стоит посмотреть на Camunda.
👉@BookJavaComparator:
List<User> users = ...;
users.sort(Comparator
.comparing(User::getName, Comparator.nullsLast(String::compareToIgnoreCase))
.thenComparingInt(User::getAge)
);
📌 comparing — сравнивает по полю (например, name).
📌 nullsLast или nullsFirst — удобно обрабатывать возможные null.
📌 thenComparingInt — дополнительная сортировка (например, по возрасту).
💡 Такой подход краткий, читабельный и отлично работает с любыми полями, даже если они могут быть null.
⚠️ Никогда не забывай про null, особенно если сортируешь данные из БД или внешних источников. Ошибка NullPointerException во время сортировки может неожиданно прилететь в проде.
👉@BookJavaCollections.sort(), знай — под капотом происходит скрытая магия.
📌 Collections.sort(list) сначала проверяет, какой это список. Если это RandomAccess-list (например, ArrayList) — используется быстрый сортировщик (TimSort) прямо по массиву.
⚠️ Но если это LinkedList (или любой не-рандом-доступ список), внутри происходит... преобразование списка в массив! Сначала все элементы копируются в массив, потом сортируются, а затем результат обратно заливается в твой список.
// Да, это реально работает так:
List<Integer> list = new LinkedList<>();
// ...заполняем...
Collections.sort(list);
💡 Почему так? Сортировка напрямую через LinkedList была бы очень медленной — доступ к элементам по индексу у него O(n), а не O(1) как у ArrayList. Поэтому JDK идёт на компромисс: временная потеря памяти, но ускорение времени сортировки.
⚠️ Следствие: если у тебя большой LinkedList, сортировка будет требовать двойную память: сначала весь список копируется в массив.
Совет:
Для больших коллекций, которые часто сортируются, используй ArrayList, либо готовься к затратам памяти.
👉@BookJava@Valid, @NotNull).
2️⃣ Используй PreparedStatement.
⚠️ Никогда не конкатенируй SQL-запросы напрямую, чтобы избежать SQL-инъекций.
3️⃣ Не раскрывай детали исключений.
📌 Отправляй клиенту общий код ошибки, а детали логируй.
4️⃣ Скрывай конфигурацию.
💡 Используй application.yml с переменными окружения для чувствительных данных (пароли, ключи API).
5️⃣ Ограничивай доступ.
📌 Spring Security — твой друг. Используй @PreAuthorize и грамотно настраивай роли и права доступа.
6️⃣ Безопасная сериализация.
⚠️ Избегай Java-стандартной сериализации. Предпочитай JSON (Jackson) с явными DTO.
7️⃣ Используй актуальные версии библиотек.
📌 Регулярно проверяй зависимости (dependency-check), чтобы избежать известных уязвимостей.
8️⃣ Грамотно логируй.
💡 Не логируй пароли, токены и персональные данные. Используй SLF4J с Logback, настрой уровень логов.
9️⃣ Безопасные куки и заголовки.
📌 Используй атрибуты куки: Secure, HttpOnly, SameSite. Spring Security поможет тебе настроить это быстро.
🔟 Настрой Security Headers.
📌 Используй заголовки:
* X-Frame-Options: DENY
* X-Content-Type-Options: nosniff
* Content-Security-Policy (CSP)
Безопасность — это не разовая задача, а процесс 💡
👉@BookJava