Java for Beginner
777 subscribers
758 photos
220 videos
12 files
1.28K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Что выведет код?

public class Task031225 {
public static void main(String[] args) {
try {
System.out.print("A");
throw new RuntimeException("1");
} catch (RuntimeException e) {
System.out.print("B");
throw new RuntimeException("2");
} finally {
System.out.print("C");
throw new RuntimeException("3");
}
}
}


#Tasks
👍1
Вопрос с собеседований

Как работает ConcurrentSkipListMap? 🤓


Ответ:

Это сортированная конкурентная карта, основанная на skip-list структуре.

Обеспечивает логарифмическое время операций и безопасный доступ из нескольких потоков без полного блокирования.

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



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ребят продолжаю искать желающих изучить Java!

Пишите будет интересно 🤟
История IT-технологий сегодня — 04 декабря


ℹ️ Кто родился в этот день

Эрик Стивен Рэймонд (родился 4 декабря 1957 года)американский программист и писатель, один из идеологов движения open source, автор книги «The Cathedral and the Bazaar» — важного манифеста для сообщества свободного ПО.


🌐 Знаковые события

1948 — Государственный комитет Совета министров СССР по внедрению передовой техники в народное хозяйство зарегистрировал за номером 10475 изобретение И. С. Бруком и Б. И. Рамеевым цифровой электронной вычислительной машины.


#Biography #Birth_Date #Events #04Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Глава 2. List — списки

Методы remove, contains

Операции remove и contains находятся в тесной концептуальной взаимосвязи — обе требуют поиска элемента в коллекции, но с разными целями и последствиями. В то время как contains выполняет пассивную проверку наличия элемента, remove осуществляет активное извлечение элемента с последующей реструктуризацией коллекции. Это различие в целях приводит к существенным различиям в сложности и поведении операций, особенно в контексте различных реализаций List.


Метод contains: проверка существования элемента


Метод contains выполняет фундаментальную операцию проверки принадлежности элемента к коллекции. Эта операция основана на семантике равенства, определяемой методом equals() объектов, и требует полного или частичного обхода коллекции в зависимости от ее внутренней структуры.


ArrayList: линейный поиск в массиве

ArrayList, основанный на динамическом массиве, не предоставляет специализированных структур данных для ускорения операций поиска. В результате операция contains требует последовательного обхода всех или части элементов массива.

Детальный процесс выполнения contains(element)

Фаза инициализации поиска:
Операция начинается с анализа входного элемента. Если передан null, поиск будет вестись среди null элементов коллекции. Если передан не-null объект, будет использоваться его метод equals() для сравнения.

Фаза последовательного обхода:
Система инициирует последовательный обход внутреннего массива elementData от индекса 0 до size-1.

Для каждого элемента выполняются следующие шаги:
Извлечение текущего элемента из массива по индексу i
Проверка на null текущего элемента


Сравнение элементов:
Если оба элемента null → совпадение найдено
Если текущий элемент не null и equals(element) возвращает true → совпадение найдено
Иначе → переход к следующему элементу


Фаза завершения поиска:
Как только найдено совпадение, операция немедленно возвращает true. Если обход завершен без нахождения совпадения, возвращается false.

Оптимизации и особенности

Ранний выход:
Основная оптимизация заключается в возможности раннего выхода при нахождении первого совпадения.

Отсутствие структурных изменений:
Операция contains не модифицирует внутреннюю структуру данных и не влияет на счетчик модификаций (modCount).

Влияние порядка элементов:
Время выполнения зависит от позиции искомого элемента в массиве. Элементы в начале находятся быстрее, чем элементы в конце.

Временная сложность
Операция contains в ArrayList имеет временную сложность O(n) в худшем случае, где n — количество элементов в списке. В среднем случае, при равномерном распределении позиций элементов, сложность составляет O(n/2) = O(n).


LinkedList: последовательный обход цепочки узлов

LinkedList, реализованный как двусвязный список, требует полного обхода цепочки узлов для выполнения операции contains, так как не поддерживает прямой доступ к элементам по значению.

Детальный процесс выполнения contains(element)

Фаза инициализации:
Как и в ArrayList, операция начинается с анализа входного элемента и подготовки к использованию метода equals().

