Библиотека Java разработчика
10.8K subscribers
1.14K photos
564 videos
58 files
1.44K links
📚 Лайфхаки, приёмы и лучшие практики для Java-разработчиков. Всё, что ускорит код и прокачает навыки. Java, Spring, Maven, Hibernate.


По всем вопросам @evgenycarter

РКН clck.ru/3KoGeP
Download Telegram
💡 Не делай этого в @PostConstruct — особенно в проде

Сейчас расскажу, почему инициализировать важную бизнес-логику в @PostConstruct — плохая идея.

Типичный пример:


@Component
public class CacheLoader {

private final SomeService service;

public CacheLoader(SomeService service) {
this.service = service;
}

@PostConstruct
public void init() {
service.loadDataIntoCache(); // ⚠️ обращение к БД
}
}


🧨 Проблема: @PostConstruct вызывается до того, как приложение полностью поднялось.
Если внутри будет ошибка (например, БД недоступна) — приложение может упасть, или что хуже — запуститься в полурабочем состоянии.


📌 Кроме того:

* Нет контроля над порядком выполнения таких методов;
* Нельзя легко переиспользовать эту логику (например, вручную перезагрузить кеш);
* В тестах или dev-среде — такие вызовы часто мешают.


Современный подход — использовать ApplicationListener:


@Component
public class CacheLoader implements ApplicationListener<ApplicationReadyEvent> {

private final SomeService service;

@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
service.loadDataIntoCache(); // 👍 вызывается только после старта
}
}


📌 Альтернатива — аннотация @EventListener:


@EventListener(ApplicationReadyEvent.class)
public void onReady() {
// безопасно загружаем данные
}


📦 В Spring Boot это нативный и рекомендованный способ выполнения кода после старта.


🧠 Резюме:
🔹 @PostConstruct — только для простой инициализации бинов.
🔹 Бизнес-логику и I/O — в @EventListener(ApplicationReadyEvent.class).

👉@BookJava
👍11😁1
👩‍💻 Java-разработчик? Хотите ускорить разработку и избавиться от рутины?

На открытом уроке «Kotlin Multiplatform: лайфхак для Java-разработчиков» от OTUS мы покажем, как с помощью Kotlin Multiplatform (KMP) использовать один и тот же код для различных проектов — от Android и iOS до backend-систем.

Что вас ждёт:
✔️ Узнаете, как интегрировать Kotlin Multiplatform в Java-проекты и настроить совместимость с существующим стеком.
✔️ Сможете избежать дублирования логики и сэкономите время на поддержке разных модулей для разных платформ.
✔️ Получите практические знания, как создавать общий код для JVM, Android и iOS.

Открытый урок проходит в преддверии старта курса «Kotlin Backend Developer. Professional».

🎁 Всем участникам вебинара дарим промокод, который дает скидку на обучение - Kotlin5

➡️ Встречаемся 14 мая в 20:00 МСК — присоединяйтесь и узнайте, как сэкономить время и силы с Kotlin Multiplatform: https://vk.cc/cLEsDR

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
🤮1
🧠 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.

👉@BookJava
👍13
🧠 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 под каждую категорию исключений.

👉@BookJava
👍8
👩‍💻 Открытый урок «Облака и Mongo DB Atlas»

🗓 12 мая в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Разработчик на Spring Framework»

Погружаемся в мир облачных технологий и учимся разворачивать кластер MongoDB бесплатно.

Программа вебинара:
✔️ Облачные технологии: что такое облака, какие бывают уровни (IaaS, PaaS, SaaS) с простыми аналогиями для понимания.
✔️ Практическая демонстрация: как создать кластер MongoDB в Atlas и подключиться к нему.

Вебинар будет полезен:
Разработчикам, начинающим backend-программистам, студентам IT-курсов и всем, кто хочет разобраться в облачных сервисах.

В результате вебинара вы:
Научитесь создавать кластеры MongoDB в облаке.

🔗 Ссылка на регистрацию: https://vk.cc/cLJqe0

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🧠 Неочевидный 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) не как декор, а как инструмент для оптимизации.

👉@BookJava
👍9
🚀 Подборка Telegram каналов для программистов

Системное администрирование, DevOps 📌

