Джава Ван Лав
406 subscribers
19 photos
3 videos
23 links
Java, Kotlin, Spring Boot, JVM, архитектура, Kubernetes, PostgreSQL и немного здравого смысла.

YouTube: https://www.youtube.com/@rustam-kuramshin
Хабр: https://habr.com/ru/users/RustamKuramshin/articles/
Для связи: @KuramshinRustam
Download Telegram
Channel name was changed to «Джава Ван Лав»
Channel photo updated
🇮🇩 ATTENTION, PEOPLE OF INDONESIA!

This is NOT a channel about the island of Java.

I repeat:

❌ No volcanoes.
❌ No travel guides.
❌ No beaches.
❌ No hotels.
❌ No geography.

Only:

☕ Java
☕ Spring
☕ JVM
☕ PostgreSQL
☕ Kubernetes
☕ Backend Engineering

Thousands of developers. Zero tourism.

If you’re looking for the island — my apologies.

If you’re looking for software engineering — welcome to Java One Love
😁14🔥3❤‍🔥2👍1🤩1
Джава Ван Лав pinned «🇮🇩 ATTENTION, PEOPLE OF INDONESIA! This is NOT a channel about the island of Java. I repeat: ❌ No volcanoes. ❌ No travel guides. ❌ No beaches. ❌ No hotels. ❌ No geography. Only: ☕ Java ☕ Spring ☕ JVM ☕ PostgreSQL ☕ Kubernetes ☕ Backend Engineering Thousands…»
Forwarded from Jmix.ru
2023 — попробуйте ChatGPT.
2024 — попробуйте генерировать код.
2025 — попробуйте ИИ-агентов.
2️⃣0️⃣2️⃣6️⃣ — пора разобраться, как встроить ИИ в управляемый процесс разработки.
 
Сегодня вопрос уже не в том, использовать ли ИИ в разработке.
Вопрос в другом: как с его помощью создавать реальные бизнес-системы и не превращать инженерный проект в творческое изделие?
 
В апреле мы провели вебинар «Вайб-кодинг в энтерпрайз: 5 блокеров и путь к управляемой разработке».
Можно пересмотреть — VK Video / YouTube.

Теперь переходим от концепции к практике. ⬇️
 
📆 16 июня в 16:00 по МСК
🛠 проведем бесплатный воркшоп: «Создаем B2B CRM с ИИ на Java».
 
На примере open-source CRM покажем, как пройти путь от спецификации до первого рабочего контура корпоративного приложения.
 
Разберем:
⏩ как поставить задачу ИИ-агенту и удерживать его в рамках проекта;
⏩как создавать модель данных, экраны и бизнес-логику;
⏩как использовать документацию, скиллы и проверки IDE;
⏩какие ошибки типичны для агентного режима;
⏩чем управляемая ИИ-разработка отличается от вайб-кодинга.
 
Воркшоп проведут:
▶️ Виктор Фадеев, руководитель продукта Джеймикс;
▶️ Дмитрий Ващенко, ведущий тренер Джеймикс;
▶️ Дмитрий Змитрович, CEO Kodacode.
 
Если вам интересен не очередной разговор про ИИ, а практический сценарий разработки корпоративного Java-приложения — ждем вас!
 
📌 Регистрируйтесь.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍1
Reflection в Java: временно выходим из правил языка

Reflection часто описывают как API, через который можно узнать поля класса, найти метод по имени или вызвать private-конструктор. Такое описание верное, но почти ничего не объясняет.

Главная идея reflection связана с устройством JVM.

После компиляции Java-код превращается в class-файлы с метаданными о типах, методах, полях, модификаторах и связях между классами. Во время загрузки классов JVM собирает из этих данных модель выполняемой программы.

Без нее JVM не смогла бы определить, какую реализацию size() вызвать здесь:

List values = getValues();
values.size();


Тип объекта и иерархия его класса известны JVM во время выполнения. Reflection дает Java-коду доступ к части этих метаданных.

Например, мы можем загрузить класс, найти конструктор и вызвать метод, хотя конкретный тип не был известен во время компиляции:

Class type = Class.forName(className);

Constructor constructor = type.getConstructor();
Object instance = constructor.newInstance();

Method method = type.getMethod(“process”, String.class);
Object result = method.invoke(instance, “data”);


На этой возможности построены dependency injection, ORM, сериализация, тестовые библиотеки и многие другие механизмы Java-экосистемы.

Но у такой гибкости есть цена.

При обычном вызове компилятор заранее проверяет тип объекта, сигнатуру метода, аргументы и доступность. В рефлексивном коде значительная часть этих проверок переносится в runtime.