Фаза последовательного обхода узлов:
Система начинает обход с головного узла (head) и последовательно перемещается по цепочке:
Извлечение элемента из текущего узла (node.item)
Проверка на null извлеченного элемента
Сравнение элементов с использованием семантики equals()
Переход к следующему узлу через
node.next

Фаза завершения:
Операция возвращает true при первом найденном совпадении или false после полного обхода всех узлов.

Особенности производительности

Отсутствие индексации:
В отличие от ArrayList, LinkedList не может использовать преимущества случайного доступа для ускорения поиска.

Худший случай:
Требуется полный обход всех n узлов, если элемент отсутствует или находится в конце списка.

Лучший случай:
Элемент находится в головном узле, что требует всего одного сравнения.

Временная сложность
Операция contains в LinkedList имеет временную сложность O(n) в худшем и среднем случае, аналогично ArrayList.


#Java #для_новичков #beginner #List #ArrayList #LinkedList #remove #contains
👍2
Сравнительный анализ contains в ArrayList и LinkedList

Количественные характеристики


Время выполнения:
ArrayList: 5-10 наносекунд на сравнение × количество сравнений
LinkedList: 10-20 наносекунд на узел × количество узлов


Потребление памяти:
ArrayList: Минимальное — только локальные переменные
LinkedList: Минимальное — временные переменные обхода


Качественные различия

Локальность памяти:
ArrayList: Отличная — последовательный доступ к непрерывному блоку памяти
LinkedList: Плохая — случайные доступы к узлам в куче


Влияние кэширования:
ArrayList: Высокое — предсказуемый доступ улучшает prefetching
LinkedList: Низкое — непредсказуемые переходы между узлами



Метод remove: удаление элементов

Метод remove выполняет одну из наиболее сложных операций в интерфейсе List — извлечение элемента из коллекции с последующей реструктуризацией внутренней структуры данных. Эта операция существует в двух основных вариантах: удаление по индексу (remove(int index)) и удаление по значению (remove(Object o)).


Удаление по индексу в ArrayList

Удаление элемента из ArrayList требует не только извлечения значения, но и структурной реорганизации — сдвига всех последующих элементов для заполнения образовавшейся пустоты.


Детальный процесс выполнения remove(index)


Фаза валидации:
Происходит проверка корректности индекса — он должен находиться в диапазоне [0, size-1].

Фаза извлечения значения:
Система сохраняет элемент из указанной позиции массива для последующего возврата.

Фаза сдвига элементов:
Начинается критически важный процесс реорганизации массива:
// Концептуальное представление сдвига
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
Этот сдвиг требует копирования всех элементов, находящихся правее удаляемой позиции, на одну позицию влево.


Фаза очистки и обновления:
Последняя позиция массива (elementData[size-1]) устанавливается в null
Размер списка уменьшается (size--)
Счетчик модификаций увеличивается (modCount++)


Возврат значения:
Сохраненный элемент возвращается вызывающему коду.

Стоимость операции

Временная сложность:
Удаление по индексу в ArrayList имеет временную сложность O(n) в худшем случае, где n — количество элементов.

Сложность зависит от позиции удаляемого элемента:
Удаление последнего элемента: O(1)
Удаление первого элемента: O(n)
Средний случай: O(n/2) = O(n)


Потребление памяти:
Операция не требует дополнительного выделения памяти, но может создавать временные объекты при копировании.


Удаление по индексу в LinkedList

Удаление элемента из LinkedList включает два основных этапа: поиск целевого узла и перестройку ссылок соседних узлов.

Детальный процесс выполнения remove(index)

Фаза валидации:
Проверка корректности индекса аналогично ArrayList.

Фаза поиска узла:
В зависимости от положения индекса выбирается стратегия поиска:
Для первой половины списка: обход от головы
Для второй половины: обход от хвоста


Фаза изоляции узла:
После нахождения целевого узла выполняется операция "вырезания" его из цепочки:
Сохранение ссылок: Запоминаются ссылки на предыдущий (prev) и следующий (next) узлы

Обновление связей:
Если есть предыдущий узел, его next ссылка устанавливается на следующий узел
Если есть следующий узел, его prev ссылка устанавливается на предыдущий узел

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


