@OptimisticLock в Hibernate
Аннотация @OptimisticLock управляет оптимистичной блокировкой сущности в Hibernate, определяя, какие поля должны проверяться на изменение при обновлении.
Пакет: org.hibernate.annotations
Применяется к: классу сущности (@Entity) или отдельным полям.
Стратегии:
OptimisticLockType.VERSION (по умолчанию) — использует @Version.
OptimisticLockType.ALL — проверяет все поля.
OptimisticLockType.DIRTY — проверяет только измененные поля.
OptimisticLockType.NONE — отключает проверку.
Параметры и настройки
Атрибуты
type: Стратегия проверки изменений (VERSION, ALL, DIRTY, NONE).
excluded: Если true, поле исключается из проверки (при type = ALL/DIRTY).
Примеры
Жизненный цикл и обработка
Как работает оптимистичная блокировка?
При чтении сущности:
Hibernate запоминает состояние полей (для ALL/DIRTY).
При обновлении:
Генерируется SQL вида:
Если условие не выполняется (данные изменились), выбрасывается OptimisticLockException.
Интеграция с Spring Boot
Глобальная стратегия:
Обработка исключений:
Примеры использования
Пример 1: Проверка всех полей
Пример 2: Исключение поля
Проблемы
ALL/DIRTY: Требуют хранения исходного состояния (память).
Нет поддержки в JPA: Только Hibernate.
Когда использовать?
VERSION — для большинства случаев.
DIRTY — для больших сущностей с редкими конфликтами.
#Java #Training #Hard #Spring #Hibernate #OptimisticLock
Аннотация @OptimisticLock управляет оптимистичной блокировкой сущности в Hibernate, определяя, какие поля должны проверяться на изменение при обновлении.
Пакет: org.hibernate.annotations
Применяется к: классу сущности (@Entity) или отдельным полям.
Стратегии:
OptimisticLockType.VERSION (по умолчанию) — использует @Version.
OptimisticLockType.ALL — проверяет все поля.
OptimisticLockType.DIRTY — проверяет только измененные поля.
OptimisticLockType.NONE — отключает проверку.
Параметры и настройки
Атрибуты
type: Стратегия проверки изменений (VERSION, ALL, DIRTY, NONE).
excluded: Если true, поле исключается из проверки (при type = ALL/DIRTY).
Примеры
@Entity
@OptimisticLock(type = OptimisticLockType.ALL)
public class Account {
@Id
private Long id;
@OptimisticLock(excluded = true) // Не проверяется при обновлении
private String secretCode;
}
Жизненный цикл и обработка
Как работает оптимистичная блокировка?
При чтении сущности:
Hibernate запоминает состояние полей (для ALL/DIRTY).
При обновлении:
Генерируется SQL вида:
UPDATE account SET ... WHERE id = ? AND (secretCode IS NULL OR secretCode = ?)
Если условие не выполняется (данные изменились), выбрасывается OptimisticLockException.
Интеграция с Spring Boot
Глобальная стратегия:
spring.jpa.properties.hibernate.optimistic_lock.strategy=all
Обработка исключений:
@ExceptionHandler(OptimisticLockException.class)
public void handleConflict() { /* ... */ }
Примеры использования
Пример 1: Проверка всех полей
@Entity
@OptimisticLock(type = OptimisticLockType.ALL)
public class Product {
@Version
private Long version; // Дополнительная проверка
}
Пример 2: Исключение поля
@Entity
@OptimisticLock(type = OptimisticLockType.DIRTY)
public class User {
@OptimisticLock(excluded = true)
private LocalDateTime lastLogin;
}
Проблемы
ALL/DIRTY: Требуют хранения исходного состояния (память).
Нет поддержки в JPA: Только Hibernate.
Когда использовать?
VERSION — для большинства случаев.
DIRTY — для больших сущностей с редкими конфликтами.
#Java #Training #Hard #Spring #Hibernate #OptimisticLock
👍2
Spring Cloud Gateway: Архитектурный страж микросервисов
В монолитной архитектуре приложение имеет одну точку входа — HTTP-порт, через который проходят все запросы. Клиент взаимодействует с единым целым. При переходе к микросервисам эта модель разрушается: вместо одного приложения появляются десятки или сотни независимых сервисов, каждый со своим API и сетевым адресом. Прямое обращение клиентов ко всем сервисам создаёт фундаментальные проблемы: клиент должен знать топологию сети, обеспечивать отказоустойчивость для каждого вызова, дублировать логику аутентификации и преобразования данных. Именно здесь возникает необходимость в паттерне API Gateway — единой интеллектуальной точке входа, которая инкапсулирует внутреннюю структуру системы и предоставляет клиентам унифицированный интерфейс. Spring Cloud Gateway (SCG) — это реализация этого паттерна в экосистеме Spring, построенная на реактивной парадигме для удовлетворения требований современных высоконагруженных распределённых систем.
Архитектурное позиционирование и место в экосистеме
Spring Cloud Gateway функционирует как шлюз прикладного уровня (Layer 7) в модели OSI.
Его позиция строго определена: между внешними клиентами (мобильные приложения, браузеры, сторонние системы) и внутренним кластером микросросервисов. Он не является заменой балансировщику нагрузки сетевого уровня (например, AWS NLB или hardware-балансировщику), но работает в тесной связке с ним. Типичная многоуровневая архитектура включает внешний балансировщик, который распределяет трафик между несколькими инстансами SCG для обеспечения отказоустойчивости и масштабируемости, а сам SCG уже занимается интеллектуальной маршрутизацией к конкретным сервисам.
В экосистеме Spring Cloud Gateway является эволюционным преемником Zuul 1.x. Zuul 1, построенный на блокирующем сервлетном API, имел архитектурные ограничения, связанные с выделением потока на каждый соединение, что создавало проблемы при большом количестве одновременных соединений, особенно с длительными запросами (например, Server-Sent Events, WebSockets). SCG был создан с нуля на реактивном стеке Spring WebFlux и проекте Reactor, что позволило реализовать полностью неблокирующую, асинхронную архитектуру, способную эффективно работать с тысячами одновременных соединений на скромных аппаратных ресурсах. Это стратегическое выравнивание с реактивной парадигмой, которую Spring продвигает для построения масштабируемых систем.
#Java #middle #Spring_Cloud_Gateway
В монолитной архитектуре приложение имеет одну точку входа — HTTP-порт, через который проходят все запросы. Клиент взаимодействует с единым целым. При переходе к микросервисам эта модель разрушается: вместо одного приложения появляются десятки или сотни независимых сервисов, каждый со своим API и сетевым адресом. Прямое обращение клиентов ко всем сервисам создаёт фундаментальные проблемы: клиент должен знать топологию сети, обеспечивать отказоустойчивость для каждого вызова, дублировать логику аутентификации и преобразования данных. Именно здесь возникает необходимость в паттерне API Gateway — единой интеллектуальной точке входа, которая инкапсулирует внутреннюю структуру системы и предоставляет клиентам унифицированный интерфейс. Spring Cloud Gateway (SCG) — это реализация этого паттерна в экосистеме Spring, построенная на реактивной парадигме для удовлетворения требований современных высоконагруженных распределённых систем.
Архитектурное позиционирование и место в экосистеме
Spring Cloud Gateway функционирует как шлюз прикладного уровня (Layer 7) в модели OSI.
Его позиция строго определена: между внешними клиентами (мобильные приложения, браузеры, сторонние системы) и внутренним кластером микросросервисов. Он не является заменой балансировщику нагрузки сетевого уровня (например, AWS NLB или hardware-балансировщику), но работает в тесной связке с ним. Типичная многоуровневая архитектура включает внешний балансировщик, который распределяет трафик между несколькими инстансами SCG для обеспечения отказоустойчивости и масштабируемости, а сам SCG уже занимается интеллектуальной маршрутизацией к конкретным сервисам.
В экосистеме Spring Cloud Gateway является эволюционным преемником Zuul 1.x. Zuul 1, построенный на блокирующем сервлетном API, имел архитектурные ограничения, связанные с выделением потока на каждый соединение, что создавало проблемы при большом количестве одновременных соединений, особенно с длительными запросами (например, Server-Sent Events, WebSockets). SCG был создан с нуля на реактивном стеке Spring WebFlux и проекте Reactor, что позволило реализовать полностью неблокирующую, асинхронную архитектуру, способную эффективно работать с тысячами одновременных соединений на скромных аппаратных ресурсах. Это стратегическое выравнивание с реактивной парадигмой, которую Spring продвигает для построения масштабируемых систем.
#Java #middle #Spring_Cloud_Gateway
👍4
Детальный разбор решаемых проблем
1. Интеллектуальная маршрутизация и абстракция сервисов
Базовая и критически важная функция — динамическая маршрутизация запроса к соответствующему backend-сервису на основе содержимого запроса. SCG анализирует HTTP-запросы (путь, заголовки, параметры) и, используя механизм предикатов, определяет, какой маршрут должен быть применён. Каждый маршрут связан с определённым URI назначения (например, lb://SERVICE-NAME при использовании Service Discovery через Spring Cloud LoadBalancer). Это позволяет полностью скрыть от клиента реальные сетевые адреса и даже имена хостов сервисов. Клиент обращается к api.company.com/orders, а шлюз решает, что этот запрос должен уйти в сервис order-service-v2, работающий в трёх экземплярах. Механизм балансировки нагрузки на стороне клиента (client-side load balancing) интегрирован непосредственно в процесс проксирования.
2. Централизованная безопасность и контроль доступа
Вместо того чтобы встраивать идентификацию и авторизацию в каждый микросервис, что приводит к дублированию кода и сложности управления политиками, SCG позволяет централизовать эту логику. Типичный сценарий: фильтр шлюза (Gateway Filter) перехватывает входящий запрос, извлекает JWT-токен из заголовка Authorization, валидирует его подпись и срок действия, декодирует claims (утверждения) и либо добавляет обогащённую информацию (например, роли пользователя) в заголовки для передачи в нижестоящий сервис, либо сразу отвергает запрос с кодом 401 или 403. Это реализует паттерн "валидация токена на периметре". Таким образом, внутренние сервисы могут доверять данным из заголовков (которые, впрочем, должны проверяться на целостность в средах с высокими требованиями безопасности), что избавляет их от необходимости иметь доступ к секретам для проверки подписи JWT.
3. Применение кросс-сервисных политик
Многие требования, такие как ограничение частоты запросов (rate limiting), применяются на уровне всего API или конкретного пользователя, а не отдельного сервиса. SCG может интегрироваться с системами вроде Redis для реализации алгоритмов ограничения (например, "token bucket" или "sliding window"). Фильтр шлюза считает количество запросов с определённого ключа (IP, user ID, API key) за временное окно и блокирует превысившие лимит запросы до того, как они создадут нагрузку на бизнес-сервисы. Аналогично реализуется политика Circuit Breaker (размыкатель цепи): SCG отслеживает ошибки или задержки при вызовах к конкретному сервису и при достижении порога временно "разрывает цепь", перенаправляя запросы на заранее определённый fallback-ответ (например, кэшированные данные или заглушку), не нагружая падающий сервис. Это повышает отказоустойчивость всей системы.
4. Наблюдаемость и анализ трафика (Observability)
Как критически важный узел, через который проходит весь трафик, SCG является идеальным местом для сбора телеметрии. Он может автоматически генерировать метрики (например, количество запросов в секунду, задержки, процент ошибок) для каждого маршрута и экспортировать их в системы мониторинга типа Prometheus через Micrometer. Кроме того, он присваивает и распространяет уникальные идентификаторы запросов (trace ID), которые позволяют агрегаторам трассировки, таким как Zipkin или Sleuth, восстановить полный путь запроса по всем микросервисам. Это обеспечивает сквозную видимость, без которой отладка распределённой системы крайне затруднена.
5. Трансформация запросов и ответов
SCG выполняет роль адаптера между внешним и внутренним API. Это может быть простая перезапись путей (rewrite path): клиент отправляет запрос на /api/v1/user, а шлюз перенаправляет его на внутренний сервис по пути /user. Более сложные сценарии включают модификацию заголовков (добавление, удаление), трансформацию тела запроса/ответа (например, из XML в JSON) с помощью встроенных или кастомных фильтров. Это позволяет внутренним сервисам эволюционировать независимо от клиентов, а шлюзу — обеспечивать обратную совместимость.
#Java #middle #Spring_Cloud_Gateway
1. Интеллектуальная маршрутизация и абстракция сервисов
Базовая и критически важная функция — динамическая маршрутизация запроса к соответствующему backend-сервису на основе содержимого запроса. SCG анализирует HTTP-запросы (путь, заголовки, параметры) и, используя механизм предикатов, определяет, какой маршрут должен быть применён. Каждый маршрут связан с определённым URI назначения (например, lb://SERVICE-NAME при использовании Service Discovery через Spring Cloud LoadBalancer). Это позволяет полностью скрыть от клиента реальные сетевые адреса и даже имена хостов сервисов. Клиент обращается к api.company.com/orders, а шлюз решает, что этот запрос должен уйти в сервис order-service-v2, работающий в трёх экземплярах. Механизм балансировки нагрузки на стороне клиента (client-side load balancing) интегрирован непосредственно в процесс проксирования.
2. Централизованная безопасность и контроль доступа
Вместо того чтобы встраивать идентификацию и авторизацию в каждый микросервис, что приводит к дублированию кода и сложности управления политиками, SCG позволяет централизовать эту логику. Типичный сценарий: фильтр шлюза (Gateway Filter) перехватывает входящий запрос, извлекает JWT-токен из заголовка Authorization, валидирует его подпись и срок действия, декодирует claims (утверждения) и либо добавляет обогащённую информацию (например, роли пользователя) в заголовки для передачи в нижестоящий сервис, либо сразу отвергает запрос с кодом 401 или 403. Это реализует паттерн "валидация токена на периметре". Таким образом, внутренние сервисы могут доверять данным из заголовков (которые, впрочем, должны проверяться на целостность в средах с высокими требованиями безопасности), что избавляет их от необходимости иметь доступ к секретам для проверки подписи JWT.
3. Применение кросс-сервисных политик
Многие требования, такие как ограничение частоты запросов (rate limiting), применяются на уровне всего API или конкретного пользователя, а не отдельного сервиса. SCG может интегрироваться с системами вроде Redis для реализации алгоритмов ограничения (например, "token bucket" или "sliding window"). Фильтр шлюза считает количество запросов с определённого ключа (IP, user ID, API key) за временное окно и блокирует превысившие лимит запросы до того, как они создадут нагрузку на бизнес-сервисы. Аналогично реализуется политика Circuit Breaker (размыкатель цепи): SCG отслеживает ошибки или задержки при вызовах к конкретному сервису и при достижении порога временно "разрывает цепь", перенаправляя запросы на заранее определённый fallback-ответ (например, кэшированные данные или заглушку), не нагружая падающий сервис. Это повышает отказоустойчивость всей системы.
4. Наблюдаемость и анализ трафика (Observability)
Как критически важный узел, через который проходит весь трафик, SCG является идеальным местом для сбора телеметрии. Он может автоматически генерировать метрики (например, количество запросов в секунду, задержки, процент ошибок) для каждого маршрута и экспортировать их в системы мониторинга типа Prometheus через Micrometer. Кроме того, он присваивает и распространяет уникальные идентификаторы запросов (trace ID), которые позволяют агрегаторам трассировки, таким как Zipkin или Sleuth, восстановить полный путь запроса по всем микросервисам. Это обеспечивает сквозную видимость, без которой отладка распределённой системы крайне затруднена.
5. Трансформация запросов и ответов
SCG выполняет роль адаптера между внешним и внутренним API. Это может быть простая перезапись путей (rewrite path): клиент отправляет запрос на /api/v1/user, а шлюз перенаправляет его на внутренний сервис по пути /user. Более сложные сценарии включают модификацию заголовков (добавление, удаление), трансформацию тела запроса/ответа (например, из XML в JSON) с помощью встроенных или кастомных фильтров. Это позволяет внутренним сервисам эволюционировать независимо от клиентов, а шлюзу — обеспечивать обратную совместимость.
#Java #middle #Spring_Cloud_Gateway
👍3🤯1
Конкурентный ландшафт и выбор технологии
Рынок gateway-решений насыщен. SCG не единственный и не универсальный вариант.
Kong
Сильный, промышленный, плагинно-ориентированный API-gateway на базе NGINX и LuaJIT. Скорее всего превосходит SCG по пропускной способности на уровне низкоуровневой сети. Но менее гибок для JVM-экосистемы и глубокой интеграции с Spring.
NGINX
Высокопроизводительный L7-proxy, но без богатой программируемости. SCG выигрывает там, где нужны сложные фильтры и логика на Java/Kotlin.
Envoy
Современный proxy, облачная стандартизация, основа для Istio. Максимально производительный, нативный, ориентирован на service mesh. SCG выигрывает на уровне интеграции с приложением и кастомизируемости внутри JVM.
Traefik
Простой, легкий, динамический, ориентирован на Docker/Kubernetes. SCG более мощен при построении сложных политик, при наличии Spring-экосистемы.
Spring Cloud Gateway vs. Spring MVC (и Zuul 1)
Здесь различие фундаментально и лежит в плоскости архитектурной парадигмы.
Spring MVC и Zuul 1 построены на сервлетной модели, которая привязывает каждый HTTP-запрос к потоку (thread) на всё время его обработки. Потоки — дорогой ресурс, их количество в пуле ограничено (часто 200-500). Когда все потоки заняты ожиданием ответа от медленного backend-сервиса, шлюз перестаёт принимать новые запросы, даже если CPU простаивает. SCG, построенный на WebFlux и Reactor Netty, использует событийно-ориентированную, неблокирующую модель. Небольшое количество потоков (часто равное количеству ядер CPU) обрабатывает множество соединений. Когда запрос проксируется к backend-сервису, поток не блокируется в ожидании ответа, а освобождается для обработки других событий (новых запросов или пришедших ответов). Колбэк вызывается, когда ответ готов. Это позволяет одному экземпляру SCG эффективно обслуживать десятки тысяч одновременных long-lived соединений (например, для streaming API) с предскатуемым потреблением памяти. С точки зрения JVM, это означает отказ от пула потоков Tomcat/Jetty и работу на основе NIO-селекторов в Netty, где события диспетчеризуются ядром Reactor.
Архитектура: Reactor Netty и WebFlux
Работа Spring Cloud Gateway начинается с автоконфигурации Spring Boot. Ядром является ReactorNettyHttpPredicateHandlerMapping. Весь входящий трафик принимается сервером Netty, который работает на реактивном канале HttpServer.
Когда Netty принимает новый HTTP-запрос, он преобразует его в реактивный тип ServerHttpRequest и запускает цепочку обработки.
Основной цикл выглядит так:
Сопоставление маршрута (Route Predicate Handler Mapping): Для входящего ServerHttpRequest проверяются все определённые в контексте маршруты (Route). Каждый маршрут содержит коллекцию Predicate. Предикаты — это условия на основе запроса (например, Path=/api/**, Header=X-Request-Id, Method=GET). Проверка выполняется последовательно до первого совпадения. Этот процесс не блокирующий, все предикаты оцениваются в том же реактивном потоке.
Сборка цепочки фильтров (Filtering Web Handler): Найденный маршрут содержит упорядоченный список фильтров (GatewayFilter) и URI назначения. Формируется цепочка обработки DefaultGatewayFilterChain. Фильтры делятся на два типа: "pre-filters" (выполняются до вызова проксируемого сервиса) и "post-filters" (выполняются после получения ответа). Сам вызов проксируемого сервиса также реализован как специальный фильтр — NettyRoutingFilter.
Выполнение цепочки фильтров (Reactive Pipeline): Цепочка выполняется как реактивный пайплайн. Например, пре-фильтр аутентификации проверяет токен, используя реактивный ReactiveJwtDecoder, который не блокирует поток. Если вызов к сервису ключей необходим, он выполняется асинхронно. Затем фильтр преобразования путей модифицирует ServerHttpRequest. Ключевой фильтр LoadBalancerClientFilter взаимодействует с реактивным ReactorLoadBalancer для преобразования логического имени сервиса (из lb://SERVICE) в реальный физический адрес, выбранный с учётом балансировки.
#Java #middle #Spring_Cloud_Gateway
Рынок gateway-решений насыщен. SCG не единственный и не универсальный вариант.
Kong
Сильный, промышленный, плагинно-ориентированный API-gateway на базе NGINX и LuaJIT. Скорее всего превосходит SCG по пропускной способности на уровне низкоуровневой сети. Но менее гибок для JVM-экосистемы и глубокой интеграции с Spring.
NGINX
Высокопроизводительный L7-proxy, но без богатой программируемости. SCG выигрывает там, где нужны сложные фильтры и логика на Java/Kotlin.
Envoy
Современный proxy, облачная стандартизация, основа для Istio. Максимально производительный, нативный, ориентирован на service mesh. SCG выигрывает на уровне интеграции с приложением и кастомизируемости внутри JVM.
Traefik
Простой, легкий, динамический, ориентирован на Docker/Kubernetes. SCG более мощен при построении сложных политик, при наличии Spring-экосистемы.
Spring Cloud Gateway vs. Spring MVC (и Zuul 1)
Здесь различие фундаментально и лежит в плоскости архитектурной парадигмы.
Spring MVC и Zuul 1 построены на сервлетной модели, которая привязывает каждый HTTP-запрос к потоку (thread) на всё время его обработки. Потоки — дорогой ресурс, их количество в пуле ограничено (часто 200-500). Когда все потоки заняты ожиданием ответа от медленного backend-сервиса, шлюз перестаёт принимать новые запросы, даже если CPU простаивает. SCG, построенный на WebFlux и Reactor Netty, использует событийно-ориентированную, неблокирующую модель. Небольшое количество потоков (часто равное количеству ядер CPU) обрабатывает множество соединений. Когда запрос проксируется к backend-сервису, поток не блокируется в ожидании ответа, а освобождается для обработки других событий (новых запросов или пришедших ответов). Колбэк вызывается, когда ответ готов. Это позволяет одному экземпляру SCG эффективно обслуживать десятки тысяч одновременных long-lived соединений (например, для streaming API) с предскатуемым потреблением памяти. С точки зрения JVM, это означает отказ от пула потоков Tomcat/Jetty и работу на основе NIO-селекторов в Netty, где события диспетчеризуются ядром Reactor.
Архитектура: Reactor Netty и WebFlux
Работа Spring Cloud Gateway начинается с автоконфигурации Spring Boot. Ядром является ReactorNettyHttpPredicateHandlerMapping. Весь входящий трафик принимается сервером Netty, который работает на реактивном канале HttpServer.
Когда Netty принимает новый HTTP-запрос, он преобразует его в реактивный тип ServerHttpRequest и запускает цепочку обработки.
Основной цикл выглядит так:
Сопоставление маршрута (Route Predicate Handler Mapping): Для входящего ServerHttpRequest проверяются все определённые в контексте маршруты (Route). Каждый маршрут содержит коллекцию Predicate. Предикаты — это условия на основе запроса (например, Path=/api/**, Header=X-Request-Id, Method=GET). Проверка выполняется последовательно до первого совпадения. Этот процесс не блокирующий, все предикаты оцениваются в том же реактивном потоке.
Сборка цепочки фильтров (Filtering Web Handler): Найденный маршрут содержит упорядоченный список фильтров (GatewayFilter) и URI назначения. Формируется цепочка обработки DefaultGatewayFilterChain. Фильтры делятся на два типа: "pre-filters" (выполняются до вызова проксируемого сервиса) и "post-filters" (выполняются после получения ответа). Сам вызов проксируемого сервиса также реализован как специальный фильтр — NettyRoutingFilter.
Выполнение цепочки фильтров (Reactive Pipeline): Цепочка выполняется как реактивный пайплайн. Например, пре-фильтр аутентификации проверяет токен, используя реактивный ReactiveJwtDecoder, который не блокирует поток. Если вызов к сервису ключей необходим, он выполняется асинхронно. Затем фильтр преобразования путей модифицирует ServerHttpRequest. Ключевой фильтр LoadBalancerClientFilter взаимодействует с реактивным ReactorLoadBalancer для преобразования логического имени сервиса (из lb://SERVICE) в реальный физический адрес, выбранный с учётом балансировки.
#Java #middle #Spring_Cloud_Gateway
👍2🤯1
Проксирование запроса (NettyRoutingFilter): Это кульминация. Фильтр берет обогащённый ServerHttpRequest, конвертирует его в запрос Netty (HttpClientRequest) и отправляет через неблокирующий HTTP-клиент Netty (HttpClient) к целевому сервису. Клиент Netty использует ту же событийно-ориентированную модель. Запрос ставится в очередь на отправку, и текущий поток немедленно освобождается. Когда от backend-сервиса приходит ответ (HttpClientResponse), Netty генерирует событие, которое подхватывается реактивным пайплайном, преобразуя ответ в ServerHttpResponse.
Обработка ответа (Post-Filters): Запускается "post-filter" часть цепочки. Здесь могут работать фильтры добавления стандартных заголовков, логирования, преобразования тела ответа. Итоговый ServerHttpResponse записывается в исходный канал Netty к клиенту.
В памяти JVM это проявляется как доминирование объектов реактивных стримов (Mono, Flux), цепочек операторов и лямбда-выражений в heap. Стек вызовов глубокий, но асинхронный: в дампе потока вы не увидите блокирующего вызова HttpClient. Вместо этого увидите фреймы типа onNext, onComplete из реализации Reactor. Пул потоков Netty (обычно reactor-http-nio-) активен, но количество потоков мало. Основное потребление памяти связано с буферами Netty для HTTP-сообщений (которые используют пул ByteBuf для эффективного управления), и объектами, представляющими запросы/ответы.
Типичные сценарии реализации
Rate Limiting с использованием Redis: Фильтр, реализующий алгоритм "скользящего окна". Для каждого запроса вычисляется ключ (например, user:123:path:/api/orders). С помощью реактивного клиента Redis (ReactiveRedisTemplate) выполняются команды ZREMRANGEBYSCORE (удаление старых записей) и ZADD + ZCOUNT (добавление текущего запроса и подсчёт количества запросов в окне). Вся эта последовательность выполняется как атомарный Lua-скрипт на стороне Redis для обеспечения консистентности. Если лимит превышен, цепочка прерывается с возвратом 429 Too Many Requests.
Аутентификация и передача контекста: Кастомный GatewayFilter в порядке pre извлекает JWT из заголовка. Используя ReactiveJwtDecoder (который может кэшировать JWK для проверки подписи), токен декодируется. Из claims извлекается идентификатор пользователя и его роли, которые затем добавляются в заголовки запроса, например, X-User-Id и X-User-Roles. Важный нюанс: для предотвращения подмены заголовков внутренними сервисами, эти заголовки должны либо очищаться шлюзом от входящих значений, либо внутренние сервисы должны доверять только конкретным заголовкам, установленным шлюзом (что может быть обеспечено настройками сетевой безопасности).
API Composition (Агрегация): Хотя SCG не является специализированным агрегатором (как GraphQL BFF), он может выполнять простую агрегацию с помощью фильтра ModifyResponseBodyGatewayFilterFactory. Например, клиенту нужны данные пользователя вместе с его последним заказом. Шлюз может последовательно вызвать user-service и, используя данные из ответа, вызвать order-service, а затем объединить результаты в единый JSON. Эта операция выполняется неблокирующе с помощью операторов Reactor flatMap или zip. Однако для сложных агрегаций с множественными зависимостями предпочтительнее выделенный BFF-сервис.
Circuit Breaker с Resilience4J: SCG интегрируется с Resilience4J через конфигурацию. Для маршрута определяется конфигурация Circuit Breaker с параметрами порога ошибок, временем ожидания в полуоткрытом состоянии и т.д. Когда фильтр активирован, все вызовы через него оборачиваются в защитный контур. В случае открытия контура запросы не идут к падающему сервису, а перенаправляются на заданный fallbackUri, который может указывать на статический ответ или простой сервис-заглушку, возвращающий данные из кэша.
#Java #middle #Spring_Cloud_Gateway
Обработка ответа (Post-Filters): Запускается "post-filter" часть цепочки. Здесь могут работать фильтры добавления стандартных заголовков, логирования, преобразования тела ответа. Итоговый ServerHttpResponse записывается в исходный канал Netty к клиенту.
В памяти JVM это проявляется как доминирование объектов реактивных стримов (Mono, Flux), цепочек операторов и лямбда-выражений в heap. Стек вызовов глубокий, но асинхронный: в дампе потока вы не увидите блокирующего вызова HttpClient. Вместо этого увидите фреймы типа onNext, onComplete из реализации Reactor. Пул потоков Netty (обычно reactor-http-nio-) активен, но количество потоков мало. Основное потребление памяти связано с буферами Netty для HTTP-сообщений (которые используют пул ByteBuf для эффективного управления), и объектами, представляющими запросы/ответы.
Типичные сценарии реализации
Rate Limiting с использованием Redis: Фильтр, реализующий алгоритм "скользящего окна". Для каждого запроса вычисляется ключ (например, user:123:path:/api/orders). С помощью реактивного клиента Redis (ReactiveRedisTemplate) выполняются команды ZREMRANGEBYSCORE (удаление старых записей) и ZADD + ZCOUNT (добавление текущего запроса и подсчёт количества запросов в окне). Вся эта последовательность выполняется как атомарный Lua-скрипт на стороне Redis для обеспечения консистентности. Если лимит превышен, цепочка прерывается с возвратом 429 Too Many Requests.
Аутентификация и передача контекста: Кастомный GatewayFilter в порядке pre извлекает JWT из заголовка. Используя ReactiveJwtDecoder (который может кэшировать JWK для проверки подписи), токен декодируется. Из claims извлекается идентификатор пользователя и его роли, которые затем добавляются в заголовки запроса, например, X-User-Id и X-User-Roles. Важный нюанс: для предотвращения подмены заголовков внутренними сервисами, эти заголовки должны либо очищаться шлюзом от входящих значений, либо внутренние сервисы должны доверять только конкретным заголовкам, установленным шлюзом (что может быть обеспечено настройками сетевой безопасности).
API Composition (Агрегация): Хотя SCG не является специализированным агрегатором (как GraphQL BFF), он может выполнять простую агрегацию с помощью фильтра ModifyResponseBodyGatewayFilterFactory. Например, клиенту нужны данные пользователя вместе с его последним заказом. Шлюз может последовательно вызвать user-service и, используя данные из ответа, вызвать order-service, а затем объединить результаты в единый JSON. Эта операция выполняется неблокирующе с помощью операторов Reactor flatMap или zip. Однако для сложных агрегаций с множественными зависимостями предпочтительнее выделенный BFF-сервис.
Circuit Breaker с Resilience4J: SCG интегрируется с Resilience4J через конфигурацию. Для маршрута определяется конфигурация Circuit Breaker с параметрами порога ошибок, временем ожидания в полуоткрытом состоянии и т.д. Когда фильтр активирован, все вызовы через него оборачиваются в защитный контур. В случае открытия контура запросы не идут к падающему сервису, а перенаправляются на заданный fallbackUri, который может указывать на статический ответ или простой сервис-заглушку, возвращающий данные из кэша.
#Java #middle #Spring_Cloud_Gateway
👍2🤯1
Архитектура Spring Cloud Gateway: Внутренние компоненты и жизненный цикл запроса
В основе Spring Cloud Gateway лежат три ключевые абстракции, которые формируют декларативную модель конфигурации: Route, Predicate и Filter. Эти сущности организованы в строгую иерархию, где каждая выполняет свою роль в обработке входящего запроса.
Route (Маршрут)
Является центральной конфигурационной единицей. Он определяет полный путь обработки конкретного запроса от получения до возврата ответа. Маршрут инкапсулирует три основных элемента: уникальный идентификатор, целевой URI (куда будет перенаправлен запрос после обработки) и упорядоченные коллекции предикатов и фильтров. Внутренне маршрут представлен классом org.springframework.cloud.gateway.route.Route, который является иммутабельным объектом. Иммутабельность критически важна, поскольку маршруты могут динамически обновляться в runtime (например, через обновление конфигурации из Spring Cloud Config), и необходимо гарантировать согласованное состояние во время обработки запроса.
Конфигурация маршрута в YAML демонстрирует эту структуру:
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
В основе 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
Когда 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-фильтра для логирования:
#Java #middle #Spring_Cloud_Gateway
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:
Фаза 5: Пост-обработка ответа и завершение
После получения ответа от целевого сервиса выполняется оставшаяся часть цепочки фильтров — post-фильтры. Эти фильтры получают доступ как к оригинальному запросу, так и к ответу от целевого сервиса. Они могут модифицировать статус-код, заголовки, тело ответа.
Пример post-фильтра для добавления стандартных заголовков безопасности:
После выполнения всех post-фильтров финальный ответ записывается обратно в исходный канал Netty к клиенту. Netty берёт на себя эффективную отправку данных, включая chunked encoding для stream-ответов.
#Java #middle #Spring_Cloud_Gateway
После выполнения всех 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 фильтра с кастомной конфигурацией:
Управление памятью и производительностью в 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
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
Конфигурация маршрутов в Spring Cloud Gateway
Декларативная конфигурация через application.yml
Конфигурация через YAML-файлы является декларативным подходом, где маршруты определяются статически на этапе компиляции или до запуска приложения. Этот метод обеспечивает простоту чтения, версионирование через системы контроля версий и простоту аудита изменений.
Базовая структура конфигурации маршрута
В YAML маршруты конфигурируются через иерархию spring.cloud.gateway.routes. Каждый маршрут должен иметь уникальный идентификатор, URI назначения, список предикатов и опциональный список фильтров.
Детализация предикатов
Предикаты в YAML конфигурируются как список строк, где каждая строка соответствует фабрике предикатов и её параметрам. Формат: Name=arg1,arg2,...,key1=value1,key2=value2.
Предикат Path:
Составные предикаты с помощью After и Before:
Предикат Query для проверки параметров:
Предикат RemoteAddr для контроля доступа по IP:
Кастомный предикат с использованием SpEL:
Фильтры в YAML конфигурации
Фильтры определяют преобразования, применяемые к запросу или ответу. Они выполняются в порядке объявления.
Базовые фильтры преобразования:
Фильтры для работы с телом запроса:
#Java #middle #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
👍2
Фильтры устойчивости и ограничения:
URI и метаданные
URI назначения поддерживает несколько схем:
Метаданные маршрута предоставляют дополнительную конфигурацию, специфичную для маршрута:
#Java #middle #Spring_Cloud_Gateway
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
👍2
Java DSL: Fluent API для программируемой конфигурации
Java DSL через RouteLocatorBuilder предоставляет императивный способ определения маршрутов, который позволяет использовать всю мощь Java: условия, циклы, вызовы методов, dependency injection.
Базовый fluent-синтаксис
Преимущества Java DSL перед YAML
1. Использование Spring Beans в конфигурации:
#Java #middle #Spring_Cloud_Gateway
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
👍3
2. Генерация маршрутов на основе данных из внешних источников:
3. Создание сложных составных предикатов:
Что можно делать только в DSL и нельзя в YAML
1. Использование произвольных Predicate<ServerWebExchange>:
2. Интеграция с внешними системами конфигурации:
#Java #middle #Spring_Cloud_Gateway
@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
👍3
3. Генерация маршрутов в runtime:
Динамическая маршрутизация
DiscoveryClientRouteDefinitionLocator
DiscoveryClientRouteDefinitionLocator обеспечивает автоматическое создание маршрутов на основе сервисов, зарегистрированных в Service Discovery. При активации, для каждого зарегистрированного сервиса создаётся маршрут, который перенаправляет трафик на этот сервис.
Эта конфигурация создаст маршруты вида:
http://gateway/services/user-service/** → lb://user-service
http://gateway/services/order-service/** → lb://order-service
Интеграция с Eureka
Для интеграции с Eureka необходимо добавить зависимость и соответствующую конфигурацию:
Eureka-специфичные метаданные могут использоваться для управления маршрутизацией:
#Java #middle #Spring_Cloud_Gateway
@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
👍2
Интеграция с Consul
Consul предоставляет более богатые возможности для service discovery и health checking:
Consul теги могут использоваться для реализации сложной логики маршрутизации:
#Java #middle #Spring_Cloud_Gateway
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
👍2
Кастомный RouteDefinitionRepository
Для полностью динамической маршрутизации можно реализовать собственный RouteDefinitionRepository, который будет загружать маршруты из произвольного источника (БД, файловая система, внешний сервис).
Базовая реализация с поддержкой обновлений:
Реализация с поддержкой веб-интерфейса для управления маршрутами:
#Java #middle #Spring_Cloud_Gateway
Для полностью динамической маршрутизации можно реализовать собственный 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
👍2
Интеграция с внешними системами конфигурации:
Обработка событий обновления маршрутов:
#Java #middle #Spring_Cloud_Gateway
@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
👍2
Predicates (условия маршрутизации)
Predicates в Spring Cloud Gateway — это функции, которые принимают ServerWebExchange и возвращают boolean, определяя, попадает ли входящий запрос под конкретный Route. Predicates — фундамент маршрутизации: маршрут считается подходящим только если все его предикаты возвращают true (логическое AND между перечисленными предикатами одного маршрута). Понимание механизмов работы предикатов критично для корректного и производительного построения маршрутов.
Ключевые принципы — механика и порядок
Маршрут выбран, если все его предикаты истинны.
В конфигурации YAML или Java DSL, когда у Route указано несколько предикатов, они объединяются логически через AND.
Порядок маршрутов важен.
Gateway перебирает доступные маршруты в порядке, определённом компонентом, который предоставляет Flux<Route> (обычно RouteDefinitionLocator → RouteDefinitionRouteLocator). Конкретный порядок определяется полем order (если задано) или порядком получения/создания маршрутов. Первый подходящий маршрут (в смысле совпадения предикатов) берётся в обработку. Следовательно, дешёвые «фильтрующие» предикаты (например, Host, Method) следует располагать раньше в конфигурации маршрутов по логике — не путать с порядком предикатов в одном маршруте: в одном маршруте порядок предикатов обычно не меняет семантику, но влияет на порядок выполнения (см. рекомендации по эффективности ниже).
Оценка предикатов — последовательная и краткая:
Для одного маршрута предикаты обычно вычисляются последовательно и, при первом false, дальнейшая проверка предикатов для этого маршрута останавливается (short-circuit). Тем не менее, поскольку маршрутов может быть много, Gateway продолжит проверять следующие маршруты до нахождения первого подходящего.
Рекомендации по производительности:
Помещайте дешёвые и часто отсекающие проверки (например, Host, Method, Path) до дорогих вычислений (например, проверки содержимого тела, обращения в внешние сервисы или сложных регулярных выражений).
Избегайте предикатов, которые делают блокирующие операции — predicated должны быть чисто вычислительными и быстрыми. При необходимости используйте Java DSL и инкапсулируйте асинхронную работу в фильтрах, а не в предикатах.
Если нужна сложная проверка, лучше вынести её в отдельный, оптимизированный RoutePredicateFactory (см. ниже).
Композиция логики (AND/OR):
AND — поведение по умолчанию: несколько предикатов в одном Route объединяются через AND.
OR — декларативно не представлен как оператор на уровне одной записи YAML; для выражения OR чаще используют либо несколько маршрутов с разными наборами предикатов, либо пишут кастомный предикат, который внутри реализует логическое OR. В Java DSL также проще объявить несколько маршрутов или написать композиционный предикат в коде.
Встроенные предикаты — семантика, YAML-примеры, Java DSL-примеры
Ниже даны наиболее часто используемые предикаты, их поведение и примеры конфигурации.
1) Path
Проверяет соответствие URL-пути. Поддерживает шаблоны в стиле Ant (/users/**, /api/*/items) и также поддерживает регулярные группы при использовании Rewrite и фильтров.
YAML:
Java DSL:
Замечание: Path — обычно самый дешёвый и самый часто используемый предикат; ставьте его в начале списка логических проверок маршрутов.
2) Host
Проверяет заголовок Host (например, api.example.com). Поддерживает подстановки (*.example.com).
YAML:
Java DSL:
Заметка: Host — особенно полезен при мульти-тенантной конфигурации.
#Java #middle #Spring_Cloud_Gateway
Predicates в Spring Cloud Gateway — это функции, которые принимают ServerWebExchange и возвращают boolean, определяя, попадает ли входящий запрос под конкретный Route. Predicates — фундамент маршрутизации: маршрут считается подходящим только если все его предикаты возвращают true (логическое AND между перечисленными предикатами одного маршрута). Понимание механизмов работы предикатов критично для корректного и производительного построения маршрутов.
Ключевые принципы — механика и порядок
Маршрут выбран, если все его предикаты истинны.
В конфигурации YAML или Java DSL, когда у Route указано несколько предикатов, они объединяются логически через AND.
Порядок маршрутов важен.
Gateway перебирает доступные маршруты в порядке, определённом компонентом, который предоставляет Flux<Route> (обычно RouteDefinitionLocator → RouteDefinitionRouteLocator). Конкретный порядок определяется полем order (если задано) или порядком получения/создания маршрутов. Первый подходящий маршрут (в смысле совпадения предикатов) берётся в обработку. Следовательно, дешёвые «фильтрующие» предикаты (например, Host, Method) следует располагать раньше в конфигурации маршрутов по логике — не путать с порядком предикатов в одном маршруте: в одном маршруте порядок предикатов обычно не меняет семантику, но влияет на порядок выполнения (см. рекомендации по эффективности ниже).
Оценка предикатов — последовательная и краткая:
Для одного маршрута предикаты обычно вычисляются последовательно и, при первом false, дальнейшая проверка предикатов для этого маршрута останавливается (short-circuit). Тем не менее, поскольку маршрутов может быть много, Gateway продолжит проверять следующие маршруты до нахождения первого подходящего.
Рекомендации по производительности:
Помещайте дешёвые и часто отсекающие проверки (например, Host, Method, Path) до дорогих вычислений (например, проверки содержимого тела, обращения в внешние сервисы или сложных регулярных выражений).
Избегайте предикатов, которые делают блокирующие операции — predicated должны быть чисто вычислительными и быстрыми. При необходимости используйте Java DSL и инкапсулируйте асинхронную работу в фильтрах, а не в предикатах.
Если нужна сложная проверка, лучше вынести её в отдельный, оптимизированный RoutePredicateFactory (см. ниже).
Композиция логики (AND/OR):
AND — поведение по умолчанию: несколько предикатов в одном Route объединяются через AND.
OR — декларативно не представлен как оператор на уровне одной записи YAML; для выражения OR чаще используют либо несколько маршрутов с разными наборами предикатов, либо пишут кастомный предикат, который внутри реализует логическое OR. В Java DSL также проще объявить несколько маршрутов или написать композиционный предикат в коде.
Встроенные предикаты — семантика, YAML-примеры, Java DSL-примеры
Ниже даны наиболее часто используемые предикаты, их поведение и примеры конфигурации.
1) Path
Проверяет соответствие URL-пути. Поддерживает шаблоны в стиле Ant (/users/**, /api/*/items) и также поддерживает регулярные группы при использовании Rewrite и фильтров.
YAML:
predicates:
- Path=/api/users/**
Java DSL:
.route("users", r -> r.path("/api/users/**")
.uri("lb://user-service"))Замечание: Path — обычно самый дешёвый и самый часто используемый предикат; ставьте его в начале списка логических проверок маршрутов.
2) Host
Проверяет заголовок Host (например, api.example.com). Поддерживает подстановки (*.example.com).
YAML:
predicates:
- Host=api.example.com, *.internal.example.org
Java DSL:
.route("host-route", r -> r.host("api.example.com", "*.internal.example.org")
.uri("http://localhost:8080"))Заметка: Host — особенно полезен при мульти-тенантной конфигурации.
#Java #middle #Spring_Cloud_Gateway
👍1
3) Method
Сравнивает HTTP-метод (GET/POST/PUT/...).
YAML:
Java DSL:
4) Header
Проверяет наличие и (при задании) значение заголовка. Поддерживает регулярные выражения на значение.
YAML:
Java DSL:
Если значение не указано — проверяется только наличие заголовка.
5) Query
Проверяет наличие параметра query и, при наличии второго аргумента, — значение (регулярное выражение).
YAML:
Java DSL:
6) RemoteAddr
Проверяет IP-адрес клиента — поддерживает одиночные адреса и CIDR. Важно: в случае проксирования через балансировщики/Gateway заранее убедитесь, какие IP попадают в RemoteAddr (реальный клиент или IP reverse-proxy). Иногда требуется проверка X-Forwarded-For.
YAML:
Java DSL:
7) Weight / Load Balancer predicates (логика распределения) — концепция и применение
Weight — предикат, встречающийся в экосистеме Spring Cloud Gateway в контексте динамических маршрутов и Discovery клиента. Его цель — поддержка взвешенной маршрутизации: когда один и тот же serviceId представлен несколькими маршрутами с разными весами (weight), Gateway на этапе выбора маршрута учитывает веса (для канареечных релизов, A/B, geo-aware routing и т.п.).
Практическая схема использования:
Discovery-driven маршрутизация (DiscoveryClientRouteDefinitionLocator) может генерировать маршруты для каждого экземпляра/группы с метаданными веса.
Weight-предикат в составе маршрута проверяет — подходит ли текущий запрос под данный «весовой» маршрут (обычно реализуется через случайное/round-robin решение, зависящее от weight).
Примечание по реализации и совместимости: синтаксис и встроенные реализации могут различаться между версиями Spring Cloud.
Если требуется взвешенная маршрутизация в production, часто применяют:
Spring Cloud LoadBalancer для распределения трафика по инстансам;
Weight-подход в комбинировании с DiscoveryClientRouteDefinitionLocator или кастомным RouteDefinitionRepository.
Если нужен пример конфигурации — лучше реализовать weight механизмы через Discovery + metadata на стороне сервисов или через кастомный RoutePredicateFactory (пример ниже покажет, как писать свой предикат).
Сложные комбинации — AND / OR / NOT
AND: стандартная комбинация — несколько предикатов в одном Route → все должны быть true. Шаблон конфигурирования одинаков как в YAML, так и в DSL.
OR: декларативного OR в одном Route нет.
Для выражения OR есть два варианта:
Несколько маршрутов: создайте два (или больше) маршрута, каждый с различным предикатом; порядок важен, если маршруты пересекаются.
Кастомный предикат (composite): напишите RoutePredicateFactory, который внутри реализует логику a || b, и используйте его как обычный предикат. Это предпочтительно, если хотите избежать дублирования конфигурации и централизовать логику.
NOT / Negation: также не представлен в YAML как отдельный оператор; реализуется кастомным предикатом или путём композиции (например, создание предиката NotHeader).
#Java #middle #Spring_Cloud_Gateway
Сравнивает HTTP-метод (GET/POST/PUT/...).
YAML:
predicates:
- Method=GET,POST
Java DSL:
.route("read-write", r -> r.method(HttpMethod.GET, HttpMethod.POST)
.uri("lb://some-service"))4) Header
Проверяет наличие и (при задании) значение заголовка. Поддерживает регулярные выражения на значение.
YAML:
predicates:
- Header=X-Client, ^mobile-.*
Java DSL:
.route("header-route", r -> r.header("X-Client", "^mobile-.*")
.uri("http://mobile-backend"))Если значение не указано — проверяется только наличие заголовка.
5) Query
Проверяет наличие параметра query и, при наличии второго аргумента, — значение (регулярное выражение).
YAML:
predicates:
- Query=version, ^v[0-9]+$
Java DSL:
.route("versioned", r -> r.query("version", "^v[0-9]+$")
.uri("lb://versioned-service"))6) RemoteAddr
Проверяет IP-адрес клиента — поддерживает одиночные адреса и CIDR. Важно: в случае проксирования через балансировщики/Gateway заранее убедитесь, какие IP попадают в RemoteAddr (реальный клиент или IP reverse-proxy). Иногда требуется проверка X-Forwarded-For.
YAML:
predicates:
- RemoteAddr=192.168.1.0/24, 10.0.0.10
Java DSL:
.route("internal-only", r -> r.remoteAddr("192.168.0.0/16", "10.0.0.0/8")
.uri("lb://internal-service"))7) Weight / Load Balancer predicates (логика распределения) — концепция и применение
Weight — предикат, встречающийся в экосистеме Spring Cloud Gateway в контексте динамических маршрутов и Discovery клиента. Его цель — поддержка взвешенной маршрутизации: когда один и тот же serviceId представлен несколькими маршрутами с разными весами (weight), Gateway на этапе выбора маршрута учитывает веса (для канареечных релизов, A/B, geo-aware routing и т.п.).
Практическая схема использования:
Discovery-driven маршрутизация (DiscoveryClientRouteDefinitionLocator) может генерировать маршруты для каждого экземпляра/группы с метаданными веса.
Weight-предикат в составе маршрута проверяет — подходит ли текущий запрос под данный «весовой» маршрут (обычно реализуется через случайное/round-robin решение, зависящее от weight).
Примечание по реализации и совместимости: синтаксис и встроенные реализации могут различаться между версиями Spring Cloud.
Если требуется взвешенная маршрутизация в production, часто применяют:
Spring Cloud LoadBalancer для распределения трафика по инстансам;
Weight-подход в комбинировании с DiscoveryClientRouteDefinitionLocator или кастомным RouteDefinitionRepository.
Если нужен пример конфигурации — лучше реализовать weight механизмы через Discovery + metadata на стороне сервисов или через кастомный RoutePredicateFactory (пример ниже покажет, как писать свой предикат).
Сложные комбинации — AND / OR / NOT
AND: стандартная комбинация — несколько предикатов в одном Route → все должны быть true. Шаблон конфигурирования одинаков как в YAML, так и в DSL.
OR: декларативного OR в одном Route нет.
Для выражения OR есть два варианта:
Несколько маршрутов: создайте два (или больше) маршрута, каждый с различным предикатом; порядок важен, если маршруты пересекаются.
Кастомный предикат (composite): напишите RoutePredicateFactory, который внутри реализует логику a || b, и используйте его как обычный предикат. Это предпочтительно, если хотите избежать дублирования конфигурации и централизовать логику.
NOT / Negation: также не представлен в YAML как отдельный оператор; реализуется кастомным предикатом или путём композиции (например, создание предиката NotHeader).
#Java #middle #Spring_Cloud_Gateway
👍1
Написание собственного Predicate — RoutePredicateFactory
Когда встроенных предикатов недостаточно по логике или производительности, пишут собственные RoutePredicateFactory. Ниже — полный пример: реализуем предикат, который проверяет наличие параметра X-Feature и сопоставляет его по списку допустимых значений, при этом конфигурируемый через YAML.
1) Структура — класс предиката
Ключевые моменты:
Наследуемся от AbstractRoutePredicateFactory<Config>. Это даёт поддержку автоконфигурируемой десериализации конфигурации из YAML в Config.
apply(Config) возвращает Predicate<ServerWebExchange>, который будет выполняться для каждого запроса.
Config — POJO с геттерами/сеттерами: Spring автоматически маппит значения из YAML (через Binder).
2) Регистрация (component или bean)
Если не использовать @Component, можно зарегистрировать как bean через конфигурацию.
3) Использование в YAML
В AbstractRoutePredicateFactory стандартная разбивка аргументов (если конфиг простой) позволит подставить список значений. Для более сложной настройки (ключ:значение) необходимо переопределить shortcutFieldOrder() или использовать вложенный Config с именованными полями.
4) Использование в Java DSL
Если хотите inline-предикат в коде (без фабрики), Java DSL позволяет:
Однако такой inline-предикат теряет преимущества конфигурируемости через YAML и переиспользуемости.
#Java #middle #Spring_Cloud_Gateway
Когда встроенных предикатов недостаточно по логике или производительности, пишут собственные RoutePredicateFactory. Ниже — полный пример: реализуем предикат, который проверяет наличие параметра X-Feature и сопоставляет его по списку допустимых значений, при этом конфигурируемый через YAML.
1) Структура — класс предиката
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.web.server.ServerWebExchange;
import java.util.function.Predicate;
import java.util.List;
public class XFeatureRoutePredicateFactory extends AbstractRoutePredicateFactory<XFeatureRoutePredicateFactory.Config> {
public XFeatureRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
List<String> allowed = config.getAllowedValues();
boolean ignoreCase = config.isIgnoreCase();
return exchange -> {
List<String> headerValues = exchange.getRequest().getHeaders().get("X-Feature");
if (headerValues == null || headerValues.isEmpty()) {
return false;
}
for (String hv : headerValues) {
for (String allowedVal : allowed) {
if (ignoreCase) {
if (hv.equalsIgnoreCase(allowedVal)) return true;
} else {
if (hv.equals(allowedVal)) return true;
}
}
}
return false;
};
}
public static class Config {
private List<String> allowedValues;
private boolean ignoreCase = true;
public List<String> getAllowedValues() { return allowedValues; }
public void setAllowedValues(List<String> allowedValues) { this.allowedValues = allowedValues; }
public boolean isIgnoreCase() { return ignoreCase; }
public void setIgnoreCase(boolean ignoreCase) { this.ignoreCase = ignoreCase; }
}
}
Ключевые моменты:
Наследуемся от AbstractRoutePredicateFactory<Config>. Это даёт поддержку автоконфигурируемой десериализации конфигурации из YAML в Config.
apply(Config) возвращает Predicate<ServerWebExchange>, который будет выполняться для каждого запроса.
Config — POJO с геттерами/сеттерами: Spring автоматически маппит значения из YAML (через Binder).
2) Регистрация (component или bean)
import org.springframework.stereotype.Component;
@Component
public class XFeatureRoutePredicateFactory extends AbstractRoutePredicateFactory<XFeatureRoutePredicateFactory.Config> {
// ... код как выше
}
Если не использовать @Component, можно зарегистрировать как bean через конфигурацию.
3) Использование в YAML
spring:
cloud:
gateway:
routes:
- id: feature-route
uri: http://feature-service
predicates:
- XFeature=allowed1,allowed2
В AbstractRoutePredicateFactory стандартная разбивка аргументов (если конфиг простой) позволит подставить список значений. Для более сложной настройки (ключ:значение) необходимо переопределить shortcutFieldOrder() или использовать вложенный Config с именованными полями.
4) Использование в Java DSL
Если хотите inline-предикат в коде (без фабрики), Java DSL позволяет:
.route("xfeature-inline", r -> r.p(exchange -> {
List<String> values = exchange.getRequest().getHeaders().get("X-Feature");
return values != null && values.stream().anyMatch(v -> v.equalsIgnoreCase("allowed1"));
}).uri("http://feature-service"))Однако такой inline-предикат теряет преимущества конфигурируемости через YAML и переиспользуемости.
#Java #middle #Spring_Cloud_Gateway
👍1