Библиотека джависта | Java, Spring, Maven, Hibernate
22.6K subscribers
2.35K photos
52 videos
47 files
3.41K links
Все самое полезное для Java-разработчика в одном канале.

Список наших каналов: https://t.me/proglibrary/9197

Для обратной связи: @proglibrary_feeedback_bot

По рекламе: @proglib_adv

РКН: https://gosuslugi.ru/snet/67a5bbda1b17b35b6c1a55c4
Download Telegram
🕯 Memory Ordering и happens-before

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

🔹 Проблема без volatile


Современные процессоры и компиляторы переупорядочивают инструкции для оптимизации. Каждое ядро имеет store buffer — запись в память не мгновенна, сначала попадает в буфер. Другое ядро может не увидеть запись ещё долгое время.

// Поток 1
data = 42;
ready = true;

// Поток 2
if (ready) {
System.out.println(data); // может напечатать 0
}


Без барьеров компилятор или процессор может переставить data = 42 и ready = true. Или поток 2 увидит ready = true из кеша раньше чем data = 42 из store buffer дойдёт до памяти.

🔹 Что на самом деле гарантирует volatile

Запись в volatile переменную

Сбрасывает store buffer. Все предыдущие записи этого потока становятся видимы другим потокам до того как запись в volatile будет видна.

Чтение volatile переменной

Инвалидирует локальный кеш. Поток видит актуальные значения всех записей, которые произошли до соответствующей volatile-записи в другом потоке.

Это и есть happens-before: если поток A записал в volatile переменную, а поток B прочитал это значение — всё что A делал до записи, видимо B после чтения.

volatile boolean ready;
int data; // не volatile!

// Поток 1
data = 42; // HB-предшествует записи в ready
ready = true; // volatile write

// Поток 2
if (ready) { // volatile read
// data гарантированно == 42
System.out.println(data);
}


data не volatile, но гарантия работает через happens-before цепочку.

🔹 Что volatile не гарантирует

Атомарность составных операций:

volatile long counter;
counter++; // не атомарно: read → increment → write


На 32-битных JVM даже чтение/запись long без volatile не атомарна (два 32-битных слова). volatile long делает операцию атомарной, но counter++ всё равно не атомарна как составная операция.

🔹 Модель памяти Java (JMM) и happens-before

JMM определяет happens-before не только для volatile. Полный список отношений:

— Запись в поле до разблокировки монитора HB разблокировке.
— Разблокировка монитора HB последующей блокировке того же монитора.
— Запись в volatile HB последующему чтению той же переменной.
— Завершение Thread.start() HB любому действию в запущенном потоке.
— Любое действие в потоке HB Thread.join() на этом потоке.

Эти правила транзитивны. Именно на этом строятся корректные публикации объектов: final поля объекта видны всем потокам без дополнительной синхронизации после завершения конструктора, потому что завершение конструктора HB любому доступу к объекту через корректно опубликованную ссылку.

🔹 StampedLock и оптимистичное чтение

StampedLock (Java 8+) предлагает три режима: запись, пессимистичное чтение, оптимистичное чтение. Оптимистичное чтение не берёт блокировку вообще — читает данные и потом валидирует что запись не произошла:

long stamp = lock.tryOptimisticRead();
int x = point.x;
int y = point.y;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
x = point.x;
y = point.y;
} finally {
lock.unlockRead(stamp);
}
}


validate — это volatile-read под капотом, устанавливающий happens-before с последней записью. Паттерн работает корректно именно из-за JMM семантики, а не "просто так".

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍103🔥3
💡 Collections.singletonList вместо new ArrayList<>()

Когда список точно из одного элемента — не создавай изменяемый:
//  Избыточно
List<String> roles = new ArrayList<>();
roles.add("ADMIN");
someMethod(roles);

// Лаконично и без лишней аллокации
someMethod(Collections.singletonList("ADMIN"));


singletonList возвращает неизменяемую обёртку вокруг одного объекта без внутреннего массива. Это дешевле по памяти и явно выражает намерение.

⚠️ Список иммутабельный, add() бросит UnsupportedOperationException.

✔️ Альтернатива в Java 9+: List.of("ADMIN") — то же самое, но более современный API.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍3🔥2
🔍 Чтение csv файла

Простая команда на случай, когда надо быстро и в удобном формате прочитать CSV-файл в терминале:
$ cat inventory.csv | column -t -s,


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

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍64🔥3
👑 Магия IntelliJ IDEA

