Когда список точно из одного элемента — не создавай изменяемый:
// ❌ Избыточно
List<String> roles = new ArrayList<>();
roles.add("ADMIN");
someMethod(roles);
// ✅ Лаконично и без лишней аллокации
someMethod(Collections.singletonList("ADMIN"));
singletonList возвращает неизменяемую обёртку вокруг одного объекта без внутреннего массива. Это дешевле по памяти и явно выражает намерение.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍3🔥2
Простая команда на случай, когда надо быстро и в удобном формате прочитать CSV-файл в терминале:
$ cat inventory.csv | column -t -s,
Флаг
-s указывает на использование запятых в качестве разделителей, а -t форматирует выходные данные в чистую таблицу.══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤4🔥3
Если используешь Ctrl + P (подсказка параметров метода), то вот ещё один полезный хот кей: Shift + Ctrl + I → быстрый просмотр определения.
🔹 Зачем это нужно
— Позволяет посмотреть реализацию метода/класса/интерфейса без перехода в другой файл.
— Работает с любыми символами: методами, переменными, константами, даже SQL-мэпперами в MyBatis.
— Незаменимо, если не хочешь терять контекст текущего кода.
🔹 Как использовать
— Наведи курсор на метод, поле или класс, нажми Ctrl + Shift + I — появится всплывающее окно с реализацией.
— Работает и в дебаге, и при просмотре внешних библиотек (если есть исходники).
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥6❤3
Классическая проблема растущего проекта: сервис уведомлений начинает захлёбываться. В пике прилетает 10к запросов в секунду, а он обрабатывает 3к. Остальные просто теряются.
Можно горизонтально масштабировать, но это не решает проблему архитектурно. А Kafka решает.
Идея простая: Producer пишет, Consumer читает в своём темпе. Никто никого не ждёт. Никто никого не роняет.
@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 как ключ — все события одного пользователя попадут в одну партицию и будут обработаны строго по порядку.@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 и каждый читает топик независимо.
order-created (3 партиции)
├── partition-0 → consumer-instance-1
├── partition-1 → consumer-instance-2
└── partition-2 → consumer-instance-3
Не хватает скорости обработки → поднимаешь ещё инстансов. Kafka сама перераспределит партиции. Но инстансов больше, чем партиций держать смысла нет, лишние будут просто простаивать.
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 уже сдвинулся, сообщение потеряно.
Иногда одно сообщение падает раз за разом: битые данные, баг в логике. 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. Основной поток не заблокирован, разбираешься с проблемой отдельно.Нужен ответ прямо сейчас → REST
Получатель может быть недоступен → Kafka
Один источник и много потребителей → Kafka
Аудит и история событий → Kafka
Простой CRUD без нагрузки → REST
Kafka не серебряная пуля. Она добавляет операционную сложность: нужно думать об idempotency, порядке сообщений, мониторинге lag у Consumer-групп. Но когда система начинает терять данные под нагрузкой, цена этой сложности оправдана.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍5❤3
Spring ApplicationContext при старте проходит через чётко определённую последовательность фаз. Понимание этой последовательности объясняет, почему одни расширения работают, а другие нет, и почему порядок объявления бинов иногда имеет значение.
Фазы загрузки контекста
Контекст читает конфигурацию и строит реестр BeanDefinition. На этом этапе объекты ещё не создаются. BeanDefinition — это метаданные: класс, scope, зависимости, lazy/eager, init/destroy методы.
После того как все BeanDefinition загружены, но до создания каких-либо бинов, вызываются BeanFactoryPostProcessor. Они получают доступ к ConfigurableListableBeanFactory и могут модифицировать, добавлять или удалять BeanDefinition.
Самый известный пример — PropertySourcesPlaceholderConfigurer. Он резолвит ${...} плейсхолдеры в BeanDefinition прямо на этом этапе. Именно поэтому @Value("${some.property}") работает — к моменту создания бина значение уже подставлено в метаданные.
ConfigurationClassPostProcessor — ещё один критический BeanFactoryPostProcessor. Он обрабатывает @Configuration классы, @ComponentScan, @Import, регистрирует дополнительные BeanDefinition. Без него Spring Boot не работал бы.
Для каждого eager singleton:
— Создание инстанса через конструктор или фабричный метод.
— Внедрение зависимостей (setter injection, field injection).
— Вызов BeanPostProcessor.postProcessBeforeInitialization.
— Вызов init-метода (@PostConstruct → InitializingBean.afterPropertiesSet() → initMethod)
— Вызов BeanPostProcessor.postProcessAfterInitialization.
BeanPostProcessor — именно здесь работает Spring AOP. AbstractAutoProxyCreator реализует BeanPostProcessor и в postProcessAfterInitialization оборачивает бин в прокси если нужно.
После инициализации всех singleton-бинов Spring вызывает afterSingletonsInstantiated() на каждом бине, реализующем этот интерфейс. Полезно, когда нужно выполнить логику гарантированно после того, как все синглтоны готовы — в отличие от @PostConstruct, который срабатывает до завершения инициализации остальных бинов.
После того как все бины готовы. 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
resources-learning-spring — там собраны лучшие материалы для изучения Spring от команды Spring Office Hours. Репозиторий вырос из подкаст-эпизода, где авторы делились любимыми ресурсами, и с тех пор активно пополняется сообществом.
🔹 Внутри найдёте всё по категориям: официальная документация Spring Boot и Spring Framework, книги, YouTube-каналы, подкасты, блоги, конференции и живые GitHub-репозитории с примерами.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤4🔥3
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🔥3❤1
🧵 Просто о сложном: ThreadLocal
В многопоточном приложении потоки разделяют общую память. Это создаёт проблемы с состоянием. ThreadLocal решает это элегантно — каждый поток получает свою копию переменной.
🔹 Как устроено внутри
Никакой центральной Map на уровне JVM нет. Всё проще: у каждого объекта Thread есть поле threadLocals типа ThreadLocal.ThreadLocalMap. Это внутренняя Map, которая живёт внутри самого потока.
Когда ты вызываешь set() — значение кладётся в Map текущего потока, где ключ — сам объект ThreadLocal:
get() — достаёт из Map того же потока. Другой поток обращается к своей Map и видит своё значение. Пересечений нет по конструкции.
🔹 Инициализация через withInitial
Если get() вызван, а set() ещё не было, возвращается null. Чтобы этого избежать, используют фабрику:
Лямбда вызовется один раз на поток, при первом get(). Дальше кешируется в той же threadLocals Map.
🔹 Что реально использует ThreadLocal в экосистеме
Spring держит через него всё, что должно быть доступно в рамках одного запроса без явной передачи через параметры:
→ TransactionSynchronizationManager — текущее соединение с БД, чтобы сервис и репозиторий в рамках @Transactional работали с одним коннектом
→ SecurityContextHolder — аутентифицированный пользователь
→ RequestContextHolder — текущий HttpServletRequest
Идея одна: вместо того чтобы тащить контекст через каждый метод, кладёшь его в ThreadLocal на входе в поток и достаёшь где нужно.
🔹 Где ломается
Thread Pool. Потоки переиспользуются и после обработки запроса поток возвращается в пул, но его threadLocals Map никуда не девается. Если не вызвать remove(), то следующий запрос, попавший в этот поток, найдёт чужие данные.
Это не гипотетическая проблема. Именно так утекают security-контексты и появляются баги, которые воспроизводятся только под нагрузкой.
Второй момент — утечка памяти. ThreadLocalMap использует WeakReference на ключ, но значение держится сильной ссылкой. Если поток живёт долго (а в пуле — фактически вечно) и remove() никто не вызвал — объект не будет собран GC, пока поток жив.
Поэтому паттерн всегда один:
ThreadLocal решает конкретную задачу — изоляция состояния без синхронизации. Не серебряная пуля, не замена synchronized. Просто инструмент для случаев, когда данные должны жить в рамках потока, а не шариться между ними.
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava
В многопоточном приложении потоки разделяют общую память. Это создаёт проблемы с состоянием. 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
👍7❤4🔥2
🏭 Паттерн Factory Method
Factory Method — это порождающий паттерн, который предоставляет интерфейс для создания объектов в суперклассе, но позволяет подклассам изменять тип создаваемых объектов.
Использование
🔹 Когда тип создаваемого объекта зависит от контекста или конфигурации.
🔹 Когда нужно расширять систему новыми продуктами без правки существующего кода.
🔹 Когда создание объекта — это не просто new, а логика с условиями.
Преимущества
1️⃣ Ослабление связи между кодом и конкретными классами
При прямом вызове new вы жёстко привязаны к конкретному классу. Если его нужно заменить — придётся найти и исправить все точки создания. Фабрика убирает эту зависимость: код работает через интерфейс, конкретная реализация подставляется в одном месте.
2️⃣ Централизованное управление созданием объектов
Фабрика может содержать нетривиальную логику:
▪️ Выбор нужного класса в зависимости от условий.
▪️ Управление жизненным циклом объекта.
▪️ Применение Singleton или Object Pool внутри фабрики.
3️⃣ Расширяемость без правки существующего кода
Новый тип продукта — новый подкласс. Основной код не трогаете. Это и есть принцип Open/Closed в действии.
4️⃣ Упрощение тестирования
Фабрику можно подменить в тестах — подсунуть mock-реализацию. Это чище, чем патчить конструктор или использовать статические методы.
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava
Factory Method — это порождающий паттерн, который предоставляет интерфейс для создания объектов в суперклассе, но позволяет подклассам изменять тип создаваемых объектов.
Использование
🔹 Когда тип создаваемого объекта зависит от контекста или конфигурации.
🔹 Когда нужно расширять систему новыми продуктами без правки существующего кода.
🔹 Когда создание объекта — это не просто new, а логика с условиями.
Преимущества
При прямом вызове new вы жёстко привязаны к конкретному классу. Если его нужно заменить — придётся найти и исправить все точки создания. Фабрика убирает эту зависимость: код работает через интерфейс, конкретная реализация подставляется в одном месте.
Фабрика может содержать нетривиальную логику:
▪️ Выбор нужного класса в зависимости от условий.
▪️ Управление жизненным циклом объекта.
▪️ Применение Singleton или Object Pool внутри фабрики.
Новый тип продукта — новый подкласс. Основной код не трогаете. Это и есть принцип Open/Closed в действии.
Фабрику можно подменить в тестах — подсунуть mock-реализацию. Это чище, чем патчить конструктор или использовать статические методы.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍5👾2🔥1
Многие из нас знают паттерны «по названиям». Но можешь ли объяснить, чем AbstractFactory отличается от FactoryMethod? Или когда Prototype — не оверинжиниринг, а реальное решение?
На Хабре вышла первая часть добротного разбора порождающих паттернов с примерами. Без воды, с живыми примерами на Kafka vs RabbitMQ, Spring @Component, Lombok @Builder.
👉 Читать на Хабре
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍5🔥1