Из-за этого появляются знакомые особенности Core Reflection API:

- метод ищется по имени и массиву типов аргументов
- параметры передаются как Object[]
- примитивы требуют boxing и unboxing
- результат Method.invoke() возвращается как Object
- ошибка внутри вызываемого метода оборачивается в InvocationTargetException
- проблемы с сигнатурой или доступом обнаруживаются во время выполнения
- setAccessible(true) позволяет обойти часть правил инкапсуляции

Можно сказать, что рефлексивный код написан на Java, но работает по правилам динамической среды JVM. Многие гарантии статической типизации в этот момент исчезают.

Насколько reflection медленнее обычного вызова?

Универсального коэффициента здесь нет.

Рефлексивный вызов может включать создание массива аргументов, boxing, проверки доступа и дополнительные уровни косвенного вызова. JIT-компилятору также сложнее анализировать такой call site и выполнять inlining.

Результат зависит от конкретного кода:

- ищется ли Method при каждом вызове или хранится в кеше
- известен ли JVM конкретный target
- насколько часто выполняется вызов
- сколько работы делает вызываемый метод
- какая версия JDK используется
- находится ли reflection в горячем участке приложения

Разница хорошо заметна в цикле, где миллионы раз вызывается крошечный getter. В коде инициализации Spring-контекста или сериализации объекта рядом с сетевым запросом затраты могут потеряться на фоне остальной работы.

Поэтому бенчмарк вида «рефлекшн медленнее на 23%» почти ничего не говорит о производительности приложения. Микробенчмарк всегда выдаст число. Вопрос в том, описывает ли это число реальную нагрузку.

Есть и исторический нюанс. До Java 18 reflection сначала использовал native-вызовы, а после достижения порога генерировал специальный bytecode accessor. Начиная с Java 18 реализация Method, Constructor и Field переведена на method handles. Публичный API сохранился, внутренний механизм изменился. Подробнее об этом можно прочитать в JEP 416:

https://openjdk.org/jeps/416

Если хочется глубже разобраться в reflection, у Бена Эванса есть две отличные статьи. Первая объясняет модель и API, вторая разбирает внутреннюю реализацию и влияние рефлекшн вызовов на производительность:

Reflection for the modern Java programmer
https://blogs.oracle.com/javamagazine/java-reflection-introduction/

The performance implications of Java reflection
https://blogs.oracle.com/javamagazine/java-reflection-performance/
👍10🔥7👏2
73 минуты истории Java: от Oak до virtual threads

Все уже наверное слышали. На канале CultRepo вышел документальный фильм The Java Story. И это, пожалуй, самая полная экранизация истории Java из тех, что у нас теперь есть.

История начинается задолго до enterprise-разработки, Spring и обсуждений Hibernate. Java - тогда ещё Oak - создавалась внутри проекта Green для бытовых вычислительных устройств и интерактивного телевидения.

Проект проиграл ключевой тендер, команду практически распустили, а сама технология оказалась никому не нужна. Затем появился браузер Mosaic - и инженеры Sun поняли, что Oak можно использовать для запуска интерактивных приложений прямо на веб-страницах.

Так начался один из самых удачных пивотов в индустрии.

Дальше фильм проходит почти по всей биографии платформы:
- интеграция с Netscape и внезапный взлёт Java в 1995 году
- попытка Microsoft создать несовместимую версию Java для Windows;
- рождение Spring и Hibernate как реакции на эту сложность
- открытие исходников Java и создание OpenJDK
- Java 8, которая фактически стала последним шансом вернуть языку развитие
- переход на шестимесячный цикл релизов
- Loom, virtual threads, Valhalla, Panama и дальнейшая эволюция JVM

Интернсен фрагмент о появлении Spring и Hibernate. Сегодня они воспринимаются как естественная часть Java-экосистемы, но из фильма хорошо видно, что оба проекта выросли из несогласия с официальным направлением развития энтерпрайзной Java. Open source-сообщество предложило более практичные решения - а затем уже сама платформа начала заимствовать найденные ими идеи.

Историю рассказывают люди, которые непосредственно её создавали: Джеймс Гослинг, Джошуа Блох, Брайан Гётц, Марк Рейнхольд, Род Джонсон, Гэвин Кинг, создатель Tomcat Джеймс Дункан Дэвидсон, создатель Kotlin Андрей Бреслав и многие другие.

Получилась история того, как Java несколько раз оказывалась на грани провала, меняла направление и при этом умудрялась сохранять совместимость с огромной экосистемой.