Если используешь Ctrl + P (подсказка параметров метода), то вот ещё один полезный хот кей: Shift + Ctrl + I → быстрый просмотр определения.

🔹 Зачем это нужно

— Позволяет посмотреть реализацию метода/класса/интерфейса без перехода в другой файл.
— Работает с любыми символами: методами, переменными, константами, даже SQL-мэпперами в MyBatis.
— Незаменимо, если не хочешь терять контекст текущего кода.

🔹 Как использовать


— Наведи курсор на метод, поле или класс, нажми Ctrl + Shift + I — появится всплывающее окно с реализацией.
— Работает и в дебаге, и при просмотре внешних библиотек (если есть исходники).

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥63
💭 Apache Kafka: как не терять данные под нагрузкой

Классическая проблема растущего проекта: сервис уведомлений начинает захлёбываться. В пике прилетает 10к запросов в секунду, а он обрабатывает 3к. Остальные просто теряются.

Можно горизонтально масштабировать, но это не решает проблему архитектурно. А Kafka решает.

Идея простая: Producer пишет, Consumer читает в своём темпе. Никто никого не ждёт. Никто никого не роняет.

1️⃣ Producer: отправляем событие

@Service
@RequiredArgsConstructor
public class OrderService {

private final KafkaTemplate<String, OrderEvent> kafkaTemplate;

public void createOrder(Order order) {
orderRepository.save(order);

OrderEvent event = new OrderEvent(order.getId(), order.getUserId());
kafkaTemplate.send("order-created", order.getUserId().toString(), event);
// топик ключ партиции payload
}
}


Ключ партиции — важная деталь. Kafka гарантирует порядок сообщений внутри одной партиции. Если передаёшь userId как ключ — все события одного пользователя попадут в одну партицию и будут обработаны строго по порядку.

2️⃣ Consumer: читаем и обрабатываем

@Component
public class NotificationConsumer {

@KafkaListener(
topics = "order-created",
groupId = "notification-group",
concurrency = "3" // 3 потока = читаем 3 партиции параллельно
)
public void handle(OrderEvent event) {
notificationService.send(event.getUserId());
}
}


groupId определяет логическую группу потребителей. Kafka гарантирует: одно сообщение получит ровно один инстанс внутри группы. Хочешь, чтобы событие получили оба сервиса уведомлений и аналитики? Разные groupId и каждый читает топик независимо.

3️⃣ Партиции и масштабирование

order-created (3 партиции)
├── partition-0 → consumer-instance-1
├── partition-1 → consumer-instance-2
└── partition-2 → consumer-instance-3


Не хватает скорости обработки → поднимаешь ещё инстансов. Kafka сама перераспределит партиции. Но инстансов больше, чем партиций держать смысла нет, лишние будут просто простаивать.

4️⃣ Что делать, если Consumer упал

Kafka хранит сообщения на диске (по умолчанию 7 дней). Consumer сам трекает, до какого offset он дочитал.

spring:
kafka:
consumer:
auto-offset-reset: earliest # читать с начала, если offset не найден
enable-auto-commit: false # коммитим offset вручную — только после успешной обработки


enable-auto-commit: false — критически важная настройка. Если Consumer упал в середине обработки, он перечитает сообщения с последнего закоммиченного offset. При true — offset уже сдвинулся, сообщение потеряно.

5️⃣ Dead Letter Topic: что делать с ядовитыми сообщениями

Иногда одно сообщение падает раз за разом: битые данные, баг в логике. Consumer уходит в бесконечный retry и встаёт колом.

@Bean
public DefaultErrorHandler errorHandler(KafkaTemplate<String, Object> kafkaTemplate) {
var recoverer = new DeadLetterPublishingRecoverer(kafkaTemplate);
var backoff = new FixedBackOff(1000L, 3); // 3 попытки с паузой 1с
return new DefaultErrorHandler(recoverer, backoff);
}


После 3 неудачных попыток сообщение уедет в топик order-created.DLT. Основной поток не заблокирован, разбираешься с проблемой отдельно.

📌 Когда Kafka, а когда нет

Нужен ответ прямо сейчас → REST
Получатель может быть недоступен → Kafka
Один источник и много потребителей → Kafka
Аудит и история событий → Kafka
Простой CRUD без нагрузки → REST

