Java for Beginner
774 subscribers
759 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
Сравнительный анализ 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
Архитектура 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
👍1
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
👍1
Фаза 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
👍1
Фаза 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
👍1
Типы фильтров и их специализация

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
👍1
Раздел 6. Коллекции в Java

Глава 2. List — списки

Практика В проекте «Библиотека» заменить массив книг на ArrayList. Реализовать методы: добавить книгу, найти по названию, удалить по индексу


Убедитесь, что ваш проект готов, и вспомните ключевые возможности ArrayList:

Динамический размер (растёт автоматически)
Доступ по индексу O(1)
Добавление в конец O(1) в среднем
Удаление O(n) (сдвиг элементов)
Поиск indexOf O(n)



Откройте проект «Библиотека»: Запустите IntelliJ IDEA и откройте проект LibraryProject.

Проверьте структуру: У вас должен быть класс Book с полями title, author, year, конструктором и методом printDetails(). Класс Library с массивом книг и методом addBook.


Замена массива на ArrayList

Замените поле массива:
Откройте файл Library.java.
Удалите объявление массива Book[] books и переменную bookCount.
Вместо этого объявите приватное поле:
private List<Book> books = new ArrayList<>();
Это будет наш основной список книг.


Инициализация:

Убедитесь, что books инициализируется в конструкторе (если не сделали при объявлении).
Можно также создать отдельный конструктор Library(), где books = new ArrayList<>(); — на ваш выбор.



Реализация метода добавления книги

Создайте или обновите метод addBook(Book book):
Метод должен быть публичным и принимать объект Book.
Используйте метод add() из ArrayList для добавления книги в конец списка.
Добавьте вывод сообщения: "Книга [title] успешно добавлена".
(Опционально) Проверьте, что книга не null перед добавлением.



Реализация метода поиска книги по названию

Создайте метод findBookByTitle(String title):
Метод должен возвращать объект Book или null, если не найден.
Переберите весь список с помощью цикла for или for-each.
Для каждой книги сравните её title с искомым (используйте equals(), учитывая регистр или используйте equalsIgnoreCase() для нечувствительности к регистру).
Если совпадение найдено — верните эту книгу.
Если цикл завершился — верните null.
Добавьте вывод: "Книга найдена: [title]" или "Книга не найдена".


Альтернативный способ (по желанию):
Используйте метод indexOf() с временным объектом-заглушкой, у которого title совпадает с искомым (переопределите equals() в Book, если нужно).


Реализация метода удаления книги по индексу

Создайте метод removeBookByIndex(int index):
Метод должен возвращать boolean: true — если удаление прошло успешно, false — если индекс неверный.
Проверьте, что индекс в допустимом диапазоне: >= 0 и < books.size().
Если индекс неверный — выведите сообщение "Неверный индекс" и верните false.
Если индекс корректный — используйте метод remove(index) из ArrayList.
Сохраните удаленную книгу (Book removedBook = books.remove(index);) и выведите сообщение "Книга удалена: [title удаленной книги]".
Верните true.



#Java #для_новичков #beginner #List #ArrayList #LinkedList #Практика
👍3
Обновление Main для тестирования
Теперь протестируем новые методы в классе Main.

Создайте объект Library и добавьте книги:
Создайте несколько объектов Book (минимум 4-5).
Добавьте их в библиотеку через addBook().


Протестируйте поиск:
Вызовите findBookByTitle() с существующим и несуществующим названием.
Если книга найдена — вызовите printDetails() на возвращенном объекте.


Протестируйте удаление:
Выведите текущий размер списка (books.size()).
Удалите книгу по валидному индексу (например, 1).
Удалите по невалидному индексу (например, -1 или больше size).
Выведите размер после удаления.


Дополнительно: Добавьте метод printAllBooks() в Library, который перебирает весь список и вызывает printDetails() для каждой книги — используйте его для проверки состояния библиотеки.


Тестирование и отладка

Запустите проект:
Run 'Main.main()' — наблюдайте за сообщениями в консоли.

Проверьте поведение:
Добавление: Список растёт, нет ограничений по размеру.
Поиск: Находит по точному совпадению.
Удаление: Корректно удаляет и сдвигает элементы.

Отладка:
Установите breakpoint в методе removeBookByIndex и findBookByTitle.
Шагайте по коду (F8) и смотрите, как меняется размер списка и содержимое.



Полезные советы для новичков

