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

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

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

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

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


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

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


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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

loadbalancer:
configurations: default

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


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


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

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

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


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


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

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

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

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

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

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

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



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

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

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

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

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

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


#Java #middle #Spring_Cloud_Gateway
👍1
Раздел 6. Коллекции в Java

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

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


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

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



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

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


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

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


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

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



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

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



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

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


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


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

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



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

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


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


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


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


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

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

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

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



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

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



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


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



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

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


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


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



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

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

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


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


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


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


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

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



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

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

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

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

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

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

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


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

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

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

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



#Java #middle #Spring_Cloud_Gateway
👍2
Фильтры устойчивости и ограничения:
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-синтаксис

@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. Генерация маршрутов на основе данных из внешних источников:
@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