Библиотека Java разработчика
📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate. По всем вопросам @evgenycarter РКН clck.ru/3KoGeP
إظهار المزيد📈 نظرة تحليلية على قناة تيليجرام Библиотека Java разработчика
تُعد قناة Библиотека Java разработчика (@bookjava) في القطاع اللغوي الروسية لاعباً نشطاً. يضم المجتمع حالياً 10 121 مشتركاً، محتلاً المرتبة 11 760 في فئة التكنولوجيات والتطبيقات والمرتبة 63 248 في منطقة روسيا.
📊 مؤشرات الجمهور والحراك
منذ تأسيسه في невідомо، حقق المشروع نمواً سريعاً وجمع 10 121 مشتركاً.
بحسب آخر البيانات بتاريخ 25 أغسطس, 2026، تحافظ القناة على نشاط مستقر. خلال آخر 30 يوماً تغيّر عدد الأعضاء بمقدار -74، وفي آخر 24 ساعة بمقدار -6، مع بقاء الوصول العام مرتفعاً.
- حالة التحقق: غير موثّقة
- معدل التفاعل (ER): يبلغ متوسط تفاعل الجمهور 7.20%. وخلال أول 24 ساعة من النشر يحصد المحتوى عادةً 3.71% من ردود الفعل نسبةً إلى إجمالي المشتركين.
- وصول المنشورات: يحصل كل منشور على متوسط 729 مشاهدة. وخلال اليوم الأول يجمع عادةً 376 مشاهدة.
- التفاعلات والاستجابة: يتفاعل الجمهور بانتظام؛ متوسط التفاعلات لكل منشور يبلغ 5.
- الاهتمامات الموضوعية: يركز المحتوى على مواضيع رئيسية مثل string, интерфейс, строка, boot, api.
📝 الوصف وسياسة المحتوى
يصف المؤلف القناة بأنها مساحة للتعبير عن الآراء الذاتية:
“📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate.
По всем вопросам @evgenycarter
РКН clck.ru/3KoGeP”
بفضل وتيرة التحديث المرتفعة (أحدث البيانات بتاريخ 26 أغسطس, 2026) تحافظ القناة على حداثتها ومستوى وصول مرتفع. وتُظهر التحليلات تفاعلاً نشطاً من الجمهور، ما يجعلها نقطة تأثير مهمة ضمن فئة التكنولوجيات والتطبيقات.
flush() перед clear() — иначе словишь баг
Если ты используешь EntityManager вручную, например, при батчевой вставке или обновлении, то, скорее всего, пишешь что-то вроде:
for (int i = 0; i < entities.size(); i++) {
em.persist(entities.get(i));
if (i % 50 == 0) {
em.clear(); // чтобы не росла память
}
}
⚠️ Но тут баг: без flush() перед clear() ты теряешь все неперсистенные изменения! Hibernate просто забудет про них.
✅ Правильно так:
for (int i = 0; i < entities.size(); i++) {
em.persist(entities.get(i));
if (i % 50 == 0) {
em.flush();
em.clear();
}
}
📌 flush() гарантирует, что все накопленные изменения пойдут в базу, а clear() уже безопасно очищает контекст.
💡 Эта ошибка особенно коварна, потому что не всегда проявляется — зависит от настроек, триггеров в БД, кэша и т.д.
📲 Мы в MAX
👉@BookJava@Configuration
Есть распространённый анти-паттерн: ты используешь @Configuration и внутри создаёшь бины с @Bean, а в этих методах — инжектишь зависимости через параметры. Всё выглядит красиво и «по фэншую»... но только на первый взгляд.
@Configuration
public class MyConfig {
@Bean
public MyService myService(SomeDep dep) {
return new MyService(dep);
}
@Bean
public SomeDep someDep() {
return new SomeDep();
}
}
⚠️ Проблема: такие методы вызываются при старте контекста, и если SomeDep создаётся долго (например, подтягивает настройки из удалённого конфига, делает init-запрос в БД, или тянет секьюрити-контекст), это тормозит весь старт.
📌 Хуже всего, если ты не подозреваешь об этом: ведь @Bean -методы не видны как "инициализация", и кажется, что контекст тормозит "где-то ещё".
💡 Совет:
- Используй @Lazy в нужных местах, особенно если bean тяжёлый или редко используется.
- Разделяй конфигурацию: отдельно core, отдельно init-heavy.
- Не бойся отказаться от @Configuration в пользу @Component + @Service, если это упрощает понимание.
И главное — профилируй старт. spring-boot-starter-actuator + --debug могут открыть глаза.
📲 Мы в MAX
👉@BookJava
@Service
public class EmailService {
private final SmtpClient client = new SmtpClient(); // дорогая инициализация
}
⚠️ Проблема: SmtpClient создаётся сразу при старте приложения. Даже если EmailService ни разу не вызовется. Это не только waste of resources, но и может сломать запуск, если SmtpClient требует специфического окружения.
📌 Решение — ленивая инициализация, но не через старый добрый null-check, а красиво, безопасно и читаемо:
💡 Способ #1: Lazy<T> wrapper
@Component
public class EmailService {
private final Supplier<SmtpClient> client = Suppliers.memoize(SmtpClient::new);
public void sendEmail(...) {
client.get().send(...);
}
}
Можно и без Guava:
public class Lazy<T> {
private Supplier<T> supplier;
public Lazy(Supplier<T> supplier) {
this.supplier = () -> {
T value = supplier.get();
this.supplier = () -> value;
return value;
};
}
public T get() {
return supplier.get();
}
}
💡 Способ #2: через Spring
@Service
public class EmailService {
private final ObjectProvider<SmtpClient> client;
public EmailService(ObjectProvider<SmtpClient> client) {
this.client = client;
}
public void sendEmail(...) {
client.getObject().send(...);
}
}
🧵 Итог:
- Не инициализируй тяжёлые объекты зря.
- Используй Supplier, ObjectProvider или Lazy<T>.
- Это особенно критично для тестов, лямбд и кэширования.
📲 Мы в MAX
👉@BookJava@Transactional в Spring Boot 3+.
📌 Проблема — транзакция не работает, потому что метод вызывается изнутри того же класса.
Рассмотрим пример:
@Service
public class UserService {
@Transactional
public void createUser(User user) {
saveUser(user);
sendWelcomeEmail(user); // бросает исключение
}
public void saveUser(User user) {
userRepository.save(user);
}
}
💥 Если sendWelcomeEmail выбросит исключение — транзакция не откатится, потому что @Transactional работает через прокси. Вызов createUser() должен идти извне, чтобы Spring "знал", что нужно обернуть вызов в транзакцию.
✅ Решения:
1. Вынести transactional-метод в отдельный бин:
@Service
public class UserCreationService {
@Transactional
public void createUser(User user) {
// ...
}
}
2. Внедрить self-прокси:
@Autowired
private UserService self;
public void externalCaller(User user) {
self.createUser(user);
}
3. Использовать AopContext:
((UserService) AopContext.currentProxy()).createUser(user);
⚠️ Не забудь включить exposeProxy = true в @EnableAspectJAutoProxy.
💡 Современный подход — разделение ответственности: transactional-методы живут в отдельных сервисах, их проще тестировать и не возникает подобных ловушек.
📲 Мы в MAX
👉@BookJava🥇место – 1 000 000 рублей 🥈место – 700 000 рублей 🥉место – 500 000 рублей Награда за 4-8 места - 100 000 рублейПройди заочный онлайн-этап и получи путевку на очный этап турнира в Республику Татарстан! Перелет, питание, проживание - за счет организаторов. 🇷🇺 Подать заявку и узнать подробности 🇷🇺
@Transactional методов в Spring Boot
Знаете, что @Transactional по умолчанию оборачивает метод в прокси?
Это значит:
- Внутренние вызовы в том же классе не проходят через транзакцию;
- Каждый такой прокси — это AOP-магия, которую можно обойти ради производительности.
📌 Если вы точно знаете, что метод будет вызываться только извне, и вам не нужна прокси-обёртка — используйте @Transactional на уровне интерфейса и включите interface-based proxy.
@Configuration
@EnableTransactionManagement(proxyTargetClass = false) // JDK proxy
public class TransactionConfig {
}
public interface UserService {
@Transactional
void createUser(User user);
}
📉 Это немного снижает overhead, особенно в высоконагруженных сервисах, где сотни тысяч вызовов @Transactional-методов.
💡 Подходит, если:
- У вас слоистая архитектура;
- Транзакции нужны только снаружи;
- Вы не используете вызовы this.someMethod() внутри сервиса.
⚠️ Не забывайте:
- JDK Proxy работает только с интерфейсами;
- Если вызываете методы внутри того же класса — прокси не сработает (и транзакция не начнётся).
📊 Профильте. Иногда замена proxyTargetClass = true на false даёт +3-5% к throughput.
📲 Мы в MAX
👉@BookJavamain.
✔️ Как создавать и запускать потоки через Thread и Runnable.
✔️ Зачем нужен join и как дождаться завершения запущенных потоков.
✔️ Почему многопоточность может ускорить программу, а может добавить лишние накладные расходы.
✔️ Какие типовые проблемы возникают в многопоточном коде: гонки, starvation и deadlock.
🔗 Ссылка на регистрацию: https://vk.cc/cZD2sI
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576@SpringBootApplication, которая включает в себя @EnableAutoConfiguration.
💡 Решение — использовать spring.autoconfigure.exclude, чтобы явно выключить ненужное. Например:
# application.yaml
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
❗️Это особенно актуально:
- в микросервисах без UI (отключаем MVC)
- если datasource создаётся вручную
- в тестах и lightweight-режимах
👀 Как понять, что мешает?
1. Запусти приложение с флагом --debug или логгером org.springframework.boot.autoconfigure уровня DEBUG.
2. Посмотри, какие автоконфигурации "matched", но тебе не нужны.
3. Добавь их в exclude.
🧰 Альтернатива — использовать аннотацию @ImportAutoConfiguration с явным списком нужных автоконфигураций. Это тонкая настройка, идеально для библиотек и SDK.
⚠️ Не переусердствуй: отключение нужной конфигурации может привести к тихим багам. Лучше вырезать по одной и смотреть на эффект.
👉 Это один из способов сделать Spring Boot предсказуемым и быстрым, особенно в CI/CD или serverless-окружениях.
📲 Мы в MAX
👉@BookJava
spring:
main:
lazy-initialization: true
Все бины станут ленивыми — создадутся только при первом обращении. Это может сократить старт приложения на 30-60%!
✅ Локально (избирательно):
@Component
@Lazy
public class HeavyBean {
public HeavyBean() {
System.out.println("HeavyBean init...");
}
}
Или через @Lazy на зависимостях:
@Service
public class MyService {
public MyService(@Lazy HeavyBean heavyBean) {
this.heavyBean = heavyBean;
}
}
🧠 Когда использовать:
- В dev-окружении — чтобы ускорить локальный dev cycle.
- В CLI/Batch-приложениях, где используется 1-2 бина.
- Когда есть тяжёлые бины, не нужные на старте (например, интеграции, большие клиенты и т.п.).
⚠️ Осторожно:
- Если забыть @Lazy на зависимостях, Spring всё равно создаст бин.
- Некоторые бины должны быть загружены сразу (например, @Scheduled, @EventListener), иначе они не сработают.
📲 Мы в MAX
👉@BookJavaSimpleDateFormat или текущего пользователя в рамках запроса. Но с ним легко получить утечку памяти, особенно в thread pool'ах.
📌 Почему?
ThreadLocal-хранилище (Thread.threadLocals) живёт столько же, сколько поток. А потоки из пулов живут долго. Если ты забыл вызвать remove() — данные останутся в памяти навсегда.
Пример:
private static final ThreadLocal<UserContext> context = ThreadLocal.withInitial(UserContext::new);
public void handleRequest() {
try {
context.set(new UserContext("user123"));
// работа с контекстом
} finally {
context.remove(); // ОБЯЗАТЕЛЬНО!
}
}
⚠️ Что пойдёт не так без remove()?
- Поток из пула закончит обрабатывать запрос, но UserContext останется висеть в ThreadLocalMap этого потока.
- Если UserContext содержит ссылки на другие объекты (например, HttpSession, EntityManager и т.д.) — вся эта цепочка не будет GC-шиться.
- И так накапливается утечка.
💡 Советы:
- Всегда вызывай remove() в finally.
- Для Spring можно использовать RequestScope или @ControllerAdvice вместо ThreadLocal.
- Проверяй код сторонних библиотек, если они используют ThreadLocal, особенно в фильтрах и интерсепторах.
📲 Мы в MAX
👉@BookJava
-Dspring.context.cache.applicationContext=true
💡 Что это такое?
Это встроенный механизм кэширования ApplicationContext, появившийся в Spring Boot 3.2.
Он сохраняет результат построения контекста и позволяет повторно использовать его между запусками, особенно в тестах и development-сценариях.
⚙️ Как работает:
- При первом запуске контекст билдится как обычно.
- Затем сериализуется и сохраняется на диск.
- При следующем запуске он подгружается из кеша (если не изменился), что даёт ускорение в 2-3 раза и больше.
🚀 Отлично подходит для:
- Тестов (@SpringBootTest);
- Dev tools и локального запуска;
- Разработки больших монолитов.
⚠️ Не влияет на продакшн (там кеш не используется по умолчанию)
⚠️ Не поддерживает все конфигурации (например, динамические настройки могут инвалидировать кеш)
Простой флаг — а экономит кучу времени каждый день.
📲 Мы в MAX
👉@BookJava@Transactional в Spring
Сейчас покажу вам один распространённый анти-паттерн, который легко пропустить — вызов транзакционного метода внутри того же класса.
📌 Пример:
@Service
public class UserService {
@Transactional
public void registerUser(UserDto dto) {
saveUser(dto);
}
@Transactional
public void saveUser(UserDto dto) {
// сохранение пользователя
}
}
💥 Проблема:
Spring не применит транзакцию к saveUser(), потому что вызов происходит внутри одного и того же бина — минуя прокси.
Spring AOP работает через прокси, и @Transactional "срабатывает", только если метод вызывается извне, через прокси-объект.
⚠️ Это может привести к очень странным багам: вы думаете, что транзакция есть, а её нет.
💡 Как исправить:
1. Вынести метод в отдельный бин:
@Service
public class UserSaver {
@Transactional
public void save(UserDto dto) {
// сохраняем
}
}
@Service
public class UserService {
private final UserSaver saver;
public UserService(UserSaver saver) {
this.saver = saver;
}
public void registerUser(UserDto dto) {
saver.save(dto);
}
}
2. Или использовать TransactionTemplate вручную.
✅ Всегда проверяйте, как вызываются методы с @Transactional. Особенно при рефакторинге!
📲 Мы в MAX
👉@BookJava@Value vs @ConfigurationProperties — кого выбрать?
Часто вижу, как даже опытные разработчики по привычке используют @Value для инъекции конфигурации:
@Value("${app.timeout}")
private Duration timeout;
⚠️ Но с ростом приложения @Value становится хрупким и неудобным.
📌 Лучший подход — использовать @ConfigurationProperties:
@ConfigurationProperties(prefix = "app")
public record AppProperties(Duration timeout, String apiKey) {}
@Bean
@ConfigurationPropertiesBinding
public AppProperties appProperties() {
return new AppProperties();
}
✅ Преимущества @ConfigurationProperties:
- 💡 Группирует настройки логически
- 🔍 Работает с валидацией (@Validated, @NotNull, и т.д.)
- 📚 Отлично поддерживается IDE (автокомплит, рефакторинг)
- 🔧 Удобно тестировать и мокать
🆕 Начиная с Spring Boot 2.2+, можно использовать record-классы и просто зарегистрировать бин через @EnableConfigurationProperties:
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {}
Так что, если у вас в проекте до сих пор десятки @Value — самое время навести порядок.
📲 Мы в MAX
👉@BookJava@Value в Spring — это ловушка, если вы используете списки или map'ы
Многие знают, что можно заинжектить список строк из application.yml вот так:
app:
langs:
- en
- fr
- de
@Value("${app.langs}")
private List<String> langs;
Но знаете, что вы получите?
⚠️ ОШИБКУ. @Value не умеет парсить YAML-массивы. Он ожидает строку, и даже с CSV-строкой (en,fr,de) — всё не так очевидно: Spring не применяет ConversionService для списков.
📌 Решение — использовать @ConfigurationProperties:
app:
langs:
- en
- fr
- de
@ConfigurationProperties(prefix = "app")
@Component
public class AppProps {
private List<String> langs;
// геттеры/сеттеры
}
💡 Профит:
- работает с List, Map, вложенными объектами;
- валидация через @Validated и @NotEmpty;
- легко покрыть тестами;
- меньше магии.
⚠️ @Value хорош для простых скаляров. Всё остальное — через @ConfigurationProperties.
📲 Мы в MAX
👉@BookJavaOptional — это не замена null везде и всегда
Привет! Сегодня хочу поделиться одной из часто встречающихся ошибок при использовании Optional в Java.
Многие разработчики, особенно начинающие, начинают использовать Optional везде, где может быть null, думая, что это автоматически делает код "безопасным". Но так ли это?
📌 Ключевая идея Optional — сигнализировать о возможном отсутствии значения в результате вызова метода.
А не заменять все поля и параметры на Optional.
Примеры плохой практики:
public class User {
private Optional<String> name; // ❌ Не нужно так делать
}
Почему это плохо:
- Увеличивается сложность сериализации (особенно с Jackson, GSON).
- Не соответствует архитектурной задумке: Optional — это не контейнер для полей.
- Проблемы с JPA (Hibernate не дружит с Optional-полями).
- Понижается читаемость кода.
💡 Лучше использовать Optional вот так:
public Optional<User> findUserById(Long id) {
// Возвращаем Optional, потому что пользователь может не существовать
}
То есть Optional — это про контракт на метод, а не про хранение данных.
Если кратко:
- ✅ Используй Optional в сигнатурах методов, когда результат может отсутствовать.
- ❌ Не используй Optional в полях и параметрах конструктора.
А ты как используешь Optional в проектах? Был ли опыт с его неправильным применением? Пиши в комментах👇
📲 Мы в MAX
👉@BookJava@Transactional в Spring
✔️Почему важны proxy и вызов метода через Spring Bean
✔️Как работают propagation-режимы на примере REQUIRED и REQUIRES_NEW
✔️Когда происходит rollback и почему checked exceptions не всегда откатывают транзакцию
✔️Типовые ошибки при работе с транзакциями в сервисном слое
Урок будет полезен Java/Kotlin-разработчикам, которые уже пишут приложения на Spring или начинают использовать Spring в реальных backend-проектах и хотят лучше понимать поведение транзакций.
🔗 Ссылка на регистрацию: https://vk.cc/cZ9GGY
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576boolean в Java — это не просто true или false. А за этим простым типом скрывается интересный нюанс, особенно если ты задумываешься об экономии памяти.
В Java нет отдельного типа, который занимает всего 1 бит. Хотя логично было бы ожидать, что boolean — это один бит (true/false), на самом деле в памяти он занимает 1 байт (а иногда и больше, в зависимости от структуры объекта).
Пример:
public class Flags {
boolean flag1;
boolean flag2;
boolean flag3;
}
Ты думаешь — три бита. Но JVM выравнивает поля, и из-за этого объект может занимать 16 байт или больше, в зависимости от архитектуры. Почему так?
📌 Причина:
JVM упрощает модель памяти ради производительности — доступ к байтам быстрее, чем к битам. Нет битовых сдвигов, масок и лишней логики.
💡 Что делать, если хочется сэкономить память?
Используй BitSet:
BitSet flags = new BitSet(3);
flags.set(0, true);
Это уже реальная битовая структура. Отличный выбор, если у тебя десятки или сотни логических флагов.
А ты знал об этом нюансе хранения boolean? Пиши в комментариях, сталкивался ли с перерасходом памяти из-за простых типов.
📲 Мы в MAX
👉@BookJava