Импорты: Не забудьте import
java.util.ArrayList; и import java.util.List;
Generics: List<Book> books — всегда используйте generics.
Проверка на null: В addBook: if (book == null) return;
Регистр в поиске: equalsIgnoreCase() для нечувствительности к регистру.
Вывод размера: books.size() вместо books.length (это массив!).
Удаление: remove(index) возвращает удаленный элемент — удобно для сообщений.



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


Задача 1: Добавьте в Library метод getBookCount(), возвращающий books.size().
Задача 2: Реализуйте метод removeBookByTitle(String title), который находит книгу по названию и удаляет её (используйте цикл или indexOf + remove).
Задача 3: Добавьте метод printAllBooks(), который выводит все книги с номерами (индекс + 1).



#Java #для_новичков #beginner #List #ArrayList #LinkedList #Практика
👍2
Конфигурация маршрутов в Spring Cloud Gateway

Декларативная конфигурация через application.yml
Конфигурация через YAML-файлы является декларативным подходом, где маршруты определяются статически на этапе компиляции или до запуска приложения. Этот метод обеспечивает простоту чтения, версионирование через системы контроля версий и простоту аудита изменений.


Базовая структура конфигурации маршрута


В YAML маршруты конфигурируются через иерархию spring.cloud.gateway.routes. Каждый маршрут должен иметь уникальный идентификатор, URI назначения, список предикатов и опциональный список фильтров.
spring:
cloud:
gateway:
routes:
- id: user_service_route
uri: http://localhost:8081
predicates:
- Path=/api/users/**
- Method=GET,POST
- Header=X-Requested-With, XMLHttpRequest
filters:
- StripPrefix=1
- AddRequestHeader=X-Gateway-Version, v2.0
- RewritePath=/api/users/(?<segment>.*), /$\{segment}
metadata:
response-timeout: 5000
connect-timeout: 2000
max-auto-retries: 3



Детализация предикатов

Предикаты в YAML конфигурируются как список строк, где каждая строка соответствует фабрике предикатов и её параметрам. Формат: Name=arg1,arg2,...,key1=value1,key2=value2.

Предикат Path:
predicates:
- Path=/api/v1/.*
- Path=/api/orders/{segment}, /api/products/{segment}
При использовании фигурных скобок {segment} происходит извлечение переменных пути, которые затем доступны в фильтрах через шаблоны типа ${segment}.


Составные предикаты с помощью After и Before:
predicates:
- After=2023-01-20T17:42:47.789-07:00[America/Denver]
- Between=2023-01-20T17:42:47.789-07:00[America/Denver],2023-01-21T17:42:47.789-07:00[America/Denver]
Временные предикаты используют формат даты-времени ISO-8601 и учитывают временные зоны. Они полезны для канарей-развёртываний и управления версиями API.


Предикат Query для проверки параметров:
predicates:
- Query=version, ^v[0-9]+$
- Query=token
Первый вариант проверяет, что параметр version существует и соответствует регулярному выражению. Второй вариант проверяет только наличие параметра token без проверки значения.


Предикат RemoteAddr для контроля доступа по IP:
predicates:
- RemoteAddr=192.168.1.1/24, 10.0.0.1/8
Поддерживается CIDR-нотация. Этот предикат часто комбинируется с фильтрами аутентификации для создания многоуровневой безопасности.


Кастомный предикат с использованием SpEL:

predicates:
- name: Custom
args:
name: myPredicate
spel: "#{@tokenValidator.validate(request)}"
Для кастомных предикатов используется полная форма конфигурации с указанием имени и аргументов. SpEL (Spring Expression Language) позволяет вызывать Spring Beans и выполнять сложную логику.



Фильтры в YAML конфигурации

Фильтры определяют преобразования, применяемые к запросу или ответу. Они выполняются в порядке объявления.

Базовые фильтры преобразования:
filters:
# Удаление частей пути
- StripPrefix=2

# Добавление заголовков
- AddRequestHeader=X-Client-Version, 1.0.0
- AddResponseHeader=X-Response-Time, "`new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss.SSS').format(new java.util.Date())`"

# Установка пути
- SetPath=/api/v2/{segment}

# Изменение порта и хоста
- SetRequestHost=api.example.com

# Перенаправление
- RedirectTo=302, https://secure.example.com


Фильтры для работы с телом запроса:
filters:
# Модификация тела запроса (требует чтения всего тела в память)
- ModifyRequestBody=com.example.JsonTransformer, application/json, application/json, 102400

# Модификация тела ответа
- ModifyResponseBody=com.example.ResponseNormalizer, application/json, application/json

# Кэширование тела запроса для многократного чтения
- CacheRequestBody=""

Фильтры модификации тела используют регистрируемые через Spring Beans трансформеры. Важно понимать, что они требуют чтения всего тела запроса в память, что может быть проблематично для больших payload.



#Java #middle #Spring_Cloud_Gateway
👍1
Фильтры устойчивости и ограничения:
filters:
# Circuit Breaker с Resilience4J
- name: CircuitBreaker
args:
name: userServiceBreaker
fallbackUri: forward:/fallback/user
statusCodes: 500,502,503

# Ограничение частоты запросов с Redis
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
key-resolver: "#{@userKeyResolver}"

# Retry с экспоненциальной отсрочкой
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,INTERNAL_SERVER_ERROR
methods: GET,POST
series: SERVER_ERROR
backoff:
firstBackoff: 50ms
maxBackoff: 1000ms
factor: 2
basedOnPreviousValue: false



URI и метаданные

URI назначения поддерживает несколько схем:
uri: http://localhost:8081           # Прямой URL
uri: https://api.example.com:8443 # HTTPS
uri: lb://USER-SERVICE # Балансировка нагрузки через Service Discovery
uri: ws://echo.example.com # WebSocket
uri: forward:/fallback # Внутреннее перенаправление

Схема lb:// активирует клиентскую балансировку нагрузки. При использовании с Service Discovery (Eureka, Consul) имя сервиса автоматически разрешается в список доступных экземпляров.


Метаданные маршрута предоставляют дополнительную конфигурацию, специфичную для маршрута:
metadata:
# Таймауты для Netty
response-timeout: 5000 # Таймаут ответа в миллисекундах
connect-timeout: 2000 # Таймаут соединения
max-auto-retries: 2 # Автоматические повторы для сетевых ошибок

# Конфигурация для конкретных интеграций
hystrix.commandName: userCommand
metrics.tags: "service=user,version=v2"

# Пользовательские метаданные
feature-flags: "canary=true,experimental=false"
sla: "p99<200ms"

Метаданные доступны в фильтрах через exchange.getAttribute(ROUTE_ATTRIBUTE).getMetadata() и могут использоваться для реализации динамического поведения.



#Java #middle #Spring_Cloud_Gateway
👍1
Java DSL: Fluent API для программируемой конфигурации

Java DSL через RouteLocatorBuilder предоставляет императивный способ определения маршрутов, который позволяет использовать всю мощь Java: условия, циклы, вызовы методов, dependency injection.

Базовый fluent-синтаксис

@Configuration
public class GatewayConfiguration {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_route", r -> r
.path("/api/users/**")
.and()
.method(HttpMethod.GET, HttpMethod.POST)
.and()
.header("X-API-Version", "v2")
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Gateway-Route", "user")
.circuitBreaker(config -> config
.setName("userCircuitBreaker")
.setFallbackUri("forward:/fallback/user"))
.retry(config -> config
.setRetries(3)
.setStatuses(HttpStatus.INTERNAL_SERVER_ERROR,
HttpStatus.BAD_GATEWAY)
.setMethods(HttpMethod.GET)
.setBackoff(50, 200, 2, true))
)
.uri("lb://user-service")
.metadata("response-timeout", 3000)
.metadata("connect-timeout", 1000)
)
.route("product_route", r -> r
.path("/api/products/**")
.filters(f -> f
.rewritePath("/api/products/(?<segment>.*)",
"/v2/products/${segment}")
.addResponseHeader("X-Cache-Control", "max-age=3600")
.requestRateLimiter(config -> config
.setRateLimiter(redisRateLimiter())
.setKeyResolver(userKeyResolver()))
)
.uri("lb://product-service")
)
.build();
}

@Bean
public RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(10, 20, 1);
}

@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders()
.getFirst("X-User-Id")
);
}
}



Преимущества Java DSL перед YAML

1. Использование Spring Beans в конфигурации:
@Bean
public RouteLocator dynamicRouteLocator(
RouteLocatorBuilder builder,
FeatureFlagService featureService,
CircuitBreakerRegistry breakerRegistry) {

return builder.routes()
.route("feature_route", r -> r
.path("/experimental/**")
.predicate(exchange -> {
// Динамическая проверка feature flags
boolean enabled = featureService.isEnabled(
"experimental_api",
exchange.getRequest().getHeaders()
.getFirst("X-User-Id")
);
return enabled;
})
.filters(f -> f
.circuitBreaker(config -> config
.setCircuitBreakerFactory(
new ReactiveResilience4JCircuitBreakerFactory(
breakerRegistry
)
)
.setName("experimentalBreaker")
)
)
.uri("lb://experimental-service")
)
.build();
}



#Java #middle #Spring_Cloud_Gateway
👍2
2. Генерация маршрутов на основе данных из внешних источников:
@Bean
public RouteLocator generatedRoutes(
RouteLocatorBuilder builder,
RouteTemplateService templateService) {

RouteLocatorBuilder.Builder routesBuilder = builder.routes();

// Загрузка шаблонов маршрутов из базы данных
List<RouteTemplate> templates = templateService.loadTemplates();

for (RouteTemplate template : templates) {
routesBuilder.route(template.getId(), r -> {
RouteLocatorBuilder.Builder spec = r
.path(template.getPathPattern())
.uri(template.getTargetUri());

// Динамическое добавление фильтров
for (FilterConfig filterConfig : template.getFilters()) {
spec.filters(f -> addFilterDynamically(f, filterConfig));
}

return spec;
});
}

return routesBuilder.build();
}


3. Создание сложных составных предикатов:
@Bean
public RouteLocator complexPredicateRoute(RouteLocatorBuilder builder) {
return builder.routes()
.route("business_rule_route", r -> r
// Комбинация предикатов с кастомной логикой
.asyncPredicate(exchange -> {
return businessRuleEngine()
.evaluate(exchange.getRequest())
.map(BusinessDecision::isAllowed);
})
.and()
.weight("group_a", 80) // 80% трафика
.and()
.cookie("session_id", ".*")
.filters(f -> f
.modifyRequestBody(String.class, String.class,
(exchange, body) -> {
// Модификация тела на основе бизнес-правил
return Mono.just(
transformPayload(body, exchange)
);
}
)
)
.uri("lb://primary-service")
)
.build();
}



Что можно делать только в DSL и нельзя в YAML

1. Использование произвольных Predicate<ServerWebExchange>:
.route("custom_predicate", r -> r
.predicate(exchange -> {
// Любая Java-логика
String clientIp = exchange.getRequest()
.getRemoteAddress()
.getAddress()
.getHostAddress();

return !ipBlacklist.contains(clientIp) &&
rateLimiter.tryAcquire(clientIp);
})
.uri("lb://service")
)


2. Интеграция с внешними системами конфигурации:
.route("external_config", r -> r
.asyncPredicate(exchange -> {
// Загрузка правил из внешнего сервиса
return configClient.getRoutingRules()
.map(rules -> rules.matches(exchange.getRequest()));
})
.filters((exchange, chain) -> {
// Динамическая модификация на основе конфигурации
ServerHttpRequest request = exchange.getRequest();
Map<String, String> headers = externalConfig
.getHeadersForRoute(request.getPath().toString());

ServerHttpRequest mutated = request.mutate()
.headers(h -> headers.forEach(h::add))
.build();

return chain.filter(
exchange.mutate().request(mutated).build()
);
})
.uri("lb://target")
)


#Java #middle #Spring_Cloud_Gateway
👍2
3. Генерация маршрутов в runtime:
@Bean
public RouteLocator runtimeGeneratedRoutes(
RouteLocatorBuilder builder,
ApplicationContext context) {

RouteLocatorBuilder.Builder routes = builder.routes();

// Динамическое создание маршрутов на основе Bean-ов
Map<String, RouteProvider> providers = context
.getBeansOfType(RouteProvider.class);

providers.forEach((name, provider) -> {
provider.getRoutes().forEach(routeDef -> {
routes.route(routeDef.getId(), r -> {
AbstractRouteSpec spec = r
.path(routeDef.getPath())
.uri(routeDef.getUri());

routeDef.getFilters().forEach(filter -> {
spec.filters(f -> configureFilter(f, filter));
});

return spec;
});
});
});

return routes.build();
}



Динамическая маршрутизация

DiscoveryClientRouteDefinitionLocator

DiscoveryClientRouteDefinitionLocator обеспечивает автоматическое создание маршрутов на основе сервисов, зарегистрированных в Service Discovery. При активации, для каждого зарегистрированного сервиса создаётся маршрут, который перенаправляет трафик на этот сервис.

spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
predicates:
- name: Path
args:
pattern: "'/services/' + serviceId.toLowerCase() + '/**'"
filters:
- name: RewritePath
args:
regexp: "'/services/' + serviceId.toLowerCase() + '/(?<remaining>.*)'"
replacement: "'/${remaining}'"


Эта конфигурация создаст маршруты вида:
http://gateway/services/user-service/** → lb://user-service
http://gateway/services/order-service/** → lb://order-service



Интеграция с Eureka

Для интеграции с Eureka необходимо добавить зависимость и соответствующую конфигурацию:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>


eureka:
client:
service-url:
defaultZone: http://eureka-server:8761/eureka
registry-fetch-interval-seconds: 5
instance:
lease-renewal-interval-in-seconds: 10
lease-expiration-duration-in-seconds: 30

spring:
cloud:
gateway:
discovery:
locator:
enabled: true
# Добавление метаданных из Eureka в маршруты
include-expression: metadata['gateway.enabled'] == 'true'
url-expression: "'lb://' + serviceId"


Eureka-специфичные метаданные могут использоваться для управления маршрутизацией:
@Bean
public RouteLocator eurekaAwareRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("zone_aware", r -> r
.path("/zone/**")
.predicate(exchange -> {
// Выбор экземпляра в той же зоне доступности
ServiceInstance instance = loadBalancer.choose(
"target-service",
new ZonePreferenceServiceInstanceListSupplier()
);
return instance != null;
})
.uri("lb://target-service")
)
.build();
}



#Java #middle #Spring_Cloud_Gateway
👍1
Интеграция с Consul

Consul предоставляет более богатые возможности для service discovery и health checking:

spring:
cloud:
consul:
host: localhost
port: 8500
discovery:
health-check-path: /actuator/health
health-check-interval: 10s
tags:
- gateway-enabled
- version=v2
query-passing: true # Использовать только здоровые инстансы

gateway:
discovery:
locator:
enabled: true
predicates:
- name: Path
args:
pattern: "'/consul/' + serviceId + '/**'"
filters:
- name: RewritePath
args:
regexp: "'/consul/' + serviceId + '/(?<remaining>.*)'"
replacement: "'/${remaining}'"
# Фильтрация по Consul-тегам
include-expression: tags.contains('gateway-enabled')


Consul теги могут использоваться для реализации сложной логики маршрутизации:
@Bean
public RouteLocator consulTagBasedRoutes(
RouteLocatorBuilder builder,
ConsulDiscoveryClient discoveryClient) {

return builder.routes()
.route("canary_route", r -> r
.path("/api/canary/**")
.predicate(exchange -> {
// Динамическая канареечная маршрутизация
List<ServiceInstance> instances = discoveryClient
.getInstances("product-service");

// Выбор инстансов с тегом canary
ServiceInstance canaryInstance = instances.stream()
.filter(inst -> inst.getMetadata()
.getOrDefault("canary", "false")
.equals("true"))
.findFirst()
.orElse(null);

// 10% трафика на canary
return canaryInstance != null &&
Math.random() < 0.1;
})
.uri("lb://product-service")
)
.build();
}



#Java #middle #Spring_Cloud_Gateway
👍1
Кастомный RouteDefinitionRepository

Для полностью динамической маршрутизации можно реализовать собственный RouteDefinitionRepository, который будет загружать маршруты из произвольного источника (БД, файловая система, внешний сервис).

Базовая реализация с поддержкой обновлений:

@Component
public class DatabaseRouteDefinitionRepository
implements RouteDefinitionRepository, ApplicationEventPublisherAware {

private final RouteDefinitionDAO routeDefinitionDAO;
private final Map<String, RouteDefinition> cache =
new ConcurrentHashMap<>();
private ApplicationEventPublisher publisher;

public DatabaseRouteDefinitionRepository(
RouteDefinitionDAO routeDefinitionDAO) {
this.routeDefinitionDAO = routeDefinitionDAO;
loadRoutes();
}

@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(cache.values());
}

@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(routeDef -> {
return routeDefinitionDAO.save(routeDef)
.doOnSuccess(saved -> {
cache.put(saved.getId(), saved);
publishRefreshEvent();
})
.then();
});
}

@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
return routeDefinitionDAO.delete(id)
.doOnSuccess(deleted -> {
cache.remove(id);
publishRefreshEvent();
})
.then();
});
}

private void loadRoutes() {
routeDefinitionDAO.findAll()
.doOnNext(routeDef -> cache.put(routeDef.getId(), routeDef))
.subscribe();
}

private void publishRefreshEvent() {
if (publisher != null) {
publisher.publishEvent(
new RefreshRoutesEvent(this)
);
}
}

@Override
public void setApplicationEventPublisher(
ApplicationEventPublisher publisher) {
this.publisher = publisher;
}

// Метод для внешних триггеров обновления
@Scheduled(fixedDelay = 30000)
public void refreshRoutes() {
loadRoutes();
publishRefreshEvent();
}
}


Реализация с поддержкой веб-интерфейса для управления маршрутами:
@RestController
@RequestMapping("/api/gateway/routes")
public class RouteManagementController {

private final RouteDefinitionWriter routeDefinitionWriter;
private final RouteDefinitionLocator routeDefinitionLocator;

@PostMapping
public Mono<ResponseEntity<Void>> createRoute(
@RequestBody RouteDefinition routeDefinition) {
return routeDefinitionWriter
.save(Mono.just(routeDefinition))
.then(Mono.just(ResponseEntity.ok().build()));
}

@GetMapping
public Flux<RouteDefinition> getAllRoutes() {
return routeDefinitionLocator.getRouteDefinitions();
}

@PutMapping("/{id}")
public Mono<ResponseEntity<Void>> updateRoute(
@PathVariable String id,
@RequestBody RouteDefinition routeDefinition) {

return routeDefinitionWriter
.delete(Mono.just(id))
.then(routeDefinitionWriter.save(Mono.just(routeDefinition)))
.then(Mono.just(ResponseEntity.ok().build()));
}

@DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> deleteRoute(@PathVariable String id) {
return routeDefinitionWriter
.delete(Mono.just(id))
.then(Mono.just(ResponseEntity.noContent().build()));
}
}


#Java #middle #Spring_Cloud_Gateway
👍1
Интеграция с внешними системами конфигурации:
@Component
public class ExternalConfigRouteDefinitionRepository
implements RouteDefinitionRepository {

private final ConfigClient configClient;
private final AtomicReference<List<RouteDefinition>> cachedRoutes =
new AtomicReference<>(Collections.emptyList());

public ExternalConfigRouteDefinitionRepository(
ConfigClient configClient) {
this.configClient = configClient;
configClient.watchRoutes()
.doOnNext(this::updateRoutes)
.subscribe();
}

@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(cachedRoutes.get());
}

private void updateRoutes(List<RouteConfig> routeConfigs) {
List<RouteDefinition> routeDefinitions = routeConfigs.stream()
.map(this::convertToRouteDefinition)
.collect(Collectors.toList());

cachedRoutes.set(routeDefinitions);

// Генерация события обновления маршрутов
SpringApplication.publishEvent(
new RefreshRoutesEvent(this)
);
}

private RouteDefinition convertToRouteDefinition(
RouteConfig config) {
RouteDefinition definition = new RouteDefinition();
definition.setId(config.getId());
definition.setUri(URI.create(config.getUri()));

// Конвертация предикатов
config.getPredicates().forEach((name, args) -> {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(name);
predicate.setArgs(args);
definition.getPredicates().add(predicate);
});

// Конвертация фильтров
config.getFilters().forEach((name, args) -> {
FilterDefinition filter = new FilterDefinition();
filter.setName(name);
filter.setArgs(args);
definition.getFilters().add(filter);
});

definition.setMetadata(config.getMetadata());
return definition;
}

@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
// Делегирование сохранения во внешнюю систему
return route.flatMap(routeDef ->
configClient.saveRoute(convertToRouteConfig(routeDef))
).then();
}

@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id ->
configClient.deleteRoute(id)
).then();
}
}


Обработка событий обновления маршрутов:
@Component
public class RouteUpdateListener {

private final RouteRefreshListener routeRefreshListener;

@EventListener
public void handleRefreshRoutesEvent(RefreshRoutesEvent event) {
// Логирование обновления маршрутов
logger.info("Routes refreshed by {}", event.getSource());

// Метрики
meterRegistry.counter("gateway.routes.refresh").increment();

// Уведомление подписчиков
routeRefreshListener.notifyRefresh();
}

@EventListener
public void handleApplicationEvent(ApplicationEvent event) {
if (event instanceof InstanceRegisteredEvent) {
// Обработка регистрации нового инстанса
updateRoutesForNewInstance(
((InstanceRegisteredEvent) event).getInstance()
);
}
}
}


#Java #middle #Spring_Cloud_Gateway
👍1