https://t.me/bash_srv Bash Советы
https://t.me/win_sysadmin Системный Администратор Windows
https://t.me/sysadmin_girl Девочка Сисадмин
https://t.me/srv_admin_linux Админские угодья
https://t.me/linux_srv Типичный Сисадмин
https://t.me/devopslib Библиотека девопса | DevOps, SRE, Sysadmin
https://t.me/linux_odmin Linux: Системный администратор
https://t.me/devops_star DevOps Star (Звезда Девопса)
https://t.me/i_linux Системный администратор
https://t.me/linuxchmod Linux
https://t.me/sys_adminos Системный Администратор
https://t.me/tipsysdmin Типичный Сисадмин (фото железа, было/стало)
https://t.me/sysadminof Книги для админов, полезные материалы
https://t.me/i_odmin Все для системного администратора
https://t.me/i_odmin_book Библиотека Системного Администратора
https://t.me/i_odmin_chat Чат системных администраторов
https://t.me/i_DevOps DevOps: Пишем о Docker, Kubernetes и др.
https://t.me/sysadminoff Новости Линукс Linux

1C разработка 📌
https://t.me/odin1C_rus Cтатьи, курсы, советы, шаблоны кода 1С
https://t.me/DevLab1C 1С:Предприятие 8
https://t.me/razrab_1C 1C Разработчик
https://t.me/buh1C_prog 1C Программист | Бухгалтерия и Учёт
https://t.me/rabota1C_rus Вакансии для программистов 1С

Программирование C++📌
https://t.me/cpp_lib Библиотека C/C++ разработчика
https://t.me/cpp_knigi Книги для программистов C/C++
https://t.me/cpp_geek Учим C/C++ на примерах

Программирование Python 📌
https://t.me/pythonofff Python академия.
https://t.me/BookPython Библиотека Python разработчика
https://t.me/python_real Python подборки на русском и английском
https://t.me/python_360 Книги по Python

Java разработка 📌
https://t.me/BookJava Библиотека Java разработчика
https://t.me/java_360 Книги по Java Rus
https://t.me/java_geek Учим Java на примерах

GitHub Сообщество 📌
https://t.me/Githublib Интересное из GitHub

Базы данных (Data Base) 📌
https://t.me/database_info Все про базы данных

Мобильная разработка: iOS, Android 📌
https://t.me/developer_mobila Мобильная разработка
https://t.me/kotlin_lib Подборки полезного материала по Kotlin

Фронтенд разработка 📌
https://t.me/frontend_1 Подборки для frontend разработчиков
https://t.me/frontend_sovet Frontend советы, примеры и практика!
https://t.me/React_lib Подборки по React js и все что с ним связано

Разработка игр 📌
https://t.me/game_devv Все о разработке игр

Библиотеки 📌
https://t.me/book_for_dev Книги для программистов Rus
https://t.me/programmist_of Книги по программированию
https://t.me/proglb Библиотека программиста
https://t.me/bfbook Книги для программистов

БигДата, машинное обучение 📌
https://t.me/bigdata_1 Big Data, Machine Learning

Программирование 📌
https://t.me/bookflow Лекции, видеоуроки, доклады с IT конференций
https://t.me/rust_lib Полезный контент по программированию на Rust
https://t.me/golang_lib Библиотека Go (Golang) разработчика
https://t.me/itmozg Программисты, дизайнеры, новости из мира IT
https://t.me/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻
https://t.me/nodejs_lib Подборки по Node js и все что с ним связано
https://t.me/ruby_lib Библиотека Ruby программиста
https://t.me/lifeproger Жизнь программиста. Авторский канал.

QA, тестирование 📌
https://t.me/testlab_qa Библиотека тестировщика

Шутки программистов 📌
https://t.me/itumor Шутки программистов

Защита, взлом, безопасность 📌
https://t.me/thehaking Канал о кибербезопасности
https://t.me/xakep_2 Хакер Free

Книги, статьи для дизайнеров 📌
https://t.me/ux_web Статьи, книги для дизайнеров

Математика 📌
https://t.me/Pomatematike Канал по математике
https://t.me/phis_mat Обучающие видео, книги по Физике и Математике
https://t.me/matgeoru Математика | Геометрия | Логика

Excel лайфхак📌
https://t.me/Excel_lifehack

https://t.me/mir_teh Мир технологий (Technology World)

Вакансии 📌
https://t.me/sysadmin_rabota Системный Администратор
https://t.me/progjob Вакансии в IT
🧠 Трюк с @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+ обязательно проверьте, что нужные конфигурации действительно подхватываются. Особенно, если раньше они подключались "магически".

👉@BookJava
👍6
🔧 Maven vs. Gradle: что выбрать разработчику?

Когда речь заходит о сборке Java-проектов, выбор обычно падает на два главных инструмента: Maven и Gradle. Оба давно стали стандартом индустрии, но каждый имеет свои особенности. Разберёмся, что выбрать 👇

Maven — проверенная классика

Строгая структура: легче читать и сопровождать
Надёжность и предсказуемость сборки
Большое комьюнити и множество плагинов
⚠️ XML-конфигурация громоздкая
⚠️ Медленнее по сравнению с Gradle