Kafka не серебряная пуля. Она добавляет операционную сложность: нужно думать об idempotency, порядке сообщений, мониторинге lag у Consumer-групп. Но когда система начинает терять данные под нагрузкой, цена этой сложности оправдана.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍53
🔍 BeanDefinition Pipeline

Spring ApplicationContext при старте проходит через чётко определённую последовательность фаз. Понимание этой последовательности объясняет, почему одни расширения работают, а другие нет, и почему порядок объявления бинов иногда имеет значение.

Фазы загрузки контекста

1️⃣ Загрузка BeanDefinition'ов

Контекст читает конфигурацию и строит реестр BeanDefinition. На этом этапе объекты ещё не создаются. BeanDefinition — это метаданные: класс, scope, зависимости, lazy/eager, init/destroy методы.

2️⃣ BeanFactoryPostProcessor

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

Самый известный пример — PropertySourcesPlaceholderConfigurer. Он резолвит ${...} плейсхолдеры в BeanDefinition прямо на этом этапе. Именно поэтому @Value("${some.property}") работает — к моменту создания бина значение уже подставлено в метаданные.

ConfigurationClassPostProcessor — ещё один критический BeanFactoryPostProcessor. Он обрабатывает @Configuration классы, @ComponentScan, @Import, регистрирует дополнительные BeanDefinition. Без него Spring Boot не работал бы.

⚠️ BeanFactoryPostProcessor сам является бином, но Spring создаёт его раньше остальных отдельным путём. Если BeanFactoryPostProcessor зависит от другого бина через @Autowired — этот бин будет создан раньше срока, до фазы BeanPostProcessor. Это ведёт к тому что аспекты и другие пост-процессоры не применяются к нему. Spring даже выводит предупреждение в лог в таких случаях.

3️⃣ Создание экземпляра и инициализация

Для каждого eager singleton:
— Создание инстанса через конструктор или фабричный метод.
— Внедрение зависимостей (setter injection, field injection).
— Вызов BeanPostProcessor.postProcessBeforeInitialization.
— Вызов init-метода (@PostConstruct → InitializingBean.afterPropertiesSet() → initMethod)
— Вызов BeanPostProcessor.postProcessAfterInitialization.

BeanPostProcessor — именно здесь работает Spring AOP. AbstractAutoProxyCreator реализует BeanPostProcessor и в postProcessAfterInitialization оборачивает бин в прокси если нужно.

4️⃣ SmartInitializingSingleton

После инициализации всех singleton-бинов Spring вызывает afterSingletonsInstantiated() на каждом бине, реализующем этот интерфейс. Полезно, когда нужно выполнить логику гарантированно после того, как все синглтоны готовы — в отличие от @PostConstruct, который срабатывает до завершения инициализации остальных бинов.

5️⃣ Публикация события ContextRefreshedEvent

После того как все бины готовы. ApplicationListener на этот ивент — стандартный способ выполнить логику после полного старта контекста.

🔹 Порядок BeanPostProcessor'ов

Порядок определяется через Ordered или PriorityOrdered. PriorityOrdered применяется раньше Ordered, Ordered раньше остальных. Внутри одного уровня по getOrder().

Это важно когда несколько BeanPostProcessor обрабатывают один бин: AOP-прокси, валидация, кастомный мониторинг. Неправильный порядок может привести к тому что один пост-процессор получит прокси вместо оригинального объекта или наоборот.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🔥4🤔3🙏2
🖥 На GitHub есть отличный репозиторий

resources-learning-spring — там собраны лучшие материалы для изучения Spring от команды Spring Office Hours. Репозиторий вырос из подкаст-эпизода, где авторы делились любимыми ресурсами, и с тех пор активно пополняется сообществом.

🔹 Внутри найдёте всё по категориям: официальная документация Spring Boot и Spring Framework, книги, YouTube-каналы, подкасты, блоги, конференции и живые GitHub-репозитории с примерами.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍74🔥3
👑 Магия IntelliJ IDEA

IntelliJ IDEA может помочь с созданием тестов. С помощью Ctrl + Shift + T можно сгенерировать структуру тестов для классов и методов.

🔹 Зачем это нужно

— Вместо того, чтобы вручную писать шаблон тестов, IDEA сгенерирует базовую структуру тестов для классов или методов.
— Особенно полезно, когда проект растет, а тестов слишком много, чтобы каждый раз выдумывать тестовую структуру с нуля.

🔹 Как использовать

