Паттерны использования
Модификация элементов на лету
Условная замена
Коррекция данных
add(E e): Вставка нового элемента
Концептуальное назначение
Метод add(E e) представляет собой наиболее мощную операцию ListIterator — он позволяет вставлять новые элементы в список непосредственно перед элементом, который будет возвращен следующим вызовом next(). Это уникальная возможность, недоступная в базовом Iterator.
Семантика и поведение
Позиция вставки: Элемент вставляется перед элементом, который будет возвращен next(), или в конец списка, если next() вернет NoSuchElementException.
Влияние на итератор: После вставки последующий вызов next() вернет новый элемент, а previous() вернет только что вставленный элемент.
Состояние: Сбрасывает состояние lastReturned, поэтому последующий вызов remove() или set() выбросит исключение до следующего вызова next() или previous().
Внутренние механизмы реализации
Общий алгоритм
Реализация для ArrayList
Особенности:
Вызов ArrayList.add(index, element) со сдвигом элементов
Корректировка курсора и счетчиков
Обработка возможного расширения массива
Реализация для LinkedList
Особенности:
Эффективная вставка за O(1) после нахождения позиции
Манипуляции со ссылками между узлами
Автоматическая обработка граничных условий
Особенности производительности
Временная сложность:
ArrayList: O(n) — требуется сдвиг элементов
LinkedList: O(1) — вставка в найденную позицию
Память: Создает новый объект узла (LinkedList) или может вызвать расширение массива (ArrayList).
Потокобезопасность: Не является потокобезопасным.
#Java #для_новичков #beginner #ListIterator
Модификация элементов на лету
public static void transformStrings(List<String> strings) {
ListIterator<String> iterator = strings.listIterator();
while (iterator.hasNext()) {
String original = iterator.next();
String transformed = original.toUpperCase();
iterator.set(transformed);
}
}Условная замена
public static void replaceIf(List<Integer> numbers, Predicate<Integer> condition, Integer replacement) {
ListIterator<Integer> iterator = numbers.listIterator();
while (iterator.hasNext()) {
Integer current = iterator.next();
if (condition.test(current)) {
iterator.set(replacement);
}
}
}Коррекция данных
public static void correctDataSequence(List<DataPoint> data) {
if (data.isEmpty()) return;
ListIterator<DataPoint> iterator = data.listIterator();
DataPoint previous = iterator.next();
while (iterator.hasNext()) {
DataPoint current = iterator.next();
// Коррекция аномальных значений
if (isAnomaly(current, previous)) {
DataPoint corrected = interpolate(previous, current);
iterator.set(corrected);
}
previous = current;
}
}add(E e): Вставка нового элемента
Концептуальное назначение
Метод add(E e) представляет собой наиболее мощную операцию ListIterator — он позволяет вставлять новые элементы в список непосредственно перед элементом, который будет возвращен следующим вызовом next(). Это уникальная возможность, недоступная в базовом Iterator.
Семантика и поведение
Позиция вставки: Элемент вставляется перед элементом, который будет возвращен next(), или в конец списка, если next() вернет NoSuchElementException.
Влияние на итератор: После вставки последующий вызов next() вернет новый элемент, а previous() вернет только что вставленный элемент.
Состояние: Сбрасывает состояние lastReturned, поэтому последующий вызов remove() или set() выбросит исключение до следующего вызова next() или previous().
Внутренние механизмы реализации
Общий алгоритм
public void add(E e) {
checkForComodification();
try {
int i = cursor;
// Вставка в список
list.add(i, e);
// Корректировка состояния итератора
cursor = i + 1;
lastReturned = null;
nextIndex++;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}Реализация для ArrayList
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastReturned = null;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}Особенности:
Вызов ArrayList.add(index, element) со сдвигом элементов
Корректировка курсора и счетчиков
Обработка возможного расширения массива
Реализация для LinkedList
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null) {
// Вставка в конец
linkLast(e);
} else {
// Вставка перед next
linkBefore(e, next);
}
nextIndex++;
expectedModCount++;
}Особенности:
Эффективная вставка за O(1) после нахождения позиции
Манипуляции со ссылками между узлами
Автоматическая обработка граничных условий
Особенности производительности
Временная сложность:
ArrayList: O(n) — требуется сдвиг элементов
LinkedList: O(1) — вставка в найденную позицию
Память: Создает новый объект узла (LinkedList) или может вызвать расширение массива (ArrayList).
Потокобезопасность: Не является потокобезопасным.
#Java #для_новичков #beginner #ListIterator
👍2
Паттерны использования
Динамическое пополнение списка
Интерполяция пропущенных значений
Построение упорядоченного списка
Интеграция всех методов: Сложные сценарии использования
Полный цикл двунаправленной обработки
Динамическое пополнение списка
public static void expandListWithPrimes(List<Integer> numbers) {
ListIterator<Integer> iterator = numbers.listIterator();
while (iterator.hasNext()) {
Integer current = iterator.next();
if (isPrime(current)) {
// Вставляем квадрат простого числа перед ним
iterator.previous(); // Возвращаемся к простому числу
iterator.add(current * current);
iterator.next(); // Продолжаем с исходного элемента
}
}
}Интерполяция пропущенных значений
public static void interpolateMissing(List<Double> values) {
if (values.size() < 2) return;
ListIterator<Double> iterator = values.listIterator();
Double prev = iterator.next();
while (iterator.hasNext()) {
Double current = iterator.next();
if (current == null && prev != null) {
// Нашли null после non-null значения
// Возвращаемся к null и заменяем его интерполированным значением
iterator.previous(); // Переходим к null
iterator.set((prev + getNextNonNull(iterator)) / 2);
iterator.next(); // Продолжаем
}
prev = current;
}
}Построение упорядоченного списка
public static <T extends Comparable<T>> void insertSorted(List<T> sortedList, T newElement) {
ListIterator<T> iterator = sortedList.listIterator();
while (iterator.hasNext()) {
if (newElement.compareTo(iterator.next()) < 0) {
// Нашли позицию для вставки
iterator.previous(); // Возвращаемся к элементу, который больше нового
iterator.add(newElement);
return;
}
}
// Новый элемент больше всех существующих
iterator.add(newElement);
}Интеграция всех методов: Сложные сценарии использования
Полный цикл двунаправленной обработки
public class AdvancedListProcessing {
public static <T> void bidirectionalTransform(List<T> list,
Function<T, T> forwardTransform,
Function<T, T> backwardTransform) {
ListIterator<T> iterator = list.listIterator();
// Фаза 1: Прямой обход с трансформацией
while (iterator.hasNext()) {
T original = iterator.next();
T transformed = forwardTransform.apply(original);
iterator.set(transformed);
}
// Фаза 2: Обратный обход с дополнительной трансформацией
while (iterator.hasPrevious()) {
T current = iterator.previous();
T doublyTransformed = backwardTransform.apply(current);
// Условная вставка нового элемента
if (shouldInsertAfter(current)) {
iterator.next(); // Переходим к следующей позиции для вставки
iterator.add(generateNewElement(current));
iterator.previous(); // Возвращаемся к текущему элементу
}
iterator.set(doublyTransformed);
}
}
public static <T> List<T> mergeAdjacentDuplicates(List<T> list) {
if (list.size() < 2) {
return new ArrayList<>(list);
}
ListIterator<T> iterator = list.listIterator();
List<T> result = new ArrayList<>();
T current = iterator.next();
result.add(current);
while (iterator.hasNext()) {
T next = iterator.next();
if (!current.equals(next)) {
// Разные элементы - добавляем в результат
result.add(next);
current = next;
} else {
// Дубликат - пропускаем
// При необходимости можно выполнить merge операцию
mergeLastTwo(result);
}
}
return result;
}
public static <T> void processWithLookaheadAndLookbehind(List<T> list,👍2
BiFunction<T, T, T> lookaheadProcessor,
BiFunction<T, T, T> lookbehindProcessor) {
if (list.size() < 2) return;
ListIterator<T> iterator = list.listIterator();
// Обработка с lookahead
while (iterator.hasNext()) {
int currentIndex = iterator.nextIndex();
T current = iterator.next();
if (iterator.hasNext()) {
// Есть следующий элемент для lookahead
T lookahead = iterator.next();
T processed = lookaheadProcessor.apply(current, lookahead);
// Возвращаемся и заменяем текущий элемент
iterator.previous(); // К lookahead
iterator.previous(); // К current
iterator.set(processed);
iterator.next(); // К lookahead
iterator.next(); // К следующему элементу для продолжения
}
}
// Обработка с lookbehind
while (iterator.hasPrevious()) {
T current = iterator.previous();
if (iterator.hasPrevious()) {
T lookbehind = iterator.previous();
T processed = lookbehindProcessor.apply(current, lookbehind);
// Возвращаемся и заменяем
iterator.next(); // К lookbehind
iterator.next(); // К current
iterator.set(processed);
iterator.previous(); // К lookbehind для продолжения
}
}
}
}
Управление состоянием и переходы
Диаграмма состояний ListIterator
Состояния:
1. Инициализация: lastReturned = null, cursor = 0
2. После next(): lastReturned = элемент, cursor = nextIndex
3. После previous(): lastReturned = элемент, cursor = previousIndex + 1
4. После remove(): lastReturned = null
5. После set(): lastReturned остается установленным
6. После add(): lastReturned = null, cursor увеличивается на 1
Допустимые переходы:
- next() → remove() ✓, set() ✓, add() ✓
- previous() → remove() ✓, set() ✓, add() ✓
- remove() → next() ✓, previous() ✓, add() ✗, set() ✗, remove() ✗
- set() → next() ✓, previous() ✓, remove() ✗, add() ✗
- add() → next() ✓, previous() ✓, remove() ✗, set() ✗
Валидация последовательности операций
public class ListIteratorValidator {
public static <T> boolean validateOperationSequence(ListIterator<T> iterator,
List<String> operations) {
boolean canRemoveOrSet = false;
for (String op : operations) {
switch (op) {
case "next":
if (!iterator.hasNext()) return false;
iterator.next();
canRemoveOrSet = true;
break;
case "previous":
if (!iterator.hasPrevious()) return false;
iterator.previous();
canRemoveOrSet = true;
break;
case "remove":
if (!canRemoveOrSet) return false;
iterator.remove();
canRemoveOrSet = false;
break;
case "set":
if (!canRemoveOrSet) return false;
// Необходим элемент для замены
iterator.set(null); // Упрощенная проверка
canRemoveOrSet = false;
break;
case "add":
iterator.add(null); // Всегда допустимо
canRemoveOrSet = false;
break;
default:
return false;
}
}
return true;
}
}#Java #для_новичков #beginner #ListIterator
👍2
Оптимизированные паттерны использования
Эффективное использование для ArrayList
Эффективное использование для LinkedList
#Java #для_новичков #beginner #ListIterator
Эффективное использование для ArrayList
public class ArrayListOptimizations {
// Избегайте частых add() и remove() в середине списка
public static void efficientArrayListProcessing(List<String> list) {
// Вместо множественных add() через ListIterator
// Собирайте изменения и применяйте их пакетно
List<String> toAdd = new ArrayList<>();
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
String current = iterator.next();
if (shouldDuplicate(current)) {
toAdd.add(current + "_copy");
}
}
// Пакетное добавление в конец
list.addAll(toAdd);
}
// Используйте set() вместо remove() + add() для замены
public static void replaceEfficiently(List<Integer> numbers) {
ListIterator<Integer> iterator = numbers.listIterator();
while (iterator.hasNext()) {
Integer current = iterator.next();
if (current % 2 == 0) {
iterator.set(current * 2); // Эффективнее, чем remove() + add()
}
}
}
}Эффективное использование для LinkedList
public class LinkedListOptimizations {
// LinkedList идеально подходит для частых вставок/удалений через ListIterator
public static void efficientLinkedListProcessing(LinkedList<String> list) {
ListIterator<String> iterator = list.listIterator();
// Частые вставки эффективны
while (iterator.hasNext()) {
String current = iterator.next();
if (requiresPrefix(current)) {
iterator.add("prefix_" + current);
}
}
// Частые удаления также эффективны
iterator = list.listIterator();
while (iterator.hasNext()) {
if (shouldRemove(iterator.next())) {
iterator.remove();
}
}
}
// Использование previous() для навигации назад
public static void findAndProcessFromEnd(LinkedList<String> list, String target) {
ListIterator<String> iterator = list.listIterator(list.size());
while (iterator.hasPrevious()) {
String current = iterator.previous();
if (current.equals(target)) {
// Нашли - обрабатываем и можем идти в обе стороны
processFound(current);
// Можем продолжить в любом направлении
if (iterator.hasPrevious()) {
processPrevious(iterator.previous());
}
if (iterator.hasNext()) {
processNext(iterator.next());
}
break;
}
}
}
}#Java #для_новичков #beginner #ListIterator
👍2
Best Practices
1. Выбор начальной позиции
2. Минимизация переходов между next() и previous()
3. Использование nextIndex()/previousIndex() для логики, зависящей от позиции
4. Безопасность в многопоточных сценариях
#Java #для_новичков #beginner #ListIterator
1. Выбор начальной позиции
// Начинайте с нужной позиции, а не всегда с начала
ListIterator<T> iterator = list.listIterator(startPosition);
// Для обработки с конца
ListIterator<T> reverseIterator = list.listIterator(list.size());
2. Минимизация переходов между next() и previous()
// Неэффективно:
while (iterator.hasNext()) {
T current = iterator.next();
if (condition(current)) {
iterator.previous(); // Дорогой переход
iterator.add(newElement);
iterator.next(); // Еще один переход
iterator.next(); // Пропускаем добавленный элемент
}
}
// Более эффективно:
while (iterator.hasNext()) {
T current = iterator.next();
if (condition(current)) {
iterator.add(newElement);
// current остался тем же, newElement теперь перед ним
}
}
3. Использование nextIndex()/previousIndex() для логики, зависящей от позиции
ListIterator<T> iterator = list.listIterator();
while (iterator.hasNext()) {
int currentIndex = iterator.nextIndex();
T element = iterator.next();
if (currentIndex % 2 == 0) {
// Обработка четных позиций
processEven(element, currentIndex);
}
}
4. Безопасность в многопоточных сценариях
// Всегда синхронизируйте доступ к ListIterator
synchronized(list) {
ListIterator<T> iterator = list.listIterator();
while (iterator.hasNext()) {
// Обработка
}
}
// Или используйте потокобезопасные альтернативы
List<T> syncList = Collections.synchronizedList(new ArrayList<>());
// Но даже synchronizedList требует внешней синхронизации для итерации
#Java #для_новичков #beginner #ListIterator
👍2
Что выведет код?
#Tasks
import java.util.*;
public class Task121225 {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
ListIterator<String> it = list.listIterator();
System.out.print(it.next() + " ");
System.out.print(it.next() + " ");
it.add("X");
System.out.print(it.previous() + " ");
System.out.print(it.next() + " ");
System.out.print(it.previous() + " ");
it.remove();
System.out.println(list);
}
}
#Tasks
👍1
Варианты ответа:
Anonymous Quiz
38%
A B X C X [A, B, C, D]
38%
A B X X X [A, B, X, C, D]
25%
A B X X [A, B, C, D]
0%
A B X X [A, B, X, C, D]
👍1
Вопрос с собеседований
Как работает ReentrantReadWriteLock?🤓
Ответ:
Он разделяет блокировки на read и write.
Несколько читателей могут работать одновременно, но запись блокируется полностью. Это повышает производительность, если чтений больше, чем записей.
Однако write-блокировка может вызывать «голодание» читателей.
#собеседование
Как работает ReentrantReadWriteLock?
Ответ:
Несколько читателей могут работать одновременно, но запись блокируется полностью. Это повышает производительность, если чтений больше, чем записей.
Однако write-блокировка может вызывать «голодание» читателей.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
История IT-технологий сегодня — 13 декабря
ℹ️ Кто родился в этот день
Дже́йми Ве́рнер Зави́нски (англ. Jamie Werner Zawinski, известный также как jwz; род. 3 ноября 1968, Питтсбург, Пенсильвания) — программист, один из ключевых разработчиков Netscape и Mozilla; активно повлиял на развитие ранних браузеров и веб-экосистемы.
🌐 Знаковые события
1967 — в США состоялся успешный запуск космического аппарата «Пионер-8».
#Biography #Birth_Date #Events #13Декабря
Дже́йми Ве́рнер Зави́нски (англ. Jamie Werner Zawinski, известный также как jwz; род. 3 ноября 1968, Питтсбург, Пенсильвания) — программист, один из ключевых разработчиков Netscape и Mozilla; активно повлиял на развитие ранних браузеров и веб-экосистемы.
1967 — в США состоялся успешный запуск космического аппарата «Пионер-8».
#Biography #Birth_Date #Events #13Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
С 06.12 по 12.12
Предыдущий пост(с С 29.11 по 05.12)
Воскресный мотивационный пост:
Не было мотивации
Запись встреч/видео:
не было
Обучающие статьи:
Java:
Коллекции в Java
Глава 2. List — списки в Java
Практика
Глава 6. Итераторы
Интерфейс Iterator — фундаментальный механизм обхода коллекций
Интерфейс ListIterator — двунаправленный обход и расширенные возможности
Spring Cloud Gateway
Конфигурация маршрутов в Spring Cloud Gateway
Predicates (условия маршрутизации)
Полезные статьи и видео:
ПОДКЛЮЧЕНИЕ GPT GO на ГОД!
Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
Предыдущий пост(с С 29.11 по 05.12)
Воскресный мотивационный пост:
Не было мотивации
Запись встреч/видео:
не было
Обучающие статьи:
Java:
Коллекции в Java
Глава 2. List — списки в Java
Практика
Глава 6. Итераторы
Интерфейс Iterator — фундаментальный механизм обхода коллекций
Интерфейс ListIterator — двунаправленный обход и расширенные возможности
Spring Cloud Gateway
Конфигурация маршрутов в Spring Cloud Gateway
Predicates (условия маршрутизации)
Полезные статьи и видео:
ПОДКЛЮЧЕНИЕ GPT GO на ГОД!
Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
👍1
Ну что встречаемся завтра?
Можно что-то новое рассмотреть, если желание есть😜
Предлагайте👍
Можно что-то новое рассмотреть, если желание есть
Предлагайте
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
История IT-технологий сегодня — 14 декабря
ℹ️ Кто родился в этот день
Никола́й Генна́диевич Ба́сов (14 декабря 1922, Усмань, Тамбовская губерния, РСФСР — 1 июля 2001, Москва) — советский и российский физик, лауреат Нобелевской премии по физике (1964), Ленинской премии (1959) и Государственной премии СССР (1989). Дважды Герой Социалистического Труда (1969, 1982). Внёс значительный вклад в развитие квантовой электроники и создание лазерных установок.
🌐 Знаковые события
1977 — введена в эксплуатацию одна из первых коммерческих линий связи на основе оптоволокна в США.
#Biography #Birth_Date #Events #14Декабря
Никола́й Генна́диевич Ба́сов (14 декабря 1922, Усмань, Тамбовская губерния, РСФСР — 1 июля 2001, Москва) — советский и российский физик, лауреат Нобелевской премии по физике (1964), Ленинской премии (1959) и Государственной премии СССР (1989). Дважды Герой Социалистического Труда (1969, 1982). Внёс значительный вклад в развитие квантовой электроники и создание лазерных установок.
1977 — введена в эксплуатацию одна из первых коммерческих линий связи на основе оптоволокна в США.
#Biography #Birth_Date #Events #14Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
История IT-технологий сегодня — 15 декабря
ℹ️ Кто родился в этот день
Антуа́н Анри́ Беккере́ль (фр. Antoine Henri Becquerel; 15 декабря 1852 — 25 августа 1908) — французский физик, лауреат Нобелевской премии по физике и один из первооткрывателей радиоактивности. В честь него названа единица измерения активности радиоактивного источника в Международной системе единиц (СИ) — беккерель.
🌐 Знаковые события
1970 — советская межпланетная станция «Венера-7» успешно совершила посадку на Венере. Первая успешная посадка на другой планете космического аппарата, посланного с Земли.
1972 — Деннис Ритчи завершил первую работоспособную версию языка C для UNIX.
1984 — была запущена автоматическая межпланетная станция «Вега-1».
#Biography #Birth_Date #Events #15Декабря
Антуа́н Анри́ Беккере́ль (фр. Antoine Henri Becquerel; 15 декабря 1852 — 25 августа 1908) — французский физик, лауреат Нобелевской премии по физике и один из первооткрывателей радиоактивности. В честь него названа единица измерения активности радиоактивного источника в Международной системе единиц (СИ) — беккерель.
1970 — советская межпланетная станция «Венера-7» успешно совершила посадку на Венере. Первая успешная посадка на другой планете космического аппарата, посланного с Земли.
1972 — Деннис Ритчи завершил первую работоспособную версию языка C для UNIX.
1984 — была запущена автоматическая межпланетная станция «Вега-1».
#Biography #Birth_Date #Events #15Декабря
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Predicates в Spring Cloud Gateway: Механика и расширяемость условий маршрутизации
Механизм работы предикатов: от конфигурации к исполнению
Предикаты в Spring Cloud Gateway — это условия, определяющие, должен ли конкретный маршрут быть применён к входящему запросу. На архитектурном уровне предикаты реализуют паттерн Factory Method через абстракцию RoutePredicateFactory. Каждый предикат компилируется в объект Predicate<ServerWebExchange>, который затем оценивается в процессе сопоставления маршрута.
Процесс инициализации предиката
Когда приложение Spring Cloud Gateway стартует, происходит следующая последовательность:
Парсинг конфигурации: Конфигурационный файл (YAML или properties) читается, и определения предикатов преобразуются в объекты PredicateDefinition.
Поиск фабрик: Для каждого PredicateDefinition Spring ищет соответствующий bean типа RoutePredicateFactory. Имя фабрики определяется по свойству name в определении предиката.
Создание конфигурационного объекта: Фабрика создаёт конфигурационный объект (обычно static inner class), который заполняется значениями из аргументов предиката.
Генерация предиката: Вызывается метод apply() фабрики, который возвращает функциональный интерфейс Predicate<ServerWebExchange>.
Порядок оценки предикатов
Предикаты внутри маршрута оцениваются в порядке их объявления, но с важной оптимизацией: оценка происходит лениво и останавливается при первом false.
Механизм оценки реализован через цепочку вызовов and():
Детальный разбор встроенных предикатов
Path Predicate: эффективное сопоставление путей
Предикат Path — наиболее часто используемый. Внутри он использует PathPatternParser из Spring WebFlux, который компилирует строковые шаблоны в оптимизированные структуры данных.
Механика работы:
Использование переменных пути:
Host Predicate: виртуальные хосты и шаблоны
Предикат Host позволяет маршрутизировать на основе заголовка Host или имени сервера.
Расширенные шаблоны:
Шаблон {subdomain}.api.example.com извлекает поддомен как переменную.
Внутренне это преобразуется в регулярное выражение:
Query Predicate: параметры запроса
Предикат Query проверяет наличие и значение параметров URL.
Конфигурация с регулярными выражениями:
Внутренняя реализация использует ServerWebExchange.getRequest().getQueryParams() для доступа к параметрам. Важно отметить, что параметры кэшируются после первого чтения для эффективности.
#Java #middle #Spring_Cloud_Gateway
Механизм работы предикатов: от конфигурации к исполнению
Предикаты в Spring Cloud Gateway — это условия, определяющие, должен ли конкретный маршрут быть применён к входящему запросу. На архитектурном уровне предикаты реализуют паттерн Factory Method через абстракцию RoutePredicateFactory. Каждый предикат компилируется в объект Predicate<ServerWebExchange>, который затем оценивается в процессе сопоставления маршрута.
Процесс инициализации предиката
Когда приложение Spring Cloud Gateway стартует, происходит следующая последовательность:
Парсинг конфигурации: Конфигурационный файл (YAML или properties) читается, и определения предикатов преобразуются в объекты PredicateDefinition.
Поиск фабрик: Для каждого PredicateDefinition Spring ищет соответствующий bean типа RoutePredicateFactory. Имя фабрики определяется по свойству name в определении предиката.
Создание конфигурационного объекта: Фабрика создаёт конфигурационный объект (обычно static inner class), который заполняется значениями из аргументов предиката.
Генерация предиката: Вызывается метод apply() фабрики, который возвращает функциональный интерфейс Predicate<ServerWebExchange>.
Порядок оценки предикатов
Предикаты внутри маршрута оцениваются в порядке их объявления, но с важной оптимизацией: оценка происходит лениво и останавливается при первом false.
Механизм оценки реализован через цепочку вызовов and():
// Внутренняя реализация оценки предикатов маршрута
public boolean test(ServerWebExchange exchange) {
for (Predicate<ServerWebExchange> predicate : predicates) {
if (!predicate.test(exchange)) {
return false; // Прерывание при первом false
}
}
return true;
}
Детальный разбор встроенных предикатов
Path Predicate: эффективное сопоставление путей
Предикат Path — наиболее часто используемый. Внутри он использует PathPatternParser из Spring WebFlux, который компилирует строковые шаблоны в оптимизированные структуры данных.
Механика работы:
// Пример внутренней реализации
public class PathRoutePredicateFactory extends
AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
final PathPattern pattern = pathPatternParser.parse(config.getPattern());
return exchange -> {
PathContainer path = exchange.getRequest().getPath();
PathPattern.PathMatchInfo info = pattern.matchAndExtract(path);
if (info != null) {
// Сохранение извлечённых переменных в атрибуты
exchange.getAttributes().put(
PathPatternRoutePredicateHandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
info.getUriVariables()
);
return true;
}
return false;
};
}
}
Использование переменных пути:
predicates:
- Path=/api/users/{id}/orders/{orderId}
Извлечённые переменные id и orderId доступны в фильтрах через шаблоны ${id} и ${orderId}.
Host Predicate: виртуальные хосты и шаблоны
Предикат Host позволяет маршрутизировать на основе заголовка Host или имени сервера.
Расширенные шаблоны:
predicates:
- Host=**.example.com,{subdomain}.api.example.com
Шаблон {subdomain}.api.example.com извлекает поддомен как переменную.
Внутренне это преобразуется в регулярное выражение:
// Пример генерации паттерна
String pattern = "^(?<subdomain>[^.]+)\\.api\\.example\\.com$";
Query Predicate: параметры запроса
Предикат Query проверяет наличие и значение параметров URL.
Конфигурация с регулярными выражениями:
predicates:
- Query=version,v[0-9]+\.[0-9]+ # Проверка формата версии
- Query=token # Просто наличие параметра
Внутренняя реализация использует ServerWebExchange.getRequest().getQueryParams() для доступа к параметрам. Важно отметить, что параметры кэшируются после первого чтения для эффективности.
#Java #middle #Spring_Cloud_Gateway
👍2
RemoteAddr Predicate: контроль доступа по IP
Этот предикат проверяет IP-адрес клиента через CIDR-нотацию.
Механика работы:
Важные нюансы:
При использовании за прокси (nginx, load balancer) необходимо корректно настраивать X-Forwarded-For
IP-адрес извлекается из ServerHttpRequest.getRemoteAddress(), который может быть обёрткой для реального соединения
Weight Predicate: взвешенная маршрутизация
Предикат Weight используется для канареечных развёртываний и A/B тестирования. Он работает в паре: один маршрут определяет группу, а другие маршруты распределяют вес внутри группы.
Конфигурация:
Внутренняя механика:
При инициализации создаётся общий AtomicInteger для группы
Для каждого запроса вычисляется хэш (обычно на основе пути и/или заголовков)
Хэш мапируется на диапазон весов для выбора маршрута
Выбор детерминирован для одинаковых хэшей, что обеспечивает согласованную маршрутизацию
Method Predicate: фильтрация по HTTP-методам
Простейший, но эффективный предикат для ограничения доступа:
Сложные комбинации через AND/OR
Spring Cloud Gateway поддерживает логические комбинации предикатов через DSL и конфигурацию.
Комбинации через YAML
В YAML предикаты по умолчанию объединяются через логическое И (AND):
Все четыре условия должны быть истинны для применения маршрута.
Логическое ИЛИ через кастомный предикат
Для реализации ИЛИ необходимо создать составной предикат:
Использование в YAML:
Негация (NOT) через After с отрицанием
Прямой поддержки NOT нет, но можно использовать временные предикаты с отрицанием:
#Java #middle #Spring_Cloud_Gateway
Этот предикат проверяет IP-адрес клиента через CIDR-нотацию.
Механика работы:
public Predicate<ServerWebExchange> apply(Config config) {
List<IpSubnetFilterRule> sources = config.getSources().stream()
.map(IpSubnetFilterRule::new)
.collect(Collectors.toList());
return exchange -> {
InetSocketAddress remoteAddress = exchange.getRequest()
.getRemoteAddress();
if (remoteAddress != null) {
for (IpSubnetFilterRule source : sources) {
if (source.matches(remoteAddress)) {
return true;
}
}
}
return false;
};
}Важные нюансы:
При использовании за прокси (nginx, load balancer) необходимо корректно настраивать X-Forwarded-For
IP-адрес извлекается из ServerHttpRequest.getRemoteAddress(), который может быть обёрткой для реального соединения
Weight Predicate: взвешенная маршрутизация
Предикат Weight используется для канареечных развёртываний и A/B тестирования. Он работает в паре: один маршрут определяет группу, а другие маршруты распределяют вес внутри группы.
Конфигурация:
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.example.org
predicates:
- Path=/api/**
- Weight=group1, 80
metadata:
response-timeout: 3000
- id: weight_low
uri: https://weightlow.example.org
predicates:
- Path=/api/**
- Weight=group1, 20
metadata:
response-timeout: 5000
Внутренняя механика:
При инициализации создаётся общий AtomicInteger для группы
Для каждого запроса вычисляется хэш (обычно на основе пути и/или заголовков)
Хэш мапируется на диапазон весов для выбора маршрута
Выбор детерминирован для одинаковых хэшей, что обеспечивает согласованную маршрутизацию
Method Predicate: фильтрация по HTTP-методам
Простейший, но эффективный предикат для ограничения доступа:
predicates:
- Method=GET,POST,OPTIONS
Внутренне преобразуется в проверку exchange.getRequest().getMethod().
Сложные комбинации через AND/OR
Spring Cloud Gateway поддерживает логические комбинации предикатов через DSL и конфигурацию.
Комбинации через YAML
В YAML предикаты по умолчанию объединяются через логическое И (AND):
predicates:
- Path=/api/**
- Method=GET
- Header=X-API-Key,.*
- Query=version,v2
Все четыре условия должны быть истинны для применения маршрута.
Логическое ИЛИ через кастомный предикат
Для реализации ИЛИ необходимо создать составной предикат:
@Component
public class OrRoutePredicateFactory extends
AbstractRoutePredicateFactory<OrRoutePredicateFactory.Config> {
public OrRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
for (PredicateDefinition def : config.getPredicates()) {
RoutePredicateFactory factory = findFactory(def);
if (factory != null && factory.apply(convert(def)).test(exchange)) {
return true;
}
}
return false;
};
}
public static class Config {
private List<PredicateDefinition> predicates = new ArrayList<>();
// геттеры и сеттеры
}
}
Использование в YAML:
predicates:
- name: Or
args:
predicates:
- name: Path
args:
pattern: /api/v1/**
- name: Path
args:
pattern: /legacy/api/**
Негация (NOT) через After с отрицанием
Прямой поддержки NOT нет, но можно использовать временные предикаты с отрицанием:
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// Инвертирование любого предиката
return !delegatePredicate.test(exchange);
};
}#Java #middle #Spring_Cloud_Gateway
👍2
Создание кастомного предиката
Реализация RoutePredicateFactory
Создание кастомного предиката требует реализации интерфейса RoutePredicateFactory:
#Java #middle #Spring_Cloud_Gateway
Реализация RoutePredicateFactory
Создание кастомного предиката требует реализации интерфейса RoutePredicateFactory:
@Component
public class JwtClaimRoutePredicateFactory extends
AbstractRoutePredicateFactory<JwtClaimRoutePredicateFactory.Config> {
private final JwtDecoder jwtDecoder;
public JwtClaimRoutePredicateFactory(JwtDecoder jwtDecoder) {
super(Config.class);
this.jwtDecoder = jwtDecoder;
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// Извлечение JWT из заголовка
String authHeader = exchange.getRequest()
.getHeaders()
.getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return false;
}
String token = authHeader.substring(7);
try {
// Декодирование и проверка claims
Jwt jwt = jwtDecoder.decode(token);
// Проверка конкретного claim
String claimValue = jwt.getClaimAsString(config.getClaimName());
if (claimValue == null) {
return false;
}
// Сопоставление с ожидаемым значением
if (config.getPattern() != null) {
return claimValue.matches(config.getPattern());
}
return config.getExpectedValues()
.stream()
.anyMatch(expected -> expected.equals(claimValue));
} catch (JwtException e) {
return false;
}
};
}
// Конфигурационный класс
public static class Config {
private String claimName;
private String pattern;
private List<String> expectedValues = new ArrayList<>();
// Геттеры и сеттеры
public String getClaimName() {
return claimName;
}
public void setClaimName(String claimName) {
this.claimName = claimName;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
public List<String> getExpectedValues() {
return expectedValues;
}
public void setExpectedValues(List<String> expectedValues) {
this.expectedValues = expectedValues;
}
}
// Короткая форма конфигурации
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("claimName", "pattern");
}
}
#Java #middle #Spring_Cloud_Gateway
👍2
Регистрация и использование
Spring Boot автоматически обнаружит компонент через аннотацию @Component.
Для использования в YAML:
Или в краткой форме, если реализован shortcutFieldOrder():
Предикат с динамической конфигурацией
Для предикатов, требующих внешней конфигурации или состояния:
Асинхронные предикаты
Для предикатов, требующих асинхронных операций (обращение к БД, внешним API):
Асинхронные предикаты возвращают AsyncPredicate<ServerWebExchange>, который оценивается в реактивном контексте и не блокирует event loop threads.
#Java #middle #Spring_Cloud_Gateway
Spring Boot автоматически обнаружит компонент через аннотацию @Component.
Для использования в YAML:
predicates:
- name: JwtClaim
args:
claimName: roles
expectedValues: admin,superuser
Или в краткой форме, если реализован shortcutFieldOrder():
predicates:
- JwtClaim=roles,admin|superuser
Предикат с динамической конфигурацией
Для предикатов, требующих внешней конфигурации или состояния:
@Component
public class RateLimitPredicateFactory extends
AbstractRoutePredicateFactory<RateLimitPredicateFactory.Config>
implements ApplicationListener<EnvironmentChangeEvent> {
private final RateLimiter rateLimiter;
private final Map<String, Config> configCache = new ConcurrentHashMap<>();
public RateLimitPredicateFactory(RateLimiter rateLimiter) {
super(Config.class);
this.rateLimiter = rateLimiter;
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
// Кэширование конфигурации для производительности
String cacheKey = config.getClientId() + ":" + config.getLimit();
configCache.put(cacheKey, config);
return exchange -> {
Config cachedConfig = configCache.get(cacheKey);
// Извлечение идентификатора клиента из запроса
String clientId = extractClientId(exchange);
if (clientId == null || !clientId.equals(cachedConfig.getClientId())) {
return false;
}
// Проверка лимита
return rateLimiter.tryAcquire(clientId, cachedConfig.getLimit());
};
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
// Очистка кэша при изменении конфигурации
configCache.clear();
}
private String extractClientId(ServerWebExchange exchange) {
// Логика извлечения clientId из запроса
return exchange.getRequest().getHeaders()
.getFirst("X-Client-Id");
}
public static class Config {
private String clientId;
private int limit;
// Геттеры и сеттеры
}
}
Асинхронные предикаты
Для предикатов, требующих асинхронных операций (обращение к БД, внешним API):
@Component
public class ExternalServicePredicateFactory extends
AbstractRoutePredicateFactory<ExternalServicePredicateFactory.Config> {
private final WebClient webClient;
@Override
public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
return exchange -> {
// Асинхронная проверка через внешний сервис
return webClient.post()
.uri(config.getValidationUrl())
.bodyValue(buildValidationRequest(exchange))
.retrieve()
.bodyToMono(ValidationResponse.class)
.map(response -> response.isValid())
.onErrorReturn(false); // При ошибке считаем предикат false
};
}
public static class Config {
private String validationUrl;
private int timeoutMs = 1000;
// Геттеры и сеттеры
}
}
Асинхронные предикаты возвращают AsyncPredicate<ServerWebExchange>, который оценивается в реактивном контексте и не блокирует event loop threads.
#Java #middle #Spring_Cloud_Gateway
👍2
Производительность и оптимизации
Кэширование вычислений предикатов
Для дорогих предикатов важно кэшировать результаты:
Порядок предикатов для оптимизации
Располагайте предикаты в порядке возрастания вычислительной сложности:
Мониторинг и метрики
Интеграция с Micrometer для мониторинга эффективности предикатов:
#Java #middle #Spring_Cloud_Gateway
Кэширование вычислений предикатов
Для дорогих предикатов важно кэшировать результаты:
public Predicate<ServerWebExchange> apply(Config config) {
LoadingCache<ServerWebExchange, Boolean> cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(exchange -> computeExpensivePredicate(exchange, config));
return exchange -> {
try {
return cache.get(exchange);
} catch (Exception e) {
return false;
}
};
}Порядок предикатов для оптимизации
Располагайте предикаты в порядке возрастания вычислительной сложности:
predicates:
- Method=GET # Быстрая проверка
- Path=/api/** # Умеренная сложность
- Query=token,.* # Простая проверка параметра
- JwtClaim=roles,admin # Дорогая проверка JWT
Это позволяет отсеивать неподходящие запросы до выполнения дорогих операций.
Мониторинг и метрики
Интеграция с Micrometer для мониторинга эффективности предикатов:
public Predicate<ServerWebExchange> apply(Config config) {
Counter matchCounter = meterRegistry.counter(
"gateway.predicate.matches",
"predicate", config.getName()
);
Counter totalCounter = meterRegistry.counter(
"gateway.predicate.checks",
"predicate", config.getName()
);
Timer timer = meterRegistry.timer(
"gateway.predicate.duration",
"predicate", config.getName()
);
return exchange -> {
totalCounter.increment();
return timer.record(() -> {
boolean matches = internalTest(exchange, config);
if (matches) {
matchCounter.increment();
}
return matches;
});
};
}#Java #middle #Spring_Cloud_Gateway
👍3
Что выведет код?
#Tasks
public class Task151225 {
public static void main(String[] args) {
Boolean b1 = true;
Boolean b2 = new Boolean(true);
boolean b3 = true;
Boolean b4 = Boolean.valueOf(true);
System.out.println(b1 == b2);
System.out.println(b1 == b3);
System.out.println(b2 == b4);
System.out.println(b3 == b4);
}
}#Tasks
👍2