Gradle — гибкость и скорость

Поддержка Kotlin и Groovy DSL
Инкрементальные сборки и кэширование → быстрее
Более гибкий подход к настройке
⚠️ Порог входа выше
⚠️ Иногда сложно отлаживать конфигурацию


💡 Вывод:

* 🔹 Выбирай Maven, если важны стабильность, простота и читаемость.
* 🔹 Выбирай Gradle, если хочешь максимум производительности и гибкости.

🎯 В крупных проектах Gradle становится всё популярнее, особенно при использовании Kotlin. Но в enterprise-среде Maven по-прежнему правит бал.

А ты чем пользуешься? Делись опытом в комментах ⬇️

👉@BookJava
👍9
🔍Тестовое собеседование на Middle Java-разработчика завтра

14 мая(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.

Как это будет:
📂 Константин Лесняк, Java-разработчик с большим опытом проведения собесов, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Костя будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Косте

Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы. 

Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot

Реклама. ООО "ШОРТКАТ", ИНН: 9731139396, erid: 2VtzqxZCqXi
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
💡 Чем опасен @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 (если нужен контроль и очереди)

👉@BookJava
🔥7👍2
Возможности Kotlin для создания DSL на примере JsonBuilder

Приглашаем на открытый урок.

🗓 22 мая в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Kotlin Backend Developer. Professional».

Что вас ждёт:
✔️ рассмотрим общую теорию о DSL: назначение, особенности, практика;
✔️ попрактикуемся в создании DSL на примере JsonBuilder;
✔️ рассмотрим возможности Kotlin, полезные для создания DSL.

🎁 Всем участникам вебинара дарим промокод, который дает скидку на обучение - Kotlin5

👉 Регистрация на вебинар: https://vk.cc/cLUdvA

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
🧠 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-бинов;
▫️чтобы ускорить старт приложения.

👉@BookJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Хотите овладеть Spark на профессиональном уровне?

Приглашаем дата-инженеров 26 мая в 20:00 на открытый урок «Spark в Kubernetes».

На занятии мы рассмотрим особенности и варианты запуска Spark в Kubernetes.

🔊 Вебинар проведет Вадим Заигрин, Team Lead команд инженеров данных на разных проектах.

Продолжить освоение инструментов дата-инжиниринга вы сможете на онлайн-курсе «Spark Developer» от OTUS.

➡️ Ссылка для регистрации: https://vk.cc/cLWZuO

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
👎2
🧠 Внезапная ловушка с @Transactional и self-invocation в Spring

Вы знали, что вызов метода с @Transactional внутри того же класса не активирует транзакцию? Это один из самых частых подводных камней.

📌 Почему так происходит?
Spring AOP работает через прокси. Когда вы вызываете метод this.someTransactionalMethod(), вы обходите прокси и вызываете метод напрямую — без обёртки, которая включает транзакцию.

Пример:


@Service
public class UserService {

public void createUser() {
// транзакция не начнётся
this.saveUser();
}

@Transactional
public void saveUser() {
// ожидаем, что тут будет транзакция, но её нет
}
}


⚠️ Итог: никакой транзакции, и вы получите баг, который сложно заметить: данные не откатываются, lazy-loading кидает LazyInitializationException, и т.д.

💡 Как правильно:

1. Вынести transactional-метод в другой бин:


@Service
public class UserSaver {
@Transactional
public void saveUser() { ... }
}


2. Или получить прокси текущего бина через AopContext:


public void createUser() {
((UserService) AopContext.currentProxy()).saveUser();
}


📌 Для второго варианта не забудьте включить:


@EnableAspectJAutoProxy(exposeProxy = true)


⚠️ Важно: Не используйте AopContext без крайней необходимости. Предпочтительнее делегировать логику в отдельный бин — это чище и легче тестируется.

👉@BookJava
👍4👎2
👩‍💻 SpELые приложения на Spring

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

🗓 21 мая в 19:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Разработчик на Spring Framework».

О чём поговорим:
✔️Разоберем, для чего нужен SpEL.
✔️Рассмотрим, в каких проектах Spring его можно встретить.

Кому будет интересно:
Spring-разработчикам, Java-бэкенд-инженерам, архитекторам ПО, IT-специалистам и студентам, заинтересованным в технологиях Spring.

В результате урока:
Узнаете, для чего нужен SpEL и где его можно применять.

🔗 Ссылка на регистрацию: https://vk.cc/cLY8ET

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21
🧠 Как не словить 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 — ваши лучшие друзья, если нужен контроль и производительность.

👉@BookJava
👍5