Фаза очистки и обновления:
Ссылки удаляемого узла обнуляются (item, next, prev) для помощи garbage collector
Размер списка уменьшается
Счетчик модификаций увеличивается


Возврат значения:
Элемент из удаленного узла возвращается вызывающему коду.

Стоимость операции

Временная сложность:
Удаление по индексу в LinkedList имеет сложность O(n) из-за необходимости поиска узла. Однако сам процесс "вырезания" узла выполняется за O(1).

Распределение стоимости:
Поиск узла: O(n)
Перестройка ссылок: O(1)



#Java #для_новичков #beginner #List #ArrayList #LinkedList #remove #contains
👍2
Удаление по значению в ArrayList

Операция remove(Object o) в ArrayList сочетает в себе линейный поиск (аналогично contains) и структурную реорганизацию (аналогично удалению по индексу).


Детальный процесс выполнения remove(element)

Фаза поиска:
Выполняется последовательный обход массива от начала до конца для поиска первого вхождения элемента. Поиск использует семантику equals() и специально обрабатывает null значения.

Фаза удаления:
Если элемент найден на позиции i:
Сохраняется значение для возврата
Выполняется сдвиг элементов от i+1 до size-1 на одну позицию влево


Последняя позиция обнуляется
Размер уменьшается, modCount увеличивается

Особенности:
Удаляется только первое вхождение элемента
Если элемент не найден, возвращается false
Если элемент найден и удален, возвращается true


Временная сложность

Операция имеет сложность O(n) в худшем случае:
Поиск: O(n) в худшем случае
Удаление: O(n) в худшем случае (при удалении из начала)



Удаление по значению в LinkedList

Процесс аналогичен удалению по индексу, но с предварительным поиском по значению.


Детальный процесс

Фаза поиска:
Последовательный обход узлов от головы до хвоста с использованием equals() для сравнения.

Фаза удаления:
При нахождении узла с совпадающим значением выполняется операция "вырезания" аналогично удалению по индексу.

Особенности:
Удаляется только первый найденный узел с совпадающим значением
Возвращает true при успешном удалении, false при отсутствии элемента


Сравнительный анализ операций удаления

Временная сложность

Удаление по индексу:
ArrayList: O(n) в худшем случае (удаление из начала)
LinkedList: O(n) в худшем случае (удаление из середины)

Удаление по значению:
ArrayList: O(n) поиск + O(n) удаление = O(n)
LinkedList: O(n) поиск + O(1) удаление = O(n)



Потребление памяти

ArrayList:
Не требует дополнительной памяти (кроме временных переменных)
Может оставлять "пустоты" в массиве (обнуленные ссылки)


LinkedList:
Освобождает память узла (24-32 байта на узел)
Требует времени garbage collector для очистки


Многопоточные аспекты

Проблемы конкурентного доступа

Несинхронизированные реализации:
Обе операции не являются thread-safe и могут привести к:
Состояниям гонки при одновременных модификациях
Повреждению внутренних структур данных
Непредсказуемому поведению итераторов


Стратегии синхронизации

Явная синхронизация:
synchronized(list) {
if (list.contains(element)) {
list.remove(element);
}
}


Thread-safe альтернативы:
CopyOnWriteArrayList для сценариев "частое чтение, редкая запись"
Collections.synchronizedList() с external locking
Concurrent коллекции для специализированных use cases


Memory consistency
Операции remove и contains требуют proper memory barriers для обеспечения visibility изменений между потоками. Без синхронизации нет гарантий, что один поток увидит изменения, сделанные другим потоком.



#Java #для_новичков #beginner #List #ArrayList #LinkedList #remove #contains
👍1
Оптимизации и специализированные сценарии

Эффективные паттерны использования

Для ArrayList:
Удаление с конца более эффективно, чем с начала
Пакетное удаление может быть оптимизировано через создание нового списка
Использование removeAll(Collection) для массовых операций


Для LinkedList:
Удаление из начала/конца значительно эффективнее, чем из середины
Использование removeFirst()/removeLast() для операций на концах
Итераторные методы более эффективны для последовательного удаления


Избегание неэффективных паттернов

Антипаттерны:
Частые удаления из начала ArrayList
Использование contains перед remove без необходимости (двойной обход)
Игнорирование возможности использования Iterator.remove()