Если вы занимаетесь Java-разработкой, но знаете историю Java в основном отдельными фрагментами, эти 73 минуты стоит посмотреть:
https://www.youtube.com/watch?v=ZqGSg4b_cZA
🔥5👍1
Фильтр Блума: множество, которое иногда ошибается

Представим сервис с миллионами идентификаторов. Нам нужно быстро отвечать на вопрос: встречался ли такой ID раньше?

Хранить все значения в условном HashSet может быть слишком дорого. Если точный положительный ответ не обязателен, можно использовать фильтр Блума.

Он возвращает один из двух результатов: точно отсутствует и возможно присутствует.

Ложноположительный ответ возможен. Ложноотрицательный - нет. Если фильтр говорит, что элемента не было, значит его действительно не добавляли.

Как он устроен

Внутри находится массив битов и несколько хеш-функций.

При добавлении элемента вычисляется несколько позиций:

"java" -> [2, 7, 12]

0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0
↑ ↑ ↑


Биты в этих позициях устанавливаются в 1.

При проверке элемента фильтр снова вычисляет те же позиции. Если хотя бы один бит равен нулю, элемента точно нет.

boolean mightContain(String value) {
for (int index : indexes(value)) {
if (!bits.get(index)) {
return false;
}
}
return true;
}


Если все биты установлены, элемент, вероятно, добавляли. Те же позиции могли независимо занять другие значения, поэтому гарантировать наличие нельзя.

Как выглядела бы реализация на Java

Для хранения битов подойдет BitSet. Методу add() нужно вычислить несколько индексов и установить соответствующие биты. Метод mightContain() вычисляет те же индексы и проверяет, что каждый бит установлен.

Запускать отдельную хеш-функцию для каждого индекса необязательно. Обычно используют double hashing: вычисляют два хеша, а остальные получают из их комбинаций.

При создании фильтра задаются два параметра:

BloomFilter filter = new BloomFilter(1_000_000, 0.01);


Первый параметр - ожидаемое число элементов. Второй - допустимая вероятность ложного срабатывания.

Для миллиона элементов и вероятности ошибки 1% потребуется около 9,6 миллиона бит, то есть примерно 1,14 МБ. Оптимальное количество хешей в этом случае равно семи.

filter.add("order-42");

filter.mightContain("order-42"); // true
filter.mightContain("order-99"); // скорее всего false


В реальном проекте писать такую структуру самостоятельно обычно не требуется. Например, готовая реализация есть в Guava:

BloomFilter<String> filter = BloomFilter.create(
Funnels.stringFunnel(StandardCharsets.UTF_8),
1_000_000,
0.01
);


Где это применяют

Фильтр Блума ставят перед обращением к диску, базе данных или удаленному сервису. Если он вернул false, дорогой запрос можно пропустить. Ответ true означает, что данные нужно проверить в основном хранилище.

Например, фильтр может содержать ключи, существующие в базе. Запроса с заведомо отсутствующим ключом до базы тогда не дойдет.

У структуры есть ограничения. Обычный фильтр Блума не умеет удалять элементы: сброс одного бита может нарушить проверку других значений. Для удаления применяют Counting Bloom Filter, где вместо битов используются счетчики.

Еще одна проблема - переполнение. Если добавить намного больше элементов, чем планировалось, доля единичных битов вырастет. Вместе с ней быстро увеличится количество ложных срабатываний.
👍9🔥3❤1⚡1
Forwarded from Spring АйО (Rustam Kuramshin)
⚙️ В HTTP появился метод QUERY. Пора прощаться с POST /search?

У бэкенд-разработчиков много лет был довольно неловкий выбор.

Пока фильтров мало, используем GET:

GET /orders?page=2&status=PAID&sort=createdAt,desc


Потом поиск усложняется. Появляются диапазоны дат, группы условий, вложенные фильтры. Query string постепенно превращается в плохо читаемый DSL. Заодно приходится учитывать ограничения на длину URI и то, что адрес запроса может попасть в логи.

Обычно в этот момент появляется такой эндпойнт:

POST /orders/search
Content-Type: application/json

{
"status": ["PAID", "SHIPPED"],
"createdAt": {
"from": "2026-06-01",
"to": "2026-06-30"
},
"customer": {
"country": "NL"
}
}


Решение рабочее. POST вообще не ограничен созданием ресурсов. Сервер может обрабатывать его тело по собственной семантике.

Проблема в другом: на уровне HTTP POST считается потенциально небезопасным и не идмептентным. Прокси, клиенты и другая инфраструктура не знают, что наш /search только читает данные. Такой запрос нельзя автоматически повторять или полноценно кэшировать без дополнительных договоренностей.

