Библиотека Java разработчика
前往频道在 Telegram
📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate. По всем вопросам @evgenycarter РКН clck.ru/3KoGeP
显示更多📈 Telegram 频道 Библиотека Java разработчика 的分析概览
频道 Библиотека Java разработчика (@bookjava) 俄语 语言赛道中的 是活跃参与者。目前社区聚集了 10 042 名订阅者,在 技术与应用 类别中位列第 11 640,并在 俄罗斯 地区排名第 62 744 位。
📊 受众指标与增长动态
自 невідомо 创建以来,项目保持高速增长,吸引了 10 042 名订阅者。
根据 05 十月, 2026 的最新数据,频道保持稳定运转。过去 30 天订阅人数变化为 -48,过去 24 小时变化为 -2,整体触达仍然可观。
- 认证状态: 未认证
- 互动率 (ER): 平均受众互动率为 8.21%。内容发布后 24 小时内通常能获得 3.65% 的反应,占订阅者总量。
- 帖子覆盖: 每篇帖子平均可获得 825 次浏览,首日通常累积 367 次浏览。
- 互动与反馈: 受众积极参与,单帖平均反应数为 5。
- 主题关注点: 内容集中在 string, интерфейс, строка, boot, api 等核心主题上。
📝 描述与内容策略
作者将该频道定位为表达主观观点的平台:
“📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate.
По всем вопросам @evgenycarter
РКН clck.ru/3KoGeP”
凭借高频更新(最新数据采集于 06 十月, 2026),频道始终保持新鲜度与高覆盖。分析显示受众积极互动,使其成为 技术与应用 类别中的关键影响点。
10 042
订阅者
-224 小时
-177 天
-4830 天
帖子存档
🧠 Dependency Inversion Principle (D в SOLID)
Зависимости должны идти от высокоуровневой политики к низкоуровневым деталям, а не наоборот. Абстракции — хозяева, реализации — обслуживающий персонал.
📌 Коротко о сути
* Модули верхнего уровня (бизнес-логика) зависят только от интерфейсов/абстракций.
* Модули нижнего уровня (база, сеть, файлы) также зависят от тех же абстракций.
* Сами абстракции не знают ничего о деталях, тем самым разрывая «бетонную» сцепку между слоями.
💡 Мини-пример (Java 17+, Spring Boot 3+)
// 1️⃣ Абстракция — контракт
public interface NotificationSender {
void send(Message msg);
}
// 2️⃣ Верхний уровень — бизнес-служба
@Service
public class BillingService {
private final NotificationSender sender;
public BillingService(NotificationSender sender) {
this.sender = sender; // зависим от контракта
}
public void bill(Client c) {
// ...
sender.send(new Message("Invoice #42"));
}
}
// 3️⃣ Низкий уровень — деталь
@Component
public class EmailSender implements NotificationSender {
public void send(Message msg) {
// SMTP-магия
}
}
BillingService может жить в модульном jar без spring-email-starter и SMTP-кода — протестировать его теперь элементарно.
⚠️ Где рождаются проблемы
1. Путаница DI container ≠ DIP
IoC/DI-фреймворк (Spring, CDI) — лишь удобный способ «сращивать» зависимости, но принцип работает и без контейнера (чистый constructor injection).
2. Абстракции ради галочки
Интерфейс OneImplService с единственной реализацией ломает читаемость, тесты и автоконфиг 📉.
➜ Создавай абстракцию, когда реально нужны сменяемость, тестируемость или расширяемость.
3. Утечки деталей
Если интерфейс таскает DTO из слоя хранения, ты всё ещё «протёк» к базе. Держи контракты чистыми.
4. Слепая вера в фреймворк
Жизненный цикл бинов, прокси, lazy-init — магия мешает понимать, кто кем владеет.
➜ Всегда можешь собрать объект вручную в юнит-тесте. Если сложно — запах нарушения DIP.
5. Слишком много уровней абстракций
«Контроллер → сервис → менеджер → порт → адаптер → репозиторий» превращает код в матрёшку. Дизайн важнее количества слоёв.
📝 Практические советы
* Используй constructor injection по умолчанию. Поле final = явная зависимость.
* Группируй интерфейсы по use-case, а не по технологии (например, TransferPort, а не JdbcTransferRepository).
* В тестах не мокай фреймворк — мокай контракт.
* Для одноразовых реализаций начни с class. Если появится второй вариант — быстро вынесешь интерфейс (IDE поможет).
* Проверка себя: можно ли запустить модуль верхнего уровня без нижнего? Если да — DIP соблюдён.
💬 Итог
DIP — это не про «везде интерфейсы» и не про «подключи Spring». Это про правильное направление зависимостей, которое делает код гибким, тестируемым и не заложником технологий. А проблемы возникают, когда путают инструмент с принципом и забывают, что абстракция должна прятать детали, а не выпячивать их.
📲 Мы в MAX
👉@BookJavaТри похожих слова в Java —
final, finally, finalize — но смысл у них совершенно разный. Разберёмся 🧠
🔒 final
Ключевое слово. Используется для ограничений:
* final class — нельзя наследовать.
* final method — нельзя переопределить.
* final variable — нельзя изменить значение (один раз присвоил — всё).
📌 Особенно важно для immutability и thread-safety.
final int x = 10;
x = 20; // ошибка компиляции
🧯 finally
Блок в конструкции try-catch-finally. Выполняется всегда, даже если был return или exception.
💡 Используется для освобождения ресурсов: закрытия потоков, соединений и т.д.
try {
// что-то может выбросить исключение
} catch (Exception e) {
// обработка ошибки
} finally {
// всегда выполнится
}
⚰️ finalize()
Метод из Object, вызывался перед удалением объекта сборщиком мусора.
⚠️ УСТАРЕЛ с Java 9, удалён в Java 18. Не используй.
🔪 Непредсказуем, плохо работает, тормозит GC. Вместо него — AutoCloseable и try-with-resources.
@Override
protected void finalize() throws Throwable {
System.out.println("До свидания...");
}
🧠 Важно не путать:
* final — про нельзя менять
* finally — про всегда выполнится
* finalize — про устаревший и бесполезный метод
📲 Мы в MAX
👉@BookJava🧠 Ленивая инициализация через
Supplier — элегантная альтернатива double-checked locking
Когда нужно отложить создание тяжёлого объекта до первого обращения, многие вспоминают double-checked locking:
private volatile SomeHeavyObject obj;
public SomeHeavyObject getObj() {
if (obj == null) {
synchronized (this) {
if (obj == null) {
obj = new SomeHeavyObject();
}
}
}
return obj;
}
⚠️ Многословно, хрупко, легко ошибиться. Есть лучше.
📌 Современный подход — использовать Supplier с ленивой инициализацией:
private final Supplier<SomeHeavyObject> lazyObj = Suppliers.memoize(SomeHeavyObject::new);
public SomeHeavyObject getObj() {
return lazyObj.get();
}
💡 Suppliers.memoize — из Guava. Он гарантирует потокобезопасную инициализацию один раз при первом вызове get().
Плюсы:
— Читается за секунду
— Потокобезопасно
— Нет дублирования кода
— Легко тестировать и заменять
🔁 Альтернатива в чистой Java: использовать AtomicReference и updateAndGet, но это уже длиннее и менее выразительно.
Если используешь Spring — можно просто обернуть бин в @Lazy. Но вне Spring, в обычных Java-приложениях или утилитах — Supplier с memoize() идеален.
📲 Мы в MAX
👉@BookJavaЧто такое механизм try-with-resources?
Механизм
try-with-resources в Java — это конструкция, которая упрощает работу с ресурсами, требующими закрытия (например, файлы, сокеты, соединения с БД и т.д.). Он автоматически закрывает ресурсы после завершения блока try, избавляя от необходимости писать finally вручную.
📌 Поддерживается с Java 7
📌 Ресурсы должны реализовывать интерфейс AutoCloseable
💡 Пример:
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
// reader будет закрыт автоматически, даже если произойдёт исключение
🧠 Почему это важно:
* Уменьшает boilerplate-код
* Исключает утечки ресурсов
* Упрощает обработку исключений
⚠️ Совет:
С Java 9 можно использовать уже объявленные переменные, если они final или effectively final:
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (reader) {
System.out.println(reader.readLine());
}
📲 Мы в MAX
👉@BookJava📌 picocli — это современная библиотека для создания CLI-приложений на Java. Она упрощает разработку командных интерфейсов, обеспечивая:
* Автоматическую генерацию
--help и --version
* Поддержку подкоманд (как в git commit, git push)
* Аргументы, параметры, опции с короткими и длинными флагами (-v, --verbose)
* Интеграцию с GraalVM (подходит для нативной компиляции)
* Поддержку аннотаций (аннотируй POJO — и готово!)
* Автоматическую валидацию аргументов
* Цветной вывод и гибкое форматирование
* Интерактивный режим и автодополнение
Проект активно развивается, полностью документирован и используется в сотнях продакшн-проектов. Если ты ищешь мощную и простую в использовании CLI-библиотеку на Java — picocli отличный выбор.
https://github.com/remkop/picocli
📲 Мы в MAX
👉@BookJavaDouble-brace инициализация в Java — это идиома, которая используется для инициализации коллекций (и иногда других объектов) в краткой форме. Она выглядит как две открывающие фигурные скобки подряд
{{ и имеет специфическое поведение. Пример:
import java.util.*;
List<String> list = new ArrayList<String>() {{
add("one");
add("two");
add("three");
}};
Как это работает:
Double-brace инициализация — это комбинация двух конструкций:
1. Анонимный внутренний класс:
new ArrayList<String>() { ... }
Создаётся новый безымянный подкласс ArrayList.
2. Инициализатор экземпляра:
{{ ... }}
Это блок, который выполняется при создании объекта. В него можно вставлять вызовы методов (например, add()).
Преимущества:
* Компактный и удобочитаемый синтаксис для заполнения коллекций.
* Можно использовать в полях final, например:
private static final Set<String> set = new HashSet<>() {{
add("A");
add("B");
}};
Недостатки:
1. Создаётся лишний анонимный класс — это увеличивает количество байткода и может мешать сериализации.
2. Утечки памяти — если такой класс находится внутри внешнего класса, он может неявно хранить ссылку на него.
3. Читаемость — не все разработчики знают, как это работает, и это может сбивать с толку.
4. Нарушение принципов OOP — логика инициализации размещается в конструкторе, который не явно виден.
Альтернативы:
Java 8+ (через Stream и Collectors):
List<String> list = Stream.of("one", "two", "three")
.collect(Collectors.toList());
Статический метод инициализации:
public static List<String> createList() {
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
return list;
}
Java 9+ (immutable):
List<String> list = List.of("one", "two", "three");
Set<String> set = Set.of("A", "B");
Вывод:
Double-brace инициализация — это удобный, но потенциально опасный трюк, который не рекомендуется использовать в продакшене. Лучше предпочесть более читаемые и безопасные альтернативы, особенно с учётом новых возможностей Java 8+.
📲 Мы в MAX
👉@BookJava🧠
@Value vs @ConfigurationProperties — не выбирай наобум
Оба способа хороши для конфигурации, но используют их по-разному. И если ты всё ещё везде пихаешь @Value, держи краткий гайд, когда лучше что:
📌 @Value — просто, но не гибко:
@Value("${my.prop}")
private String value;
✅ Хорошо для единичных значений
❌ Плохо для сложных структур, списков, валидации
❌ Трудно покрыть тестами (без TestPropertySource)
❌ Нет биндинга по префиксу → нет группировки
📌 @ConfigurationProperties — сила и масштаб:
@ConfigurationProperties(prefix = "app.feature")
public class FeatureProperties {
private boolean enabled;
private List<String> items;
}
💡 Используй с @EnableConfigurationProperties или аннотируй как @Component
✅ Удобно группировать и документировать
✅ Работает с вложенными структурами, коллекциями
✅ Поддерживает JSR-303 валидацию (@Validated)
✅ Легче мокать в тестах
✅ Интеграция с Spring Boot Actuator (/actuator/configprops)
⚠️ Не смешивай: не нужно тянуть @Value внутрь @ConfigurationProperties — это антипаттерн.
Если конфигурация простая — @Value норм. Но как только появляется структура, коллекции, логика — всегда используй @ConfigurationProperties.
📲 Мы в MAX
👉@BookJava🧠 Трюк с
@EventListener в Spring — убираем лишний @TransactionalEventListener
Когда тебе нужно обрабатывать события в рамках транзакции, мы часто пишем:
@TransactionalEventListener
public void handleEvent(MyEvent event) {
// ...
}
⚠️ Но есть нюанс: @TransactionalEventListener по умолчанию срабатывает после коммита. Иногда это не очевидно и вызывает баги, особенно если ожидаешь, что событие обработается внутри транзакции.
📌 Альтернатива: обычный @EventListener, но вместе с TransactionSynchronizationManager.
💡 Сниппет:
@Component
public class MyEventHandler {
@EventListener
public void handle(MyEvent event) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// обработка события после коммита
}
}
);
} else {
// fallback: нет активной транзакции — выполняем сразу
}
}
}
📎 Плюсы:
— Лучше контроль: ты сам решаешь, когда обрабатывать (до/после/вне транзакции)
— Можно централизовать поведение через utility-метод
— Гибкость: логика обработки не зависит от аннотаций Spring'а
⚠️ Минус: чуть больше кода, но понятнее поведение.
📲 Мы в MAX
👉@BookJava👩💻 HTTP-сервер на чистой Java за 30 минут
Приглашаем на открытый урок.
🗓 21 сентября в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java разработчик. Продвинутый уровень».
Разберем, как браузер общается с сервером, и создадим работающий HTTP-сервис без Spring и сторонних библиотек.
О чем поговорим:
✔️ Как устроен HTTP-запрос
✔️ Создадим сервер на Java
✔️ Добавим несколько адресов
✔️ Откроем результат в браузере
✔️ Разберём, что скрывает Spring
🔗 Ссылка на регистрацию: https://vk.cc/d1sLwn
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
🧠 Как не словить
LazyInitializationException в Spring Boot + Hibernate
Одна из самых частых ошибок при работе с JPA:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection📌 Причина: лениво загружаемая коллекция (
LAZY) обращается к БД вне транзакции — например, в слое контроллера или после закрытия Session.
💡 Как избежать?
✅ Решение 1: @Transactional в сервисе
Убедитесь, что вы обращаетесь к ленивым коллекциям внутри метода с @Transactional:
@Transactional
public UserDto getUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow();
// OK: коллекция friends будет инициализирована в транзакции
return new UserDto(user.getName(), user.getFriends());
}
⚠️ Не используйте @Transactional в контроллерах - это плохая практика.
✅ Решение 2: Fetch Join
Подгрузите нужные данные сразу через JOIN FETCH:
@Query("SELECT u FROM User u LEFT JOIN FETCH u.friends WHERE u.id = :id")
Optional<User> findByIdWithFriends(@Param("id") Long id);
📌 Плюс: 1 запрос вместо N (N+1 проблема решается).
📌 Минус: может быть избыточная загрузка, особенно с большими коллекциями.
✅ Решение 3: DTO проекция
Лучший способ в большинстве случаев - проецировать сразу в DTO:
@Query("""
SELECT new com.example.UserDto(u.name, f.name)
FROM User u
LEFT JOIN u.friends f
WHERE u.id = :id
""")
List<UserDto> findUserWithFriendNames(@Param("id") Long id);
📌 Выгружает только нужные данные. Быстро, безопасно, эффективно.
Ленивая инициализация - ок, если вы контролируете границы транзакций.
Проекции и fetch join - ваши лучшие друзья, если нужен контроль и производительность.
📲 Мы в MAX
👉@BookJava🧠 Spring Boot: ленивые зависимости через
ObjectProvider
Иногда сервису не нужно всегда инжектить другую зависимость при старте — только иногда по ходу работы. Но @Autowired всё равно тянет её сразу, даже если она вам пока не нужна. Это бьёт по времени старта и может вызвать циклические зависимости.
💡 Решение: использовать ObjectProvider<T>.
Пример:
@Service
public class NotificationService {
private final ObjectProvider<EmailSender> emailSenderProvider;
public NotificationService(ObjectProvider<EmailSender> emailSenderProvider) {
this.emailSenderProvider = emailSenderProvider;
}
public void sendEmailIfEnabled(String to, String body) {
if (featureEnabled()) {
EmailSender sender = emailSenderProvider.getIfAvailable();
if (sender != null) {
sender.send(to, body);
}
}
}
}
📌 ObjectProvider:
▫️не создаёт бин сразу — ленивый доступ;
▫️ позволяет проверить наличие бина (getIfAvailable() / ifAvailable(...));
▫️можно использовать stream() — для коллекций бинов.
⚠️ Это не альтернатива DI. Это способ контролировать создание и использование бинов вручную, когда это действительно нужно.
📈 Отлично помогает:
▫️при борьбе с циклическими зависимостями;
▫️для optional-бинов;
▫️чтобы ускорить старт приложения.
📲 Мы в MAX
👉@BookJava💡 Чем опасен
@Scheduled(fixedRate) без @Transactional?
Расписание в Spring через @Scheduled — удобный способ запускать задачи по таймеру. Но часто разработчики забывают про транзакции, особенно с fixedRate, и попадают в ловушку.
📌 Пример проблемы:
@Scheduled(fixedRate = 10_000)
public void cleanUp() {
List<Job> jobs = jobRepository.findAllByStatus(PENDING);
jobs.forEach(job -> {
job.setStatus(PROCESSING);
jobRepository.save(job);
});
}
🧨 Каждые 10 секунд метод запускается заново. Если выполнение предыдущего ещё не закончено, начнётся второй поток, который заберёт те же PENDING -записи.
В итоге — дублирование обработки, гонки, повреждение данных.
📉 Особенно критично при долгих задачах или высокой нагрузке.
✅ Решение — обернуть метод в транзакцию + использовать блокировки:
@Transactional
@Scheduled(fixedRate = 10_000)
public void cleanUp() {
List<Job> jobs = jobRepository.findAllByStatusForUpdate(PENDING); // SELECT ... FOR UPDATE
jobs.forEach(job -> {
job.setStatus(PROCESSING);
jobRepository.save(job);
});
}
📌 Или добавить флаг “locked”, чтобы явно помечать взятые задачи.
💡 Лучше использовать @Scheduled(fixedDelay) — он ждёт завершения предыдущего запуска. Это безопаснее по умолчанию.
🧠 Подумайте о том, чтобы заменить @Scheduled на:
* Spring Batch (если сложные джобы)
* Spring Integration / Flowable / Camunda (если нужны гарантии и retry)
* Quartz (если нужен контроль и очереди)
📲 Мы в MAX
👉@BookJava🔴 Завтра тестовое собеседование с Java-разработчиком
2 сентября(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
🔧 Maven vs. Gradle: что выбрать разработчику?
Когда речь заходит о сборке Java-проектов, выбор обычно падает на два главных инструмента: Maven и Gradle. Оба давно стали стандартом индустрии, но каждый имеет свои особенности. Разберёмся, что выбрать 👇
☕ Maven — проверенная классика
✅ Строгая структура: легче читать и сопровождать
✅ Надёжность и предсказуемость сборки
✅ Большое комьюнити и множество плагинов
⚠️ XML-конфигурация громоздкая
⚠️ Медленнее по сравнению с Gradle
⚡ Gradle — гибкость и скорость
✅ Поддержка Kotlin и Groovy DSL
✅ Инкрементальные сборки и кэширование → быстрее
✅ Более гибкий подход к настройке
⚠️ Порог входа выше
⚠️ Иногда сложно отлаживать конфигурацию
💡 Вывод:
* 🔹 Выбирай Maven, если важны стабильность, простота и читаемость.
* 🔹 Выбирай Gradle, если хочешь максимум производительности и гибкости.
🎯 В крупных проектах Gradle становится всё популярнее, особенно при использовании Kotlin. Но в enterprise-среде Maven по-прежнему правит бал.
📲 Мы в MAX
👉@BookJava
🧠 Трюк с
@Configuration и @ComponentScan: как не словить баг при миграции на Spring Boot 3+
Когда вы выносите конфигурацию в отдельный модуль или создаёте библиотеку с @Configuration-классами — не забывайте:
📌 Spring Boot 3+ по умолчанию НЕ сканирует пакеты вне стартового (main).
Пример:
// Внутри библиотеки
@Configuration
public class MyLibConfig {
@Bean
public MyService myService() {
return new MyService();
}
}
И вы такие:
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
🔥 Но MyService не создаётся! Почему?
💡 Потому что @ComponentScan по умолчанию сканирует ТОЛЬКО package текущего класса и ниже.
📌 Решения:
1. Ручной импорт конфигурации:
@SpringBootApplication
@Import(MyLibConfig.class)
public class App { ... }
2. Сделать конфигурацию @AutoConfiguration и подключить через spring.factories или META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports — актуально для библиотек.
3. Переместить MyLibConfig в подпакет стартового класса (не всегда возможно или удобно).
⚠️ Часто баг проявляется неявно: контекст стартует, но бины "теряются", и вы получаете NoSuchBeanDefinitionException в рантайме.
✅ После миграции на Spring Boot 3+ обязательно проверьте, что нужные конфигурации действительно подхватываются. Особенно, если раньше они подключались "магически".
📲 Мы в MAX
👉@BookJava🧠 Неочевидный performance-трюк с
@Transactional(readOnly = true)
Многие используют @Transactional(readOnly = true) просто по инерции. Но вы знали, что в Spring это влияет не только на семантику, но и на производительность?
📌 Что делает readOnly = true:
* Подсказывает Hibernate, что внутри транзакции не будет изменений сущностей.
* Это позволяет избежать затрат на грязную проверку (dirty checking).
* Не создаётся snapshot состояний сущностей → меньше памяти и операций.
💡 Пример:
@Transactional(readOnly = true)
public List<User> findActiveUsers() {
return userRepository.findByActiveTrue();
}
⚠️ А теперь важно: если вы случайно измените сущность в таком методе, Hibernate проигнорирует изменения — потому что readOnly намекает: "не трогай".
📉 В реальном приложении с большим количеством запросов к БД, особенно читающих, такой подход даёт ощутимый буст — меньше нагрузки на ORM, меньше GC, быстрее ответы.
📌 Где применять:
* Методы только для чтения.
* REST-эндпоинты GET.
* Сервис-методы, возвращающие DTO и не модифицирующие Entity.
⚠️ Где не надо:
* Методы с lazy-loading и последующими модификациями.
* Там, где возможно случайное изменение Entity (например, через mapper'ы).
👉 Используйте @Transactional(readOnly = true) не как декор, а как инструмент для оптимизации.
📲 Мы в MAX
👉@BookJava🧠 Spring Boot: правильно логируем
@ExceptionHandler
Сейчас покажу простой, но часто упускаемый момент при обработке ошибок в Spring Boot.
Если у вас есть глобальный @ExceptionHandler, убедитесь, что вы не теряете stacktrace при логировании.
❌ Плохо:
@ExceptionHandler(MyException.class)
public ResponseEntity<String> handle(MyException ex) {
log.error("MyException occurred: {}", ex.getMessage()); // stacktrace теряется!
return ResponseEntity.status(500).body("Error");
}
✅ Хорошо:
@ExceptionHandler(MyException.class)
public ResponseEntity<String> handle(MyException ex) {
log.error("MyException occurred", ex); // stacktrace будет видно в логе
return ResponseEntity.status(500).body("Error");
}
📌 log.error(String, Throwable) — правильный способ логировать исключения. Это позволяет:
* Сохранять stacktrace для дебага;
* Не терять вложенные причины (getCause());
* Работать с лог-агрегаторами (ELK, Grafana, etc).
💡 Если вы используете Slf4j и формат log.error("message: {}", ex.getMessage()), вы теряете почти всю полезную информацию об ошибке.
⚠️ И не забывайте: глобальные хендлеры — это хорошо, но не глушите все ошибки без разбора. Лучше создавать разные @ExceptionHandler под каждую категорию исключений.
📲 Мы в MAX
👉@BookJava🧠 Spring Boot: как НЕ попасть в ловушку с
@Scheduled и многопоточностью
Когда используете @Scheduled для периодических задач в Spring Boot, важно понимать: по умолчанию все задачи выполняются в одном потоке.
@Scheduled(fixedRate = 5000)
public void syncData() {
// долгая операция
}
📌 Если таких задач несколько или они работают долго — остальные ждут. Это создаёт бутылочное горлышко и приводит к неожиданным задержкам.
💡 Решение: настроить кастомный executor:
@Configuration
@EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5); // количество параллельных задач
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.initialize();
registrar.setTaskScheduler(scheduler);
}
}
Теперь все @Scheduled - методы будут использовать пул потоков, а не один.
⚠️ Не забывайте: если задача критична к ресурсам или зависит от внешних сервисов — добавьте внутреннюю защиту от повторного выполнения, например, с помощью Redis lock или базы.
✅ Подключение пула — must-have для production-проектов, где @Scheduled выполняет реальные задачи, а не просто println.
📲 Мы в MAX
👉@BookJava🚀 Подборка полезных IT каналов в Max
Системное администрирование, DevOps 📌
https://max.ru/i_odmin Все для системного администратора
https://max.ru/bash_srv Bash Советы
https://max.ru/sysadminof Книги для админов, полезные материалы
https://max.ru/i_odmin_book Библиотека Системного Администратора
https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др.
https://max.ru/tipsysdmin Типичный Сисадмин
https://max.ru/channel_win_sysadmin Системный Администратор Windows
https://max.ru/channel_linux_admin Linux: Системный администратор
https://max.ru/channel_linuxmod Linux
https://max.ru/channel_i_linux Системный администратор
https://max.ru/channel_devopslib DevOps, SRE, Sysadmin
https://max.ru/channel_devops_star DevOps Star (Звезда Девопса)
Excel лайфхак 📌
https://t.me/Excel_lifehack Excel лайфхак
Английский с нуля 🇬🇧
https://max.ru/UchuEnglish
1C разработка 📌
https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С
https://max.ru/channel_DevLab1C 1С:Предприятие 8
Программирование C++📌
https://max.ru/cpp_lib Библиотека C/C++ разработчика
https://max.ru/channel_cpp_geek C++ geek
Программирование Go📌
https://max.ru/golang_lib Библиотека Go (Golang) разработчика
Программирование React📌
https://max.ru/react_lib React
Программирование Rust📌
https://max.ru/channel_rust_lib
Программирование Python 📌
https://max.ru/python_of Python академия.
https://max.ru/BookPython Библиотека Python разработчика
Java разработка 📌
https://max.ru/bookjava Библиотека Java разработчика
https://max.ru/channel_java_geek Java Geek
GitHub Сообщество 📌
https://max.ru/githublib Интересное из GitHub
Базы данных (Data Base) 📌
https://max.ru/database_info Все про базы данных
Фронтенд разработка 📌
https://max.ru/frontend_1 Подборки для frontend разработчиков
Библиотеки 📌
https://max.ru/programmist_of Книги по программированию
https://max.ru/proglb Библиотека программиста
https://max.ru/bfbook Книги для программистов
Программирование 📌
https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций
https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT
https://max.ru/php_lib Библиотека PHP программиста 👨🏼💻👩💻
Шутки программистов 📌
https://max.ru/itumor Шутки программистов
Защита, взлом, безопасность 📌
https://max.ru/thehaking Канал о кибербезопасности
https://max.ru/xakkep_1 Хакер Free
Книги, статьи для дизайнеров 📌
https://max.ru/odesigners Статьи, книги для дизайнеров
Математика 📌
https://max.ru/Pomatematike Канал по математике
https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике
Вакансии в IT📌
https://max.ru/progjob
https://max.ru/channel_rabotait
Мир технологий 📌
https://max.ru/mir_teh Канал для любознательных
Городские📌
https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга
https://max.ru/mockva_life Свежие новости Москвы
https://max.ru/piterspb Питер Новости: Санкт-Петербург / СПБ / ДТП
https://max.ru/channel_krasnodar_novosty Краснодар Новости
https://max.ru/channel_novosibirsk_novosti Новосибирск
https://max.ru/channel_samara_novosti Новости Самары
https://max.ru/channel_ekaterinburg_novosti Новости Екатеринбурга
https://max.ru/channel_kazan_novosti Новости Казани
https://max.ru/channel_omsk_novosti Новости Омска
https://max.ru/channel_moskva_24 Москва 24