— Поместите курсор на класс или метод, для которого нужен тест.
— Нажмите Ctrl + Shift + T (на Windows/Linux) или Cmd + Shift + T (на macOS).
— Выберите, какой тестовый фреймворк использовать (JUnit, TestNG и т.д.), и IDEA предложит создать тестовый класс с нужной структурой.

IDE автоматически создаст тестовый класс с методами для проверки каждого (или выбранного) публичного метода.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥31
🧵 Просто о сложном: ThreadLocal

В многопоточном приложении потоки разделяют общую память. Это создаёт проблемы с состоянием. ThreadLocal решает это элегантно — каждый поток получает свою копию переменной.

🔹 Как устроено внутри

Никакой центральной Map на уровне JVM нет. Всё проще: у каждого объекта Thread есть поле threadLocals типа ThreadLocal.ThreadLocalMap. Это внутренняя Map, которая живёт внутри самого потока.

Когда ты вызываешь set() — значение кладётся в Map текущего потока, где ключ — сам объект ThreadLocal:
// упрощённо, внутри ThreadLocal.set(value)
Thread.currentThread().threadLocals.put(this, value);

get() — достаёт из Map того же потока. Другой поток обращается к своей Map и видит своё значение. Пересечений нет по конструкции.

🔹 Инициализация через withInitial

Если get() вызван, а set() ещё не было, возвращается null. Чтобы этого избежать, используют фабрику:
ThreadLocal<SimpleDateFormat> sdf =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

Лямбда вызовется один раз на поток, при первом get(). Дальше кешируется в той же threadLocals Map.

🔹 Что реально использует ThreadLocal в экосистеме

Spring держит через него всё, что должно быть доступно в рамках одного запроса без явной передачи через параметры:

→ TransactionSynchronizationManager — текущее соединение с БД, чтобы сервис и репозиторий в рамках @Transactional работали с одним коннектом
→ SecurityContextHolder — аутентифицированный пользователь
→ RequestContextHolder — текущий HttpServletRequest

Идея одна: вместо того чтобы тащить контекст через каждый метод, кладёшь его в ThreadLocal на входе в поток и достаёшь где нужно.

🔹 Где ломается

Thread Pool. Потоки переиспользуются и после обработки запроса поток возвращается в пул, но его threadLocals Map никуда не девается. Если не вызвать remove(), то следующий запрос, попавший в этот поток, найдёт чужие данные.

Это не гипотетическая проблема. Именно так утекают security-контексты и появляются баги, которые воспроизводятся только под нагрузкой.

Второй момент — утечка памяти. ThreadLocalMap использует WeakReference на ключ, но значение держится сильной ссылкой. Если поток живёт долго (а в пуле — фактически вечно) и remove() никто не вызвал — объект не будет собран GC, пока поток жив.

Поэтому паттерн всегда один:
try {
tl.set(value);
// работа
} finally {
tl.remove();
}


ThreadLocal решает конкретную задачу — изоляция состояния без синхронизации. Не серебряная пуля, не замена synchronized. Просто инструмент для случаев, когда данные должны жить в рамках потока, а не шариться между ними.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍74🔥2
🏭 Паттерн Factory Method

Factory Method — это порождающий паттерн, который предоставляет интерфейс для создания объектов в суперклассе, но позволяет подклассам изменять тип создаваемых объектов.

Использование

🔹 Когда тип создаваемого объекта зависит от контекста или конфигурации.
🔹 Когда нужно расширять систему новыми продуктами без правки существующего кода.
🔹 Когда создание объекта — это не просто new, а логика с условиями.

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

1️⃣ Ослабление связи между кодом и конкретными классами

При прямом вызове new вы жёстко привязаны к конкретному классу. Если его нужно заменить — придётся найти и исправить все точки создания. Фабрика убирает эту зависимость: код работает через интерфейс, конкретная реализация подставляется в одном месте.

2️⃣ Централизованное управление созданием объектов

Фабрика может содержать нетривиальную логику:

▪️ Выбор нужного класса в зависимости от условий.
▪️ Управление жизненным циклом объекта.
▪️ Применение Singleton или Object Pool внутри фабрики.

3️⃣ Расширяемость без правки существующего кода

Новый тип продукта — новый подкласс. Основной код не трогаете. Это и есть принцип Open/Closed в действии.

4️⃣ Упрощение тестирования

Фабрику можно подменить в тестах — подсунуть mock-реализацию. Это чище, чем патчить конструктор или использовать статические методы.

══════ Навигация ══════
ВакансииЗадачиСобесы

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍5👾2🔥1