Передать тело в GET технически возможно. Но RFC 9110 говорит, что у содержимого GET-запроса нет общепринятой семантики. Некоторые серверы и промежуточные узлы могут отклонить такой запрос. Среди причин упоминаются даже атаки класса request smuggling.

В июне 2026 года IETF опубликовала RFC 10008 с новым методом QUERY:

QUERY /orders HTTP/1.1
Content-Type: application/json
Accept: application/json

{
"status": ["PAID", "SHIPPED"],
"total": {
"min": 100,
"max": 1000
}
}


QUERY предназначен для запросов, описание которых передается в теле. Формат может быть любым, если он обозначен через Content-Type: JSON, SQL, JSONPath или собственный язык фильтрации.

Спецификация закрепляет за QUERY три свойства:

- safe - клиент не запрашивает изменение состояния целевого ресурса
- idempotent - запрос можно безопасно повторить
- cacheable - ответ разрешено кэшировать

Для кэша есть отдельное правило: ключ должен учитывать тело запроса и связанные с ним метаданные. Поэтому существующие CDN и reverse proxy не начнут кэшировать QUERY сами по себе. Им потребуется поддержка нового метода.

QUERY также умеет работать с conditional requests. А сервер может сообщить о допустимых форматах через новый заголовок Accept-Query.

👩‍💻 Что со Spring?

В актуальном Spring Framework полноценной поддержки QUERY пока нет. Проблема находится именно на уровне фреймворка: в RequestMethod отсутствует такое значение, поэтому обычный @RequestMapping нельзя привязать к QUERY.

Обойти ограничение можно через functional endpoints, собственный HandlerMapping или ручную проверку метода. Для нового стандарта это слишком много самодельной инфраструктуры.

Сейчас открыт PR с поддержкой RFC 10008. Команда Spring рассчитывает подготовить ее к Spring Framework 7.1, который ожидается в ноябре 2026 года. Точная версия пока не гарантирована.

Так что переписывать все POST /search сегодня рано. Ждем нормальной поддержки в Spring, клиентах и прокси. Потом уже можно будет с чистой совестью бежать менять поисковые POST-запросы на QUERY.

RFC 10008:
https://www.rfc-editor.org/info/rfc10008/

Spring Framework PR:
https://github.com/spring-projects/spring-framework/pull/34993
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8👍1
Forwarded from Spring АйО (Rustam Kuramshin)
Anthropic опубликовали AI-Native SDLC плейбук

Его основная мысль: агенты уже способны генерировать код намного быстрее человека, но окружающие процессы почти не изменились. Планирование, согласования, тестирование, проверка безопасности и выпуск по-прежнему работают с человеческой скоростью.

Узкое место просто переместилось из написания кода в соседние этапы.

Anthropic называет предлагаемую модель AI-native SDLC. Привычный линейный процесс превращается в замкнутый цикл, внутри которого каждый этап создает версионируемый артефакт для следующего:

intent.md -> spec.md -> plan.md -> код и тесты -> PR с результатами проверок -> отчет об инциденте или новый intent.md

Все это хранится в Git. История коммитов одновременно становится журналом изменений: кто сформулировал задачу, что предложил агент, какие решения принял человек и что в итоге попало в продакшен.

Как выглядит сам процесс?

На этапе планирования автор идеи обсуждает проблему с Claude. Результат фиксируется в intent.md: что требуется изменить, зачем, какие системы затрагиваются и какие ограничения нужно учитывать. Владелец продукта проверяет документ и принимает решение о продолжении работы.

Дальше агент превращает намерение в spec.md с требованиями и проектным решением. Политики безопасности, архитектурные правила и требования к интерфейсу подключаются через Skills. Спорные места агент должен явно отметить, а человек - разрешить до начала реализации.

Разработка начинается с plan.md. В нем перечисляются изменяемые файлы, порядок работы, риски и проверки результата. После утверждения плана агент пишет код. Контекст проекта хранится в CLAUDE.md, повторяемые правила оформляются как Skills, а обязательные ограничения обеспечиваются через hooks.

Для тестирования предлагается два уровня. Первый - обычные сборка, линтеры и автоматические тесты, которые агент обязан запустить перед завершением задачи. Второй - evals для проверки самого агента. Если команда меняет модель, системные инструкции, Skills или hooks, набор реальных задач должен показать, что качество работы не ухудшилось.

Во время развертывания агент проверяет PR, исправляет замечания и готовит релиз. Человек оценивает соответствие исходному замыслу и уровень риска. Критические действия, включая выпуск в продакшен, защищаются программными ограничениями и явными согласованиями.