Оптимизированные подходы:
// Вместо:
if (list.contains(element)) {
list.remove(element); // Двойной обход
}

// Использовать:
boolean removed = list.remove(element); // Один обход с ранним выходом


Влияние на итераторы

Fail-fast семантика

Обе операции изменяют modCount, что влияет на поведение итераторов:
Любое изменение списка invalidates все активные итераторы
Последующие операции итератора вызывают ConcurrentModificationException
Только Iterator.remove() может безопасно удалять элементы во время итерации


Безопасное удаление во время итерации

Правильный подход:

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (shouldRemove(element)) {
iterator.remove(); // Безопасное удаление
}
}


Неправильный подход:
for (String element : list) {
if (shouldRemove(element)) {
list.remove(element); // ConcurrentModificationException
}
}



Производительность в реальных сценариях

Сравнение операций contains:
ArrayList: 5-100 нс в зависимости от позиции элемента
LinkedList: 10-200 нс в зависимости от позиции и размера списка


Сравнение операций remove:
ArrayList (удаление из конца): 10-20 нс
ArrayList (удаление из начала): 100-5000 нс для 1000 элементов
LinkedList (удаление из конца): 10-30 нс
LinkedList (удаление из начала): 10-30 нс
LinkedList (удаление из середины): 50-5000 нс для 1000 элементов


Влияние размера коллекции

Малые коллекции (до 100 элементов):
Различия между ArrayList и LinkedList минимальны, часто доминируют другие факторы.

Средние коллекции (100-10,000 элементов):
ArrayList обычно превосходит LinkedList для большинства операций, кроме вставки/удаления в начале.

Большие коллекции (более 10,000 элементов):
ArrayList значительно превосходит LinkedList для операций доступа и поиска, но страдает при частых вставках/удалениях в начале.


Практические рекомендации


Критерии выбора реализации

Выбор ArrayList когда:
Преобладают операции случайного доступа и поиска
Редкие вставки/удаления в начале списка
Известен или может быть оценен конечный размер
Критически важна memory locality и кэширование


Выбор LinkedList когда:
Частые вставки/удаления в начале списка
Последовательный доступ является доминирующим паттерном
Размер списка сильно варьируется
Память не является основным ограничением


Оптимизация алгоритмов

Для частых операций contains/remove:
Рассмотреть использование HashSet для операций проверки существования
Использовать специализированные структуры данных для specific use cases
Кэшировать результаты частых проверок


Для пакетных операций:

Использовать removeAll() вместо цикла с индивидуальными удалениями
Рассмотреть создание новых коллекций вместо модификации существующих
Использовать stream API для декларативных операций



#Java #для_новичков #beginner #List #ArrayList #LinkedList #remove #contains
👍3
Что выведет код?

import java.util.ArrayList;
import java.util.List;

public class Task041225 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
list.add(1);

list.remove(1);
list.remove(Integer.valueOf(30));
list.remove(2);

System.out.println(list);
}
}


#Tasks
Вопрос с собеседований

Что такое volatile в Java? 🤓


Ответ:

volatile
гарантирует видимость изменений переменной между потоками и запрещает определённые оптимизации.

Он не обеспечивает атомарности сложных операций, но полезен для флагов и простых статусов.

Работает быстрее, чем полная синхронизация, но ограниченно.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
История IT-технологий сегодня — 05 декабря


ℹ️ Кто родился в этот день

