🧠
Сегодня покажу вам, почему аннотация
Допустим, у вас такой конфиг:
📌 Проблема #1: Плохая валидация
Если значение в
📌 Проблема #2: Нет автокомплита
IDE не сможет подсказать, какие свойства вообще есть. Рефакторинг ключей — боль.
📌 Проблема #3: Тестирование
Труднее подменить значения в тестах:
✅ Современный подход — использовать
И в
💡 Spring сам сконвертирует
⚠️ Не забудьте включить бин:
📌 Резюме:
👉 @java_geek
@Value
— тихая ловушка Spring'аСегодня покажу вам, почему аннотация
@Value
— не лучший выбор для работы с конфигурацией, особенно в Spring Boot 3+.Допустим, у вас такой конфиг:
@Value("${app.timeout:30}")
private int timeout;
📌 Проблема #1: Плохая валидация
Если значение в
application.yaml
невалидное (например, timeout: abc), приложение *упадёт при старте*, но вы получите лишь общее сообщение Failed to bind…
, без точной причины.📌 Проблема #2: Нет автокомплита
IDE не сможет подсказать, какие свойства вообще есть. Рефакторинг ключей — боль.
📌 Проблема #3: Тестирование
Труднее подменить значения в тестах:
@Value
не так просто "подсунуть" моками.✅ Современный подход — использовать
@ConfigurationProperties
:
@ConfigurationProperties(prefix = "app")
@Configuration
public class AppProperties {
private Duration timeout = Duration.ofSeconds(30);
// геттеры/сеттеры
}
И в
application.yaml
:
app:
timeout: 30s
💡 Spring сам сконвертирует
30s
в Duration
, поддержит автокомплит в IDE, и вы сможете валидировать значения с @Validated
.⚠️ Не забудьте включить бин:
@EnableConfigurationProperties(AppProperties.class)
📌 Резюме:
@Value
— для быстрых хаков. Для серьёзной конфигурации — @ConfigurationProperties
.👉 @java_geek
👍6🔥1
🧠 Проблема ленивой инициализации
Когда вы используете
📌 Типичный баг:
🔍 Если где-то в коде вызвать
⚠️ В результате — метод выполнится без транзакции, и вы не поймёте почему.
💡 Как избежать:
* Никогда не используйте
* Лучше разделите логику: сервис без прокси — отдельно, бизнес-методы с
✅ Надёжный способ: вызывать
👉 @java_geek
@Transactional
-сервисов в Spring BootКогда вы используете
@Transactional
на методе сервиса, Spring оборачивает бин в прокси. Но если вы решите сделать ленивую инициализацию (@Lazy
) или внедрите такой бин в другой с помощью ObjectProvider.getIfAvailable()
или @Autowired(required = false)
— есть риск неожиданного поведения.📌 Типичный баг:
@Service
@Lazy
public class MyService {
@Transactional
public void doSomething() {
// ...
}
}
🔍 Если где-то в коде вызвать
applicationContext.getBean(MyService.class)
в момент, когда прокси ещё не создан — вы получите оригинальный, не-прокси-объект. А значит, @Transactional
не сработает.⚠️ В результате — метод выполнится без транзакции, и вы не поймёте почему.
💡 Как избежать:
* Никогда не используйте
@Lazy
или ленивое получение @Transactional
-сервисов, если не понимаете нюансов проксирования.* Лучше разделите логику: сервис без прокси — отдельно, бизнес-методы с
@Transactional
— в другом бине.✅ Надёжный способ: вызывать
@Transactional
-методы только через Spring-контейнер. Даже внутри самого сервиса — через self-injection:
@Service
public class MyService {
private final MyService self;
public MyService(@Lazy MyService self) {
this.self = self;
}
public void doWrapper() {
self.doTransactional(); // прокси работает
}
@Transactional
public void doTransactional() {
// ...
}
}
👉 @java_geek
❤4👍2👎1👏1
🧠 Ленивая инициализация бинов в Spring Boot 3 — когда это спасает прод
Иногда на проде случаются сбои при старте из-за тяжёлых или нестабильных бинов. Это может быть внешняя интеграция, длительная инициализация или просто нестабильный ресурс. В таких случаях поможет ленивая инициализация.
📌 Как включить ленивую инициализацию глобально:
📌 Или в
💡 Бины будут создаваться только при первом запросе к ним. Это уменьшает время старта и помогает "пережить" временные проблемы.
⚠️ Минусы:
* Ошибки сдвигаются с этапа запуска на runtime.
* Если бин важен для старта (например, контроллер или Scheduler), его лучше инициализировать сразу.
📌 Тонкая настройка:
Хотите ленивую инициализацию только для части приложения?
💡 Работает и с
📈 Используйте в микросервисах с большим числом зависимостей или при миграции на Spring Boot 3+, где время старта стало критичным (например, в serverless-архитектуре).
👉 @java_geek
Иногда на проде случаются сбои при старте из-за тяжёлых или нестабильных бинов. Это может быть внешняя интеграция, длительная инициализация или просто нестабильный ресурс. В таких случаях поможет ленивая инициализация.
📌 Как включить ленивую инициализацию глобально:
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(App.class);
app.setLazyInitialization(true);
app.run(args);
}
}
📌 Или в
application.yml
:
spring:
main:
lazy-initialization: true
💡 Бины будут создаваться только при первом запросе к ним. Это уменьшает время старта и помогает "пережить" временные проблемы.
⚠️ Минусы:
* Ошибки сдвигаются с этапа запуска на runtime.
* Если бин важен для старта (например, контроллер или Scheduler), его лучше инициализировать сразу.
📌 Тонкая настройка:
Хотите ленивую инициализацию только для части приложения?
@Lazy
@Component
public class HeavyService {
// ...
}
💡 Работает и с
@Bean
, @Service
, @Repository
.📈 Используйте в микросервисах с большим числом зависимостей или при миграции на Spring Boot 3+, где время старта стало критичным (например, в serverless-архитектуре).
👉 @java_geek
👍3
14 мая(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама. ООО "ШОРТКАТ", ИНН: 9731139396, erid: 2VtzqvEJJNG
Please open Telegram to view this post
VIEW IN TELEGRAM
🧠 Почему
В Spring
📌 Прокси перехватывает только ВНЕШНИЕ вызовы метода.
Если вы вызываете
Пример:
💡 Даже если вы сделаете метод
⚠️ Аннотация
✅ Правильный подход:
Вынесите транзакционный метод в отдельный бин:
И используйте его из другого бина:
📌 Или используйте аспектно-ориентированную реализацию с
👉 @java_geek
@Transactional
не работает в private-методах?В Spring
@Transactional
работает через прокси. Это значит, что Spring оборачивает бин в обёртку, которая и управляет транзакцией. Но есть важный нюанс:📌 Прокси перехватывает только ВНЕШНИЕ вызовы метода.
Если вы вызываете
@Transactional
-метод изнутри того же класса, транзакция не начнётся.Пример:
@Service
public class UserService {
public void createUser() {
// вызов внутреннего метода — транзакция не активируется
saveUser();
}
@Transactional
private void saveUser() {
// НЕ будет работать как транзакционный
}
}
💡 Даже если вы сделаете метод
public
, но вызов будет из того же класса — эффект тот же: Spring-прокси его не "перехватит".⚠️ Аннотация
@Transactional
не работает на private
-методах вообще. Прокси просто не может их подменить.✅ Правильный подход:
Вынесите транзакционный метод в отдельный бин:
@Service
public class UserSaver {
@Transactional
public void saveUser() {
// теперь работает!
}
}
И используйте его из другого бина:
@Service
public class UserService {
private final UserSaver userSaver;
public UserService(UserSaver userSaver) {
this.userSaver = userSaver;
}
public void createUser() {
userSaver.saveUser(); // транзакция начнётся
}
}
📌 Или используйте аспектно-ориентированную реализацию с
@EnableAspectJAutoProxy(exposeProxy = true)
+ AopContext.currentProxy()
, но это уже хардкор.👉 @java_geek
👍5❤1
🚀 Открой для себя идеальный путь к лидерству с карьерным тестом от ОЭЗ «Алабуга»! 🌟
Мечтаете о карьере в крупной компании, где ваш потенциал раскроется на полную? Наш тест поможет вам определить вашу уникальную лидерскую роль. Может быть, именно вы станете тем лидером, который выведет команду на новый уровень?
После прохождения теста вы можете заполнить заявку и получить приглашение на эксклюзивную лидерскую программу. Участие в программе открывает реальные перспективы трудоустройства в ОЭЗ «Алабуга», предоставляя шанс начать путь к профессиональному признанию.
Сделайте первый шаг к своему будущему сегодня! Пройдите тест, подайте заявку и начните строить свою карьеру вместе с нами. 🎯
Мечтаете о карьере в крупной компании, где ваш потенциал раскроется на полную? Наш тест поможет вам определить вашу уникальную лидерскую роль. Может быть, именно вы станете тем лидером, который выведет команду на новый уровень?
После прохождения теста вы можете заполнить заявку и получить приглашение на эксклюзивную лидерскую программу. Участие в программе открывает реальные перспективы трудоустройства в ОЭЗ «Алабуга», предоставляя шанс начать путь к профессиональному признанию.
Сделайте первый шаг к своему будущему сегодня! Пройдите тест, подайте заявку и начните строить свою карьеру вместе с нами. 🎯
⚡1🔥1🍾1
Метод isEmpty()
👉 @java_geek
isEmpty()
– проверяет список на наличие элементов. Если список пустой, то возвращает true, в противном случае – false.👉 @java_geek
👍3😐2🤯1
❓Зачем нужны и какие бывают блоки инициализации?
Блоки инициализации представляют собой код, заключенный в фигурные скобки и размещаемый внутри класса вне объявления методов или конструкторов.
• Существуют статические и нестатические блоки инициализации.
• Блок инициализации выполняется перед инициализацией класса загрузчиком классов или созданием объекта класса с помощью конструктора.
• Несколько блоков инициализации выполняются в порядке следования в коде класса.
• Блок инициализации способен генерировать исключения, если их объявления перечислены в throws всех конструкторов класса.
• Блок инициализации возможно создать и в анонимном классе.
👉 @java_geek
Блоки инициализации представляют собой код, заключенный в фигурные скобки и размещаемый внутри класса вне объявления методов или конструкторов.
• Существуют статические и нестатические блоки инициализации.
• Блок инициализации выполняется перед инициализацией класса загрузчиком классов или созданием объекта класса с помощью конструктора.
• Несколько блоков инициализации выполняются в порядке следования в коде класса.
• Блок инициализации способен генерировать исключения, если их объявления перечислены в throws всех конструкторов класса.
• Блок инициализации возможно создать и в анонимном классе.
👉 @java_geek
❤2👍2🔥1
4 июня(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама. ООО "ШОРТКАТ", ИНН: 9731139396, erid: 2VtzqxFHKss
Please open Telegram to view this post
VIEW IN TELEGRAM
Collections.disjoint()
👉 @java_geek
Collections.disjoint()
проверяет, не имеют ли две коллекции общих элементов. Это полезно для фильтрации, сравнения и оптимизации поиска пересечений.👉 @java_geek
👍4
Как легко избавиться от N+1 проблемы в Spring Data JPA, используя
📌 Что такое N+1?
Когда вам нужно загрузить список сущностей (N штук), а для каждой из них Hibernate выполняет дополнительный запрос к базе — получается 1 запрос для основной выборки + N запросов для связанных данных.
💡 Пример проблемы:
При вызове
1. SELECT * FROM users
2. Для каждого пользователя: SELECT * FROM orders WHERE user_id = ?
⚠️ Почему это плохо?
* Избыточные круги по базе
* Замедление при N > 50−100
* Рост нагрузки на БД и время ответа
🔧 Решение #1:
🔧 Решение #2:
*
* В репозитории вы указываете, какой граф выбирать для метода
* Hibernate формирует один SQL с
🧠 Когда стоит использовать?
* Если часто приходят жалобы на «медленно выбирается список с зависимыми сущностями»
* Когда
* Для динамических графов можно строить
💡 Код для динамического графа:
* Здесь вы сами добавляете, какие связи надо подгрузить
* Подходит, когда нужен гибкий выбор полей «на лету»
⚠️ Важные нюансы:
*
* Следите за дублированием строк в результирующем
* Если нужно подгружать множество уровней вложенности, объявите вложенные
📌 Вывод:
1. По умолчанию ставьте
2. Когда нужна жадная загрузка — используйте
3. Динамические графы через
👉 @java_geek
@EntityGraph
и оптимизированное fetch join
. 🧠📌 Что такое N+1?
Когда вам нужно загрузить список сущностей (N штук), а для каждой из них Hibernate выполняет дополнительный запрос к базе — получается 1 запрос для основной выборки + N запросов для связанных данных.
💡 Пример проблемы:
// Репозиторий без оптимизации
public interface UserRepository extends JpaRepository<User, Long> {}
// Сущности
@Entity
public class User {
@OneToMany(mappedBy = "user")
private List<Order> orders;
}
При вызове
userRepository.findAll()
:1. SELECT * FROM users
2. Для каждого пользователя: SELECT * FROM orders WHERE user_id = ?
⚠️ Почему это плохо?
* Избыточные круги по базе
* Замедление при N > 50−100
* Рост нагрузки на БД и время ответа
🔧 Решение #1:
fetch join
в @Query
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.active = true")
List<User> findAllActiveWithOrders();
✅ Все нужные данные подтягиваются одним запросом.
❌ Но не всегда удобно, если нужно динамически выбирать связи.
🔧 Решение #2:
@EntityGraph
@Entity
@NamedEntityGraph(
name = "User.withOrders",
attributeNodes = @NamedAttributeNode("orders")
)
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
// ...
}
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(value = "User.withOrders", type = EntityGraph.EntityGraphType.LOAD)
List<User> findAllByActiveTrue();
}
*
@NamedEntityGraph
в сущности описывает, какие связи нужно подтянуть* В репозитории вы указываете, какой граф выбирать для метода
* Hibernate формирует один SQL с
LEFT OUTER JOIN
, грузя User
+ Order
за 1 запрос🧠 Когда стоит использовать?
* Если часто приходят жалобы на «медленно выбирается список с зависимыми сущностями»
* Когда
fetch = FetchType.LAZY
установлен по-умолчанию, но иногда нужна жадная загрузка* Для динамических графов можно строить
EntityGraph
прямо в коде через API💡 Код для динамического графа:
public List<User> findActiveWithDynamicGraph() {
EntityGraph<User> graph = entityManager.createEntityGraph(User.class);
graph.addAttributeNodes("orders", "profile");
TypedQuery<User> query = entityManager.createQuery(
"SELECT u FROM User u WHERE u.active = true", User.class
);
query.setHint("javax.persistence.fetchgraph", graph);
return query.getResultList();
}
* Здесь вы сами добавляете, какие связи надо подгрузить
* Подходит, когда нужен гибкий выбор полей «на лету»
⚠️ Важные нюансы:
*
@EntityGraph
работает только для root
- сущности (не на вложенных коллекциях)* Следите за дублированием строк в результирующем
List
: вы можете получить повторяющиеся User
- объекты, если у вас несколько Order. В таком случае добавьте DISTINCT
в JPQL:
@Query("SELECT DISTINCT u FROM User u JOIN FETCH u.orders WHERE u.active = true")
* Если нужно подгружать множество уровней вложенности, объявите вложенные
subgraph
в @NamedEntityGraph
📌 Вывод:
1. По умолчанию ставьте
fetch = FetchType.LAZY
для всех связей, чтобы не тянуть лишние данные.2. Когда нужна жадная загрузка — используйте
@EntityGraph
или fetch join
.3. Динамические графы через
EntityManager
помогут строить сложные выборки без избыточных запросов.👉 @java_geek
👍6
HashMap: chain hashing и битовый спрединг
🧠 HashMap использует chain hashing + битовый спрединг
🔹 Хэш-функция (bit mixing):
🔹 Выбор бакета:
📌 Separate chaining: в каждом бакете хранится связный список (Java 8+ превращает списки длиной >8 в красно-черное дерево для ускорения).
📈 Сложность: средняя O(1), в худшем случае O(n) → O(log n) после treeification.
💡 Bit mixing объединяет старшие и младшие биты, чтобы при взятии по модулю power-of-two (маска
⚠️ Не для криптографии: это простая битовая операция, а не криптостойкая хэш-функция.
👉 @java_geek
🧠 HashMap использует chain hashing + битовый спрединг
🔹 Хэш-функция (bit mixing):
int h = key.hashCode();
int hash = h ^ (h >>> 16);
🔹 Выбор бакета:
int idx = hash & (table.length - 1);
📌 Separate chaining: в каждом бакете хранится связный список (Java 8+ превращает списки длиной >8 в красно-черное дерево для ускорения).
📈 Сложность: средняя O(1), в худшем случае O(n) → O(log n) после treeification.
💡 Bit mixing объединяет старшие и младшие биты, чтобы при взятии по модулю power-of-two (маска
&
) распределение оставалось равномерным.⚠️ Не для криптографии: это простая битовая операция, а не криптостойкая хэш-функция.
👉 @java_geek
👍1
🧠 Почему
Один из самых частых вопросов: "Я поставил
📌 Ответ — в механизме прокси Spring.
Spring оборачивает бины с
💡 Пример:
📎 Решения:
1. Вынести метод в другой бин:
2. Или вызвать себя через `ApplicationContext`:
⚠️ Но лучше использовать первый способ — он чище архитектурно.
👉 @java_geek
@Transactional
не работает?Один из самых частых вопросов: "Я поставил
@Transactional
, но транзакция не откатывается. Почему?"📌 Ответ — в механизме прокси Spring.
Spring оборачивает бины с
@Transactional
в прокси, которые перехватывают вызовы и управляют транзакцией. Но работает это только при вызове метода извне. Если ты вызываешь метод с @Transactional
внутри того же класса, прокси не используется, и аннотация игнорируется.💡 Пример:
@Service
public class UserService {
public void registerUser() {
createUser(); // ❌ Транзакция не работает!
}
@Transactional
public void createUser() {
// изменения в БД
}
}
📎 Решения:
1. Вынести метод в другой бин:
@Service
public class UserService {
private final UserWriter writer;
public UserService(UserWriter writer) {
this.writer = writer;
}
public void registerUser() {
writer.createUser(); // ✅ работает
}
}
@Service
public class UserWriter {
@Transactional
public void createUser() {
// изменения в БД
}
}
2. Или вызвать себя через `ApplicationContext`:
@Autowired
private ApplicationContext context;
public void registerUser() {
context.getBean(UserService.class).createUser(); // ✅ работает
}
⚠️ Но лучше использовать первый способ — он чище архитектурно.
👉 @java_geek
❤4👍3
Приглашаем на открытый урок.
🗓 24 июня в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java-разработчик».
О чём поговорим:
Кому будет интересно:
Начинающим Java-разработчикам, студентам и всем, кто хочет перестать бояться слов «тестирование» и «баги».
В результате вебинара вы:
Создадите свой первый тест на Java, поймёте, как тестировать методы с исключениями, и начнёте писать код, которым можно гордиться.
🔗 Ссылка на регистрацию: https://vk.cc/cMXt49
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
Fluent Interface
Fluent Interface (Текучий интерфейс) — это шаблон проектирования, который позволяет создавать код, читающийся как текст на естественном языке.
Он достигается путем создания методов, которые возвращают ссылку на this объект, позволяя вызывать их в цепочке.
👉 @java_geek
Fluent Interface (Текучий интерфейс) — это шаблон проектирования, который позволяет создавать код, читающийся как текст на естественном языке.
Он достигается путем создания методов, которые возвращают ссылку на this объект, позволяя вызывать их в цепочке.
👉 @java_geek
👍3
🧠 Зачем использовать
Многие добавляют
📌 Что делает
* Подсказывает JPA провайдеру (например, Hibernate), что изменения в сущностях можно не отслеживать (
* В некоторых БД может выставить read-only transaction flag, который предотвращает нежелательные изменения (например, в PostgreSQL).
* Может ускорить выборки, особенно при больших графах сущностей.
💡 Пример:
⚠️ Важно:
*
* Hibernate всё равно создаст транзакцию — это не аннотация "без транзакций".
✅ Когда использовать:
* Методы, которые только читают данные и не модифицируют сущности.
* Большие выборки без нужды в lazy-инициализации через сессию.
* Там, где критична производительность чтения.
❌ Когда НЕ нужно:
* Если вы всё равно модифицируете сущности внутри метода.
* Если работаете с
📎 Документация Spring @Transactional
👉 @java_geek
@Transactional(readOnly = true)
?Многие добавляют
@Transactional(readOnly = true)
на сервисные методы просто по привычке. Но давайте разберёмся, что на самом деле даёт этот флаг, и где он реально ускоряет выполнение.📌 Что делает
readOnly = true
?* Подсказывает JPA провайдеру (например, Hibernate), что изменения в сущностях можно не отслеживать (
skip dirty checking
).* В некоторых БД может выставить read-only transaction flag, который предотвращает нежелательные изменения (например, в PostgreSQL).
* Может ускорить выборки, особенно при больших графах сущностей.
💡 Пример:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional(readOnly = true)
public UserDto getUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found"));
return UserDto.from(user);
}
}
⚠️ Важно:
*
readOnly = true
не блокирует изменения в БД, если вы явно вызываете save()
— это НЕ защита от дурака.* Hibernate всё равно создаст транзакцию — это не аннотация "без транзакций".
✅ Когда использовать:
* Методы, которые только читают данные и не модифицируют сущности.
* Большие выборки без нужды в lazy-инициализации через сессию.
* Там, где критична производительность чтения.
❌ Когда НЕ нужно:
* Если вы всё равно модифицируете сущности внутри метода.
* Если работаете с
@Modifying
запросами — Spring их игнорирует при readOnly = true
.📎 Документация Spring @Transactional
👉 @java_geek
👍2
2 июля(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама. ООО "ШОРТКАТ", ИНН: 9731139396, erid: 2Vtzqwfa2Fy
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥 Spring Native Image vs. Обычное Spring-приложение: В чем разница и зачем это нужно?
Разбираемся, как нативные образы меняют правила игры для Spring-приложений! 👇
Обычное Spring-приложение (JVM-based):
Когда вы запускаете классический Spring Boot на Java Virtual Machine (JVM), происходит следующее:
🔵 JVM запускается первой: Прежде чем ваш код заработает, JVM должна инициализироваться и загрузить классы. Это занимает время.
🔵 Динамическая природа: JVM позволяет динамически загружать классы и использовать рефлексию в рантайме. Это гибко, но ресурсозатратно.
🔵 "Прогрев" (Warm-up): Для пиковой производительности JVM нужно время на JIT-компиляцию кода.
🔵 Большой объем памяти: JVM сама по себе требует значительного объема RAM.
🔵 Размер артефакта: JAR-файл содержит байт-код, который JVM затем обрабатывает.
Spring Native Image (с GraalVM Native Image):
Это компиляция вашего Spring-приложения в самостоятельный нативный исполняемый файл, не требующий JVM!
🔵 Компиляция AOT (Ahead-of-Time): Весь код (включая Spring и JDK) компилируется в машинный код на этапе сборки. Все оптимизации происходят заранее.
🔵 Мгновенный старт: Нет JVM и "прогрева"! Нативные образы стартуют мгновенно (от долей секунды). Идеально для:
🔵 Серверлес-функций (Lambda, FaaS): Быстрый старт снижает задержки и стоимость.
🔵 Микросервисов: Для быстрого масштабирования.
🔵 Batch-задач: Быстрый запуск и завершение.
🔵 Низкое потребление памяти: Отсутствие JVM значительно сокращает RAM. Экономия облачных ресурсов и возможность запускать больше приложений на сервере.
🔵 Меньший размер образа: Включается только используемый код без JVM, что упрощает развертывание в контейнерах.
🔵 Статический анализ: GraalVM глубоко анализирует код, исключая неиспользуемые части.
Недостатки (куда без них?):
🔵 Длительное время сборки: AOT-компиляция занимает значительно больше времени.
🔵 Сложности с динамикой: Рефлексия и прокси требуют дополнительных настроек (runtime hints), хотя Spring Boot 3 упрощает это.
🔵 Отсутствие некоторых инструментов мониторинга: Привычные JVM-инструменты (JMX, JFR) не поддерживаются напрямую.
🔵 Специфичность платформы: Нужен отдельный образ для каждой ОС и архитектуры.
Когда стоит использовать Spring Native Image?
🔵 Когда время старта и потребление памяти критичны.
🔵 Для микросервисов, серверлес-функций и batch-задач.
🔵 Для контейнеров и облачных сред с оплатой по потреблению.
👉 @java_geek
Разбираемся, как нативные образы меняют правила игры для Spring-приложений! 👇
Обычное Spring-приложение (JVM-based):
Когда вы запускаете классический Spring Boot на Java Virtual Machine (JVM), происходит следующее:
Spring Native Image (с GraalVM Native Image):
Это компиляция вашего Spring-приложения в самостоятельный нативный исполняемый файл, не требующий JVM!
Недостатки (куда без них?):
Когда стоит использовать Spring Native Image?
👉 @java_geek
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
VK приглашает на Java AI meetup 9 июля. Обещают доклады топовых спикеров из AI-департамента, тематическую дискуссию и много полезных знакомств в индустрии.
Подробнее — здесь.
Подробнее — здесь.
🧠 Неожиданный анти-паттерн в Spring Boot:
В Spring удобно использовать
📌 По умолчанию все
Это значит:
А теперь представим, что слушатель делает что-то тяжёлое:
⚠️ Если
💡 Решение: сделать обработку асинхронной.
Просто добавьте
✅ И не забудьте включить поддержку
📌 Теперь
👉 @java_geek
@EventListener
как тихий убийца производительностиВ Spring удобно использовать
@EventListener
для реактивных действий — казалось бы, удобно и "чисто". Но есть нюанс.📌 По умолчанию все
@EventListener
вызываются синхронно и в том же потоке, где был опубликован ивент через ApplicationEventPublisher
.Это значит:
@Component
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
public void createOrder(Order order) {
// 1. Сохраняем заказ
orderRepository.save(order);
// 2. Публикуем ивент
publisher.publishEvent(new OrderCreatedEvent(order));
// 3. До выхода из метода все @EventListener уже будут выполнены
}
}
А теперь представим, что слушатель делает что-то тяжёлое:
@EventListener
public void sendEmail(OrderCreatedEvent event) {
emailService.sendConfirmation(event.getOrder());
}
⚠️ Если
sendConfirmation
висит на внешнем SMTP или уходит в сеть — метод createOrder
будет ждать его завершения!💡 Решение: сделать обработку асинхронной.
Просто добавьте
@Async
:
@Async
@EventListener
public void sendEmail(OrderCreatedEvent event) {
emailService.sendConfirmation(event.getOrder());
}
✅ И не забудьте включить поддержку
@Async
:
@EnableAsync
@Configuration
public class AsyncConfig {}
📌 Теперь
@EventListener
будет исполняться в отдельном потоке из TaskExecutor
, и createOrder
не будет блокироваться.👉 @java_geek
👍6❤1