Эксплуатация замыкает цикл. Детерминированный мониторинг обнаруживает выход метрик за допустимые границы, после чего агент подключается к диагностике. Небольшая проблема может закончиться PR или откатом. Более крупная превращается в новый intent.md и снова проходит весь процесс.

Роль инженера в такой модели заметно меняется. Больше внимания уходит на формулирование намерения, проектирование ограничений, проверку артефактов и оценку риска. Стабильность процесса обеспечивают тесты, CI, изоляция, права доступа и автоматические шлюзы.

Плейбук сильно привязан к Claude Code и продуктам Anthropic, однако сама схема применима шире. Главная идея заключается в перестройке всего процесса разработки вокруг работы агентов, а не в добавлении генерации кода в одну из существующих стадий.

Плейбук в блоге Anthropic:
https://claude.com/blog/the-ai-native-sdlc-playbook

Какой подход при разработке с ИИ-агентами используете вы в рабочих проектах? Пишите в комментариях 😎
🔥6✍2❤2👍2
Forwarded from Spring АйО (Rustam Kuramshin)
JEP 544: JVM будет сохранять машинный код между запусками

JEP 544 (Ahead-of-Time Code Compilation) предложен в JDK 28, релиз в марте 2027. Это очередной слой AOT-кэша из Project Leyden. В JDK 24 туда попали загруженные и слинкованные классы (JEP 483), в JDK 25 - профили методов (JEP 515). Теперь дошла очередь до самого машинного кода.

Как это работает

На обучающем запуске JVM компилирует горячие методы обычными C1 и C2 и складывает результат в кэш. В рабочем запуске запрос на компиляцию метода закрывается готовым кодом. Процедура та же, что и раньше: -XX:AOTCacheOutput при обучении, -XX:AOTCache в проде. Включать ничего не нужно.

Код из кэша остаётся обычным кодом HotSpot. Если рабочая нагрузка ушла в сторону от обучающей, метод деоптимизируется и перекомпилируется JIT-ом, как любой другой. Профили из JEP 515 при этом продолжают работать: по ним JVM выбирает порядок загрузки кода и пересобирает методы.

Чем AOT-код хуже JIT-кода

JIT компилирует метод, когда нужные классы давно инициализированы. Он подставляет значения static final полей как константы и выбрасывает проверки инициализации. Код из кэша начинает работать раньше, чем классы успели инициализироваться, а значение static final может отличаться от запуска к запуску. Поле приходится читать по-настоящему.

Для методов, которые обращаются к статике других классов, C2 собирает две версии: медленную с проверками инициализации и быструю без них.

Поэтому JIT никуда не уходит. AOT-код даёт быстрый старт, до пика доводит перекомпиляция.

Когда кэш не сработает

Обучающий и рабочий запуски должны совпадать по архитектуре процессора, набору инструкций и сборщику мусора: барьеры GC вшиты в машинный код. Собрали образ на хотсе с AVX-512, pod уехал на узел без него - код из кэша не загрузится. Ошибки не будет, JVM молча перейдёт на интерпретатор и JIT. В кластере с разнородными узлами это легко пропустить.

Поддерживаются x64 и AArch64, из сборщиков - Serial, Parallel, G1 и ZGC.

Цифры

По данным JEP, AOT-кэш без кода сокращает время старта на 50-70%, с кодом - на 65-80%. Основную часть выигрыша на старте дают уже вышедшие JDK 24-26, новый JEP добавляет 10-15 п.п.

Заметнее эффект на прогреве. В PR есть замер на Quarkus-сервисе с двумя ядрами: обычная JVM начинает отвечать быстрее 1 мс после 420 запросов, JDK 26 с AOT-кэшем - примерно после 200, сборка с JEP 544 - чуть позже сотого.

Второй эффект касается CPU. JIT на старте перестаёт отбирать ядра у приложения, и сильнее всего это видно на контейнерах с лимитом в 1-2 CPU. Там, где запас по CPU закладывали под прогрев, его можно будет пересмотреть.

Оценить вклад кода на своём сервисе можно диагностическим флагом:
-XX:+UnlockDiagnosticVMOptions -XX:-AOTCodeCaching

Заодно станет видно, насколько вырос файл кэша.

Что остаётся неудобным

Обучающий запуск. Кэш хорош настолько, насколько нагрузка при сборке похожа на боевую, а готового инструмента для этого нет. Spring Boot и Quarkus умеют записывать кэш при сборке, но прогон реальных сценариев остаётся на вашей стороне. Упрощение этого процесса авторы JEP прямо вынесли за рамки.

JEP: https://openjdk.org/jeps/544
👍5🔥4