Не нашел((


🌐 Знаковые события

1957 — в СССР спущен на воду первый в мире атомный ледокол «Ленин».

2014 — Exploration Flight Test 1 — первый тестовый непилотируемый запуск многоразового космического корабля «Орион».


#Biography #Birth_Date #Events #05Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Профессиональный квиз 🤓

Найдите потенциальную ошибку в коде на скрине.

Кейс интересный, поймал случайно ☺️🤫

#Quiz
Please open Telegram to view this post
VIEW IN TELEGRAM
2
Архитектура Spring Cloud Gateway: Внутренние компоненты и жизненный цикл запроса

В основе Spring Cloud Gateway лежат три ключевые абстракции, которые формируют декларативную модель конфигурации: Route, Predicate и Filter. Эти сущности организованы в строгую иерархию, где каждая выполняет свою роль в обработке входящего запроса.

Route (Маршрут)

Является центральной конфигурационной единицей. Он определяет полный путь обработки конкретного запроса от получения до возврата ответа. Маршрут инкапсулирует три основных элемента: уникальный идентификатор, целевой URI (куда будет перенаправлен запрос после обработки) и упорядоченные коллекции предикатов и фильтров. Внутренне маршрут представлен классом org.springframework.cloud.gateway.route.Route, который является иммутабельным объектом. Иммутабельность критически важна, поскольку маршруты могут динамически обновляться в runtime (например, через обновление конфигурации из Spring Cloud Config), и необходимо гарантировать согласованное состояние во время обработки запроса.

Конфигурация маршрута в YAML демонстрирует эту структуру:
spring:
cloud:
gateway:
routes:
- id: user_service_route
uri: lb://USER-SERVICE
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
- AddRequestHeader=X-Gateway-Tag, processed


Predicate (Предикат)
Условие, которое определяет, должен ли данный маршрут быть применён к входящему запросу. Предикаты реализуют функциональный интерфейс Predicate<ServerWebExchange>, где ServerWebExchange является контейнером для HTTP-запроса и ответа, а также дополнительных атрибутов, накопленных в процессе обработки. Предикаты оцениваются в определённом порядке, и первый маршрут, чьи предикаты возвращают true для данного ServerWebExchange, выбирается для дальнейшей обработки. Типичные предикаты включают проверку пути (Path=/api/**), метода HTTP (Method=GET,POST), наличия заголовков (Header=X-Request-Id, \\d+), параметров запроса, хоста, кук и даже сложные временные условия (After=2023-01-20T17:42:47.789-07:00[America/Denver]). Механизм предикатов позволяет реализовать сложную логику маршрутизации без написания imperative-кода.

Filter (Фильтр)
Компонент, который модифицирует ServerWebExchange до или после вызова целевого сервиса. Фильтры организованы в цепочку (Gateway Filter Chain) и выполняются в строгом порядке. Они реализуют интерфейс GatewayFilter, который содержит единственный метод Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain). Фильтры разделяются на две категории по моменту выполнения: "pre-filters" выполняются до передачи запроса целевому сервису (модификация запроса, аутентификация, логирование), а "post-filters" — после получения ответа от целевого сервиса (модификация ответа, добавление заголовков, метрик). Порядок выполнения фильтров внутри цепочки определяется их конфигурацией в маршруте.


Reactor Netty как HTTP-сервер: архитектурный фундамент

Spring Cloud Gateway построен на реактивном стеке Spring WebFlux, который в свою очередь использует Reactor Netty в качестве HTTP-сервера. Это фундаментальный архитектурный выбор, отличающий SCG от традиционных сервлетных контейнеров.


#Java #middle #Spring_Cloud_Gateway
Netty — это асинхронный event-driven фреймворк сетевого программирования, работающий на уровне NIO (Non-blocking I/O). В контексте JVM это означает, что вместо использования blocking socket operations и пула потоков (thread-per-connection модель), Netty использует небольшое количество потоков-селекторов (event loop threads), которые обрабатывают события на множестве соединений. Каждое соединение ассоциировано с каналом (Channel), а события (чтение данных, запись данных, изменение состояния соединения) диспетчеризуются через цепочки обработчиков (ChannelPipeline).

Когда Spring Boot приложение со SCG стартует, автоконфигурация ReactorNettyAutoConfiguration создаёт и настраивает экземпляр HttpServer Netty. Конфигурация по умолчанию устанавливает количество потоков event loop group равным количеству доступных процессорных ядер (Runtime.getRuntime().availableProcessors()), что является оптимальным для CPU-bound операций. Каждый поток event loop обслуживает множество соединений, переключаясь между ними по мере появления событий ввода-вывода.

В памяти JVM это приводит к совершенно иной структуре по сравнению с традиционными сервлетными контейнерами. Вместо большого пула потоков (200-500 объектов Thread в heap), каждый со своим стеком (1MB по умолчанию), создаётся небольшое количество долгоживущих потоков Netty. Основные структуры данных в heap — это буферы ByteBuf (которые Netty эффективно пуллит через ByteBufAllocator), объекты Channel и их контексты, а также реактивные потоки (Mono, Flux) и их операторы, представляющие pipeline обработки запроса.


Жизненный цикл запроса: от байтов в сокете до ответа

Фаза 1: Обработка в Netty и преобразование в ServerWebExchange


Когда клиент устанавливает TCP-соединение и отправляет HTTP-запрос, Netty event loop thread получает событие channelRead. Сырые байты из сокета декодируются в объект HttpRequest Netty. Затем через адаптер ReactorServerHttpRequest этот запрос оборачивается в реактивный тип ServerHttpRequest Spring WebFlux. Создаётся контейнер ServerWebExchange, который будет нести состояние запроса через весь pipeline обработки. Критически важно, что на этом этапе тело запроса ещё не читается полностью — оно представлено как реактивный поток Flux<DataBuffer>, что позволяет обрабатывать запросы потоково, без буферизации всего тела в памяти.

Фаза 2: Сопоставление маршрута через HandlerMapping


Обработка передаётся в DispatcherHandler Spring WebFlux, который ищет подходящий обработчик для запроса. В контексте SCG ключевым является RoutePredicateHandlerMapping — специализированная реализация HandlerMapping. Его задача — найти подходящий маршрут для текущего запроса.

Процесс сопоставления начинается с получения всех доступных маршрутов через RouteLocator. RouteLocator — это абстракция, которая предоставляет поток маршрутов. Реализация по умолчанию CachingRouteLocator кэширует маршруты для производительности, но поддерживает механизмы инвалидации при динамическом обновлении конфигурации. Каждый маршрут проверяется последовательно: для каждого предиката маршрута вызывается метод test(ServerWebExchange). Проверка предикатов выполняется синхронно (хотя сами предикаты могут выполнять асинхронные операции) до первого совпадения.

Сложность предиката Path демонстрирует детали реализации: при конфигурации Path=/api/users/** создаётся PathRoutePredicateFactory. Внутри он использует PathPatternParser из Spring WebFlux для компиляции строки шаблона в оптимизированную структуру данных PathPattern. При сопоставлении выполняется не простое строковое сравнение, а эффективный алгоритм сопоставления с извлечением переменных пути (например, /api/users/{id}). Это существенно быстрее, чем регулярные выражения, и не создает издержек при большом количестве маршрутов.

Когда маршрут найден, он сохраняется в атрибутах ServerWebExchange под ключом Route.class.getName(), и управление передаётся соответствующему обработчику — FilteringWebHandler.


#Java #middle #Spring_Cloud_Gateway
Фаза 3: Выполнение цепочки фильтров

FilteringWebHandler — это сердце логики преобразования запроса. Он получает выбранный маршрут и строит цепочку фильтров, упорядочивая их согласно конфигурации. Цепочка представляет собой реактивный pipeline, где каждый фильтр — это оператор, трансформирующий ServerWebExchange.

Порядок выполнения фильтров строго определён:
Сначала выполняются все GlobalFilter (глобальные фильтры), зарегистрированные в контексте приложения. Глобальные фильтры выполняются в порядке, определённом их значением getOrder().
Затем выполняются фильтры, специфичные для маршрута, в том порядке, в котором они объявлены в конфигурации маршрута.

Каждый фильтр получает контроль над ServerWebExchange и может либо модифицировать его, либо передать управление следующему фильтру в цепочке через вызов chain.filter(exchange), либо завершить обработку, вернув ответ непосредственно из шлюза. Последний сценарий используется, например, когда фильтр аутентификации обнаруживает невалидный токен и возвращает 401 Unauthorized.

Пример кастомного pre-фильтра для логирования:
@Component
public class LoggingGatewayFilterFactory extends AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> {

private static final Logger log = LoggerFactory.getLogger(LoggingGatewayFilterFactory.class);

public LoggingGatewayFilterFactory() {
super(Config.class);
}

@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
long startTime = System.currentTimeMillis();
ServerHttpRequest request = exchange.getRequest();

log.info("Incoming request: {} {} from {}",
request.getMethod(),
request.getURI().getPath(),
request.getRemoteAddress());

return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long duration = System.currentTimeMillis() - startTime;
ServerHttpResponse response = exchange.getResponse();

log.info("Completed request: {} {} -> {} in {}ms",
request.getMethod(),
request.getURI().getPath(),
response.getStatusCode(),
duration);
}));
};
}

public static class Config {
// Конфигурационные свойства фильтра
}
}

Этот фильтр демонстрирует важный паттерн: логирование в pre-фильтре, а измерение времени и логирование результата — в post-части, реализованной через then(Mono.fromRunnable(...)). Обратите внимание, что весь фильтр — это функция, возвращающая Mono<Void>, и логирование выполняется в реактивном стиле без блокирования потоков.



#Java #middle #Spring_Cloud_Gateway
Фаза 4: Проксирование запроса к целевому сервису

После выполнения всех pre-фильтров управление доходит до ключевого фильтра — NettyRoutingFilter (или WebClientHttpRoutingFilter в альтернативной реализации). Именно этот фильтр выполняет фактическое проксирование запроса к целевому сервису.

Процесс проксирования включает несколько шагов:
Преобразование URI назначения: Если URI маршрута использует схему lb:// (например, lb://USER-SERVICE), вызывается LoadBalancerClientFilter (или реактивный ReactorLoadBalancer), который преобразует логическое имя сервиса в физический адрес, выбирая конкретный экземпляр с учётом алгоритма балансировки нагрузки.

Подготовка запроса прокси: NettyRoutingFilter создаёт новый HTTP-запрос Netty, копируя метод, заголовки и тело из оригинального запроса. При этом он может применять трансформации, определённые фильтрами (например, перезапись пути, добавление заголовков).

Асинхронное выполнение запроса: Запрос отправляется через реактивный HTTP-клиент Netty (HttpClient). Клиент Netty работает в неблокирующем режиме — он ставит запрос в очередь на отправку и немедленно возвращает Mono<HttpClientResponse> без блокировки потока. Event loop thread освобождается для обработки других соединений.

Обработка ответа: Когда от целевого сервиса приходит ответ, Netty генерирует событие, которое обрабатывается реактивным pipeline. Тело ответа также остаётся в реактивном представлении (Flux<DataBuffer>), что позволяет streamingly передавать большие ответы без буферизации в памяти.

Конфигурация для балансировки нагрузки с помощью Spring Cloud LoadBalancer:

spring:
cloud:
gateway:
routes:
- id: user_service_lb
uri: lb://user-service
predicates:
- Path=/users/**

loadbalancer:
configurations: default

# Конфигурация для кэширования resolved адресов
discovery:
locator:
enabled: true
lower-case-service-id: true
При использовании lb:// схема автоматически активирует интеграцию с Service Discovery (Eureka, Consul) через ReactiveLoadBalancer. Выбор экземпляра выполняется с учётом состояния здоровья и выбранного алгоритма (round-robin по умолчанию).


Фаза 5: Пост-обработка ответа и завершение


После получения ответа от целевого сервиса выполняется оставшаяся часть цепочки фильтров — post-фильтры. Эти фильтры получают доступ как к оригинальному запросу, так и к ответу от целевого сервиса. Они могут модифицировать статус-код, заголовки, тело ответа.

Пример post-фильтра для добавления стандартных заголовков безопасности:
@Component
public class SecurityHeadersGlobalFilter implements GlobalFilter {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
if (!response.getHeaders().containsKey("X-Content-Type-Options")) {
response.getHeaders().add("X-Content-Type-Options", "nosniff");
}
if (!response.getHeaders().containsKey("X-Frame-Options")) {
response.getHeaders().add("X-Frame-Options", "DENY");
}
if (!response.getHeaders().containsKey("Content-Security-Policy")) {
response.getHeaders().add("Content-Security-Policy",
"default-src 'self'; frame-ancestors 'none'");
}
}));
}
}
Важно отметить, что этот фильтр реализует интерфейс GlobalFilter и будет применён ко всем маршрутам автоматически. Глобальные фильтры выполняются до фильтров, специфичных для маршрута, если только их порядок (через аннотацию @Order или реализацию Ordered) не указывает иное.


После выполнения всех post-фильтров финальный ответ записывается обратно в исходный канал Netty к клиенту. Netty берёт на себя эффективную отправку данных, включая chunked encoding для stream-ответов.


#Java #middle #Spring_Cloud_Gateway
Типы фильтров и их специализация

Pre-filters / Post-filters — это логическая группировка, определяемая моментом выполнения относительно вызова целевого сервиса. Технически большинство фильтров могут работать и как pre, и как post, в зависимости от их реализации. Паттерн разделения на pre/post часто реализуется через вызов chain.filter(exchange).then(...) для post-логики.

Сетевые фильтры (Netty layer) работают на более низком уровне, ближе к транспорту. NettyRoutingFilter и NettyWriteResponseFilter являются примерами таких фильтров. Они манипулируют непосредственно объектами Netty (HttpClientRequest, HttpClientResponse) и работают с реактивными потоками байтов (ByteBuf). Эти фильтры критически важны для производительности, так как обеспечивают эффективную передачу данных без лишних копирований.

Global filters применяются ко всем маршрутам автоматически. Они регистрируются как Spring Beans и могут быть упорядочены. Типичные use cases: централизованное логирование, сбор метрик, добавление стандартных заголовков, кэширование. Глобальные фильтры выполняются до фильтров маршрута, если только их порядок не указывает иное.

Per-route filters конфигурируются для конкретных маршрутов и применяются только к ним. Они декларативно задаются в конфигурации маршрута (YAML, properties) или через Java DSL.

Пример сложного per-route фильтра с кастомной конфигурацией:

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("custom_rewrite", r -> r
.path("/legacy/api/**")
.filters(f -> f
.rewritePath("/legacy/api/(?<segment>.*)", "/modern/${segment}")
.addRequestParameter("source", "gateway")
.circuitBreaker(config -> config
.setName("myCircuitBreaker")
.setFallbackUri("forward:/fallback/default"))
)
.uri("lb://backend-service"))
.build();
}
В этой конфигурации DSL демонстрируется несколько фильтров в цепочке: rewritePath изменяет путь запроса с помощью регулярного выражения, addRequestParameter добавляет параметр, а circuitBreaker оборачивает вызов в контур устойчивости с fallback.



Управление памятью и производительностью в JVM

Архитектура SCG на Reactor Netty имеет глубокие последствия для управления памятью в JVM.

Вместо пулов потоков с фиксированным размером стека, основное потребление памяти связано с:
Буферами Netty (ByteBuf): Netty использует пул буферов через ByteBufAllocator. Это позволяет эффективно переиспользовать буферы для чтения/записи данных, минимизируя аллокации и сборку мусора. Буферы могут быть off-heap (direct buffers), что уменьшает нагрузку на GC, но требует явного управления памятью.

Реактивные потоки и лямбда-выражения: Каждый запрос создаёт цепочку реактивных операторов (Mono, Flux), которые представляются как объекты в heap. Лямбда-выражения в фильтрах также становятся объектами. Хотя эти объекты обычно недолгоживущие (short-lived) и эффективно обрабатываются молодым поколением сборщика мусора (young generation GC), при высокой нагрузке может создаваться значительное давление на GC.

Кэши маршрутов и предикатов: CachingRouteLocator и компилированные PathPattern кэшируются, что увеличивает постоянное потребление памяти (old generation), но значительно ускоряет обработку запросов.

Для оптимизации производительности важно:
Настроить размеры пулов буферов Netty согласно ожидаемому размеру запросов/ответов
Использовать профилирование для выявления memory leaks в кастомных фильтрах (невыполненные подписки на реактивные потоки)
Настроить сборщик мусора для низких пауз (G1GC или Shenandoah для больших heap-ов)
Мониторить количество аллокаций и pressure на young generation через JMX или Micrometer


#Java #middle #Spring_Cloud_Gateway
Что выведет код?

import java.util.Map;
import java.util.TreeMap;

public class Task051225 {
public static void main(String[] args) {
Map<String, Integer> map = new TreeMap<>();
map.put("x", 5);
map.put("y", null);

map.merge("x", 3, Integer::sum);
map.merge("y", 7, Integer::sum);
map.merge("z", 2, Integer::sum);
map.merge("x", 1, Integer::sum);

System.out.println(map.get("x"));
System.out.println(map.get("y"));
System.out.println(map.get("z"));
System.out.println(map.size());
}